mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
347 lines
9.1 KiB
Go
347 lines
9.1 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package org
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/hanzoai/git/models/organization"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
api "github.com/hanzoai/git/modules/structs"
|
|
"github.com/hanzoai/git/routers/api/v1/user"
|
|
"github.com/hanzoai/git/routers/api/v1/utils"
|
|
"github.com/hanzoai/git/services/context"
|
|
"github.com/hanzoai/git/services/convert"
|
|
org_service "github.com/hanzoai/git/services/org"
|
|
)
|
|
|
|
// listMembers list an organization's members
|
|
func listMembers(ctx *context.APIContext, isMember bool) {
|
|
listOptions := utils.GetListOptions(ctx)
|
|
opts := &organization.FindOrgMembersOpts{
|
|
Doer: ctx.Doer,
|
|
IsDoerMember: isMember,
|
|
OrgID: ctx.Org.Organization.ID,
|
|
ListOptions: listOptions,
|
|
}
|
|
|
|
count, err := organization.CountOrgMembers(ctx, opts)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
members, _, err := organization.FindOrgMembers(ctx, opts)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
apiMembers := make([]*api.User, len(members))
|
|
for i, member := range members {
|
|
apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer)
|
|
}
|
|
|
|
ctx.SetLinkHeader(count, listOptions.PageSize)
|
|
ctx.SetTotalCountHeader(count)
|
|
ctx.JSON(http.StatusOK, apiMembers)
|
|
}
|
|
|
|
// ListMembers list an organization's members
|
|
func ListMembers(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/members organization orgListMembers
|
|
// ---
|
|
// summary: List an organization's members
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: page
|
|
// in: query
|
|
// description: page number of results to return (1-based)
|
|
// type: integer
|
|
// - name: limit
|
|
// in: query
|
|
// description: page size of results
|
|
// type: integer
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/UserList"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
var (
|
|
isMember bool
|
|
err error
|
|
)
|
|
|
|
if ctx.Doer != nil {
|
|
isMember, err = ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
}
|
|
listMembers(ctx, isMember)
|
|
}
|
|
|
|
// ListPublicMembers list an organization's public members
|
|
func ListPublicMembers(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/public_members organization orgListPublicMembers
|
|
// ---
|
|
// summary: List an organization's public members
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: page
|
|
// in: query
|
|
// description: page number of results to return (1-based)
|
|
// type: integer
|
|
// - name: limit
|
|
// in: query
|
|
// description: page size of results
|
|
// type: integer
|
|
// produces:
|
|
// - application/json
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/UserList"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
listMembers(ctx, false)
|
|
}
|
|
|
|
// IsMember check if a user is a member of an organization
|
|
func IsMember(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/members/{username} organization orgIsMember
|
|
// ---
|
|
// summary: Check if a user is a member of an organization
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user to check for an organization membership
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// description: user is a member
|
|
// "303":
|
|
// description: redirection to /orgs/{org}/public_members/{username}
|
|
// "404":
|
|
// description: user is not a member
|
|
|
|
userToCheck := user.GetContextUserByPathParam(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
if ctx.Doer != nil {
|
|
userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
} else if userIsMember || ctx.Doer.IsAdmin {
|
|
userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, userToCheck.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
} else if userToCheckIsMember {
|
|
ctx.Status(http.StatusNoContent)
|
|
} else {
|
|
ctx.APIErrorNotFound()
|
|
}
|
|
return
|
|
} else if ctx.Doer.ID == userToCheck.ID {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
}
|
|
|
|
redirectURL := setting.AppSubURL + "/v1/orgs/" + url.PathEscape(ctx.Org.Organization.Name) + "/public_members/" + url.PathEscape(userToCheck.Name)
|
|
ctx.Redirect(redirectURL)
|
|
}
|
|
|
|
// IsPublicMember check if a user is a public member of an organization
|
|
func IsPublicMember(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/public_members/{username} organization orgIsPublicMember
|
|
// ---
|
|
// summary: Check if a user is a public member of an organization
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user to check for a public organization membership
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// description: user is a public member
|
|
// "404":
|
|
// description: user is not a public member
|
|
|
|
userToCheck := user.GetContextUserByPathParam(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
is, err := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, userToCheck.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
if is {
|
|
ctx.Status(http.StatusNoContent)
|
|
} else {
|
|
ctx.APIErrorNotFound()
|
|
}
|
|
}
|
|
|
|
func checkCanChangeOrgUserStatus(ctx *context.APIContext, targetUser *user_model.User) {
|
|
// allow user themselves to change their status, and allow admins to change any user
|
|
if targetUser.ID == ctx.Doer.ID || ctx.Doer.IsAdmin {
|
|
return
|
|
}
|
|
// allow org owners to change status of members
|
|
isOwner, err := ctx.Org.Organization.IsOwnedBy(ctx, ctx.Doer.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
} else if !isOwner {
|
|
ctx.APIError(http.StatusForbidden, "Cannot change member visibility")
|
|
}
|
|
}
|
|
|
|
// PublicizeMember make a member's membership public
|
|
func PublicizeMember(ctx *context.APIContext) {
|
|
// swagger:operation PUT /orgs/{org}/public_members/{username} organization orgPublicizeMember
|
|
// ---
|
|
// summary: Publicize a user's membership
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user whose membership is to be publicized
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// description: membership publicized
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
userToPublicize := user.GetContextUserByPathParam(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
checkCanChangeOrgUserStatus(ctx, userToPublicize)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToPublicize.ID, true)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// ConcealMember make a member's membership not public
|
|
func ConcealMember(ctx *context.APIContext) {
|
|
// swagger:operation DELETE /orgs/{org}/public_members/{username} organization orgConcealMember
|
|
// ---
|
|
// summary: Conceal a user's membership
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user whose membership is to be concealed
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// "$ref": "#/responses/empty"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
userToConceal := user.GetContextUserByPathParam(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
checkCanChangeOrgUserStatus(ctx, userToConceal)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToConceal.ID, false)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// DeleteMember remove a member from an organization
|
|
func DeleteMember(ctx *context.APIContext) {
|
|
// swagger:operation DELETE /orgs/{org}/members/{username} organization orgDeleteMember
|
|
// ---
|
|
// summary: Remove a member from an organization
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: username
|
|
// in: path
|
|
// description: username of the user to remove from the organization
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// description: member removed
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
member := user.GetContextUserByPathParam(ctx)
|
|
if ctx.Written() {
|
|
return
|
|
}
|
|
if err := org_service.RemoveOrgUser(ctx, ctx.Org.Organization, member); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|