summaryrefslogtreecommitdiffstats
path: root/routers/api/v1/org
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /routers/api/v1/org
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--routers/api/v1/org/action.go473
-rw-r--r--routers/api/v1/org/avatar.go80
-rw-r--r--routers/api/v1/org/hook.go189
-rw-r--r--routers/api/v1/org/label.go258
-rw-r--r--routers/api/v1/org/member.go325
-rw-r--r--routers/api/v1/org/org.go559
-rw-r--r--routers/api/v1/org/quota.go155
-rw-r--r--routers/api/v1/org/team.go891
8 files changed, 2930 insertions, 0 deletions
diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go
new file mode 100644
index 0000000..03a1fa8
--- /dev/null
+++ b/routers/api/v1/org/action.go
@@ -0,0 +1,473 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "errors"
+ "net/http"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ secret_model "code.gitea.io/gitea/models/secret"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ actions_service "code.gitea.io/gitea/services/actions"
+ "code.gitea.io/gitea/services/context"
+ secret_service "code.gitea.io/gitea/services/secrets"
+)
+
+// ListActionsSecrets list an organization's actions secrets
+func (Action) ListActionsSecrets(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/secrets organization orgListActionsSecrets
+ // ---
+ // summary: List an organization's actions secrets
+ // 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/SecretList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ opts := &secret_model.FindSecretsOptions{
+ OwnerID: ctx.Org.Organization.ID,
+ ListOptions: utils.GetListOptions(ctx),
+ }
+
+ secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ apiSecrets := make([]*api.Secret, len(secrets))
+ for k, v := range secrets {
+ apiSecrets[k] = &api.Secret{
+ Name: v.Name,
+ Created: v.CreatedUnix.AsTime(),
+ }
+ }
+
+ ctx.SetTotalCountHeader(count)
+ ctx.JSON(http.StatusOK, apiSecrets)
+}
+
+// create or update one secret of the organization
+func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
+ // swagger:operation PUT /orgs/{org}/actions/secrets/{secretname} organization updateOrgSecret
+ // ---
+ // summary: Create or Update a secret value in an organization
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of organization
+ // type: string
+ // required: true
+ // - name: secretname
+ // in: path
+ // description: name of the secret
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/CreateOrUpdateSecretOption"
+ // responses:
+ // "201":
+ // description: response when creating a secret
+ // "204":
+ // description: response when updating a secret
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
+
+ _, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
+ if err != nil {
+ if errors.Is(err, util.ErrInvalidArgument) {
+ ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
+ } else if errors.Is(err, util.ErrNotExist) {
+ ctx.Error(http.StatusNotFound, "CreateOrUpdateSecret", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "CreateOrUpdateSecret", err)
+ }
+ return
+ }
+
+ if created {
+ ctx.Status(http.StatusCreated)
+ } else {
+ ctx.Status(http.StatusNoContent)
+ }
+}
+
+// DeleteSecret delete one secret of the organization
+func (Action) DeleteSecret(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org}/actions/secrets/{secretname} organization deleteOrgSecret
+ // ---
+ // summary: Delete a secret in an organization
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of organization
+ // type: string
+ // required: true
+ // - name: secretname
+ // in: path
+ // description: name of the secret
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: delete one secret of the organization
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
+ if err != nil {
+ if errors.Is(err, util.ErrInvalidArgument) {
+ ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
+ } else if errors.Is(err, util.ErrNotExist) {
+ ctx.Error(http.StatusNotFound, "DeleteSecret", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "DeleteSecret", err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
+// GetRegistrationToken returns the token to register org runners
+func (Action) GetRegistrationToken(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
+ // ---
+ // summary: Get an organization's actions runner registration token
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/RegistrationToken"
+
+ shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
+}
+
+// ListVariables list org-level variables
+func (Action) ListVariables(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
+ // ---
+ // summary: Get an org-level variables list
+ // 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/VariableList"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
+ OwnerID: ctx.Org.Organization.ID,
+ ListOptions: utils.GetListOptions(ctx),
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "FindVariables", err)
+ return
+ }
+
+ variables := make([]*api.ActionVariable, len(vars))
+ for i, v := range vars {
+ variables[i] = &api.ActionVariable{
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ }
+ }
+
+ ctx.SetTotalCountHeader(count)
+ ctx.JSON(http.StatusOK, variables)
+}
+
+// GetVariable get an org-level variable
+func (Action) GetVariable(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/actions/variables/{variablename} organization getOrgVariable
+ // ---
+ // summary: Get an org-level variable
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: variablename
+ // in: path
+ // description: name of the variable
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ActionVariable"
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
+ OwnerID: ctx.Org.Organization.ID,
+ Name: ctx.Params("variablename"),
+ })
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.Error(http.StatusNotFound, "GetVariable", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ }
+ return
+ }
+
+ variable := &api.ActionVariable{
+ OwnerID: v.OwnerID,
+ RepoID: v.RepoID,
+ Name: v.Name,
+ Data: v.Data,
+ }
+
+ ctx.JSON(http.StatusOK, variable)
+}
+
+// DeleteVariable delete an org-level variable
+func (Action) DeleteVariable(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org}/actions/variables/{variablename} organization deleteOrgVariable
+ // ---
+ // summary: Delete an org-level variable
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: variablename
+ // in: path
+ // description: name of the variable
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ActionVariable"
+ // "201":
+ // description: response when deleting a variable
+ // "204":
+ // description: response when deleting a variable
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if err := actions_service.DeleteVariableByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("variablename")); err != nil {
+ if errors.Is(err, util.ErrInvalidArgument) {
+ ctx.Error(http.StatusBadRequest, "DeleteVariableByName", err)
+ } else if errors.Is(err, util.ErrNotExist) {
+ ctx.Error(http.StatusNotFound, "DeleteVariableByName", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "DeleteVariableByName", err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// CreateVariable create an org-level variable
+func (Action) CreateVariable(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/actions/variables/{variablename} organization createOrgVariable
+ // ---
+ // summary: Create an org-level variable
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: variablename
+ // in: path
+ // description: name of the variable
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/CreateVariableOption"
+ // responses:
+ // "201":
+ // description: response when creating an org-level variable
+ // "204":
+ // description: response when creating an org-level variable
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ opt := web.GetForm(ctx).(*api.CreateVariableOption)
+
+ ownerID := ctx.Org.Organization.ID
+ variableName := ctx.Params("variablename")
+
+ v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
+ OwnerID: ownerID,
+ Name: variableName,
+ })
+ if err != nil && !errors.Is(err, util.ErrNotExist) {
+ ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ return
+ }
+ if v != nil && v.ID > 0 {
+ ctx.Error(http.StatusConflict, "VariableNameAlreadyExists", util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
+ return
+ }
+
+ if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value); err != nil {
+ if errors.Is(err, util.ErrInvalidArgument) {
+ ctx.Error(http.StatusBadRequest, "CreateVariable", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "CreateVariable", err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// UpdateVariable update an org-level variable
+func (Action) UpdateVariable(ctx *context.APIContext) {
+ // swagger:operation PUT /orgs/{org}/actions/variables/{variablename} organization updateOrgVariable
+ // ---
+ // summary: Update an org-level variable
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: variablename
+ // in: path
+ // description: name of the variable
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/UpdateVariableOption"
+ // responses:
+ // "201":
+ // description: response when updating an org-level variable
+ // "204":
+ // description: response when updating an org-level variable
+ // "400":
+ // "$ref": "#/responses/error"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ opt := web.GetForm(ctx).(*api.UpdateVariableOption)
+
+ v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
+ OwnerID: ctx.Org.Organization.ID,
+ Name: ctx.Params("variablename"),
+ })
+ if err != nil {
+ if errors.Is(err, util.ErrNotExist) {
+ ctx.Error(http.StatusNotFound, "GetVariable", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetVariable", err)
+ }
+ return
+ }
+
+ if opt.Name == "" {
+ opt.Name = ctx.Params("variablename")
+ }
+ if _, err := actions_service.UpdateVariable(ctx, v.ID, opt.Name, opt.Value); err != nil {
+ if errors.Is(err, util.ErrInvalidArgument) {
+ ctx.Error(http.StatusBadRequest, "UpdateVariable", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "UpdateVariable", err)
+ }
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+var _ actions_service.API = new(Action)
+
+// Action implements actions_service.API
+type Action struct{}
+
+// NewAction creates a new Action service
+func NewAction() actions_service.API {
+ return Action{}
+}
diff --git a/routers/api/v1/org/avatar.go b/routers/api/v1/org/avatar.go
new file mode 100644
index 0000000..f11eb6c
--- /dev/null
+++ b/routers/api/v1/org/avatar.go
@@ -0,0 +1,80 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "encoding/base64"
+ "net/http"
+
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ user_service "code.gitea.io/gitea/services/user"
+)
+
+// UpdateAvatarupdates the Avatar of an Organisation
+func UpdateAvatar(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/avatar organization orgUpdateAvatar
+ // ---
+ // summary: Update Avatar
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/UpdateUserAvatarOption"
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ form := web.GetForm(ctx).(*api.UpdateUserAvatarOption)
+
+ content, err := base64.StdEncoding.DecodeString(form.Image)
+ if err != nil {
+ ctx.Error(http.StatusBadRequest, "DecodeImage", err)
+ return
+ }
+
+ err = user_service.UploadAvatar(ctx, ctx.Org.Organization.AsUser(), content)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// DeleteAvatar deletes the Avatar of an Organisation
+func DeleteAvatar(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org}/avatar organization orgDeleteAvatar
+ // ---
+ // summary: Delete Avatar
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ err := user_service.DeleteAvatar(ctx, ctx.Org.Organization.AsUser())
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go
new file mode 100644
index 0000000..c1dc051
--- /dev/null
+++ b/routers/api/v1/org/hook.go
@@ -0,0 +1,189 @@
+// Copyright 2016 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "net/http"
+
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
+ webhook_service "code.gitea.io/gitea/services/webhook"
+)
+
+// ListHooks list an organziation's webhooks
+func ListHooks(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/hooks organization orgListHooks
+ // ---
+ // summary: List an organization's webhooks
+ // 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/HookList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ utils.ListOwnerHooks(
+ ctx,
+ ctx.ContextUser,
+ )
+}
+
+// GetHook get an organization's hook by id
+func GetHook(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/hooks/{id} organization orgGetHook
+ // ---
+ // summary: Get a hook
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: id
+ // in: path
+ // description: id of the hook to get
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Hook"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ hook, err := utils.GetOwnerHook(ctx, ctx.ContextUser.ID, ctx.ParamsInt64("id"))
+ if err != nil {
+ return
+ }
+
+ apiHook, err := webhook_service.ToHook(ctx.ContextUser.HomeLink(), hook)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, apiHook)
+}
+
+// CreateHook create a hook for an organization
+func CreateHook(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/hooks organization orgCreateHook
+ // ---
+ // summary: Create a hook
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // required: true
+ // schema:
+ // "$ref": "#/definitions/CreateHookOption"
+ // responses:
+ // "201":
+ // "$ref": "#/responses/Hook"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ utils.AddOwnerHook(
+ ctx,
+ ctx.ContextUser,
+ web.GetForm(ctx).(*api.CreateHookOption),
+ )
+}
+
+// EditHook modify a hook of an organization
+func EditHook(ctx *context.APIContext) {
+ // swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook
+ // ---
+ // summary: Update a hook
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: id
+ // in: path
+ // description: id of the hook to update
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditHookOption"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Hook"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ utils.EditOwnerHook(
+ ctx,
+ ctx.ContextUser,
+ web.GetForm(ctx).(*api.EditHookOption),
+ ctx.ParamsInt64("id"),
+ )
+}
+
+// DeleteHook delete a hook of an organization
+func DeleteHook(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org}/hooks/{id} organization orgDeleteHook
+ // ---
+ // summary: Delete a hook
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: id
+ // in: path
+ // description: id of the hook to delete
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ utils.DeleteOwnerHook(
+ ctx,
+ ctx.ContextUser,
+ ctx.ParamsInt64("id"),
+ )
+}
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
new file mode 100644
index 0000000..b5ec54c
--- /dev/null
+++ b/routers/api/v1/org/label.go
@@ -0,0 +1,258 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+
+ issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/modules/label"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+)
+
+// ListLabels list all the labels of an organization
+func ListLabels(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/labels organization orgListLabels
+ // ---
+ // summary: List an organization's labels
+ // 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/LabelList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ labels, err := issues_model.GetLabelsByOrgID(ctx, ctx.Org.Organization.ID, ctx.FormString("sort"), utils.GetListOptions(ctx))
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetLabelsByOrgID", err)
+ return
+ }
+
+ count, err := issues_model.CountLabelsByOrgID(ctx, ctx.Org.Organization.ID)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.SetTotalCountHeader(count)
+ ctx.JSON(http.StatusOK, convert.ToLabelList(labels, nil, ctx.Org.Organization.AsUser()))
+}
+
+// CreateLabel create a label for a repository
+func CreateLabel(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/labels organization orgCreateLabel
+ // ---
+ // summary: Create a label for an organization
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/CreateLabelOption"
+ // responses:
+ // "201":
+ // "$ref": "#/responses/Label"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateLabelOption)
+ form.Color = strings.Trim(form.Color, " ")
+ color, err := label.NormalizeColor(form.Color)
+ if err != nil {
+ ctx.Error(http.StatusUnprocessableEntity, "Color", err)
+ return
+ }
+ form.Color = color
+
+ label := &issues_model.Label{
+ Name: form.Name,
+ Exclusive: form.Exclusive,
+ Color: form.Color,
+ OrgID: ctx.Org.Organization.ID,
+ Description: form.Description,
+ }
+ if err := issues_model.NewLabel(ctx, label); err != nil {
+ ctx.Error(http.StatusInternalServerError, "NewLabel", err)
+ return
+ }
+
+ ctx.JSON(http.StatusCreated, convert.ToLabel(label, nil, ctx.Org.Organization.AsUser()))
+}
+
+// GetLabel get label by organization and label id
+func GetLabel(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/labels/{id} organization orgGetLabel
+ // ---
+ // summary: Get a single label
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: id
+ // in: path
+ // description: id of the label to get
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Label"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ var (
+ label *issues_model.Label
+ err error
+ )
+ strID := ctx.Params(":id")
+ if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
+ label, err = issues_model.GetLabelInOrgByName(ctx, ctx.Org.Organization.ID, strID)
+ } else {
+ label, err = issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, intID)
+ }
+ if err != nil {
+ if issues_model.IsErrOrgLabelNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetLabelByOrgID", err)
+ }
+ return
+ }
+
+ ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization.AsUser()))
+}
+
+// EditLabel modify a label for an Organization
+func EditLabel(ctx *context.APIContext) {
+ // swagger:operation PATCH /orgs/{org}/labels/{id} organization orgEditLabel
+ // ---
+ // summary: Update a label
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: id
+ // in: path
+ // description: id of the label to edit
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditLabelOption"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Label"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.EditLabelOption)
+ l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
+ if err != nil {
+ if issues_model.IsErrOrgLabelNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetLabelByRepoID", err)
+ }
+ return
+ }
+
+ if form.Name != nil {
+ l.Name = *form.Name
+ }
+ if form.Exclusive != nil {
+ l.Exclusive = *form.Exclusive
+ }
+ if form.Color != nil {
+ color, err := label.NormalizeColor(*form.Color)
+ if err != nil {
+ ctx.Error(http.StatusUnprocessableEntity, "Color", err)
+ return
+ }
+ l.Color = color
+ }
+ if form.Description != nil {
+ l.Description = *form.Description
+ }
+ l.SetArchived(form.IsArchived != nil && *form.IsArchived)
+ if err := issues_model.UpdateLabel(ctx, l); err != nil {
+ ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, convert.ToLabel(l, nil, ctx.Org.Organization.AsUser()))
+}
+
+// DeleteLabel delete a label for an organization
+func DeleteLabel(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org}/labels/{id} organization orgDeleteLabel
+ // ---
+ // summary: Delete a label
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: id
+ // in: path
+ // description: id of the label to delete
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if err := issues_model.DeleteLabel(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id")); err != nil {
+ ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go
new file mode 100644
index 0000000..fb66d4c
--- /dev/null
+++ b/routers/api/v1/org/member.go
@@ -0,0 +1,325 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "net/http"
+ "net/url"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/organization"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/routers/api/v1/user"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+)
+
+// listMembers list an organization's members
+func listMembers(ctx *context.APIContext, publicOnly bool) {
+ opts := &organization.FindOrgMembersOpts{
+ OrgID: ctx.Org.Organization.ID,
+ PublicOnly: publicOnly,
+ ListOptions: utils.GetListOptions(ctx),
+ }
+
+ count, err := organization.CountOrgMembers(ctx, opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ members, _, err := organization.FindOrgMembers(ctx, opts)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ apiMembers := make([]*api.User, len(members))
+ for i, member := range members {
+ apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer)
+ }
+
+ 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"
+
+ publicOnly := true
+ if ctx.Doer != nil {
+ isMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ return
+ }
+ publicOnly = !isMember && !ctx.Doer.IsAdmin
+ }
+ listMembers(ctx, publicOnly)
+}
+
+// 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, true)
+}
+
+// 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
+ // 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.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if ctx.Doer != nil {
+ userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ return
+ } else if userIsMember || ctx.Doer.IsAdmin {
+ userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, userToCheck.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ } else if userToCheckIsMember {
+ ctx.Status(http.StatusNoContent)
+ } else {
+ ctx.NotFound()
+ }
+ return
+ } else if ctx.Doer.ID == userToCheck.ID {
+ ctx.NotFound()
+ return
+ }
+ }
+
+ redirectURL := setting.AppSubURL + "/api/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
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: user is a public member
+ // "404":
+ // description: user is not a public member
+
+ userToCheck := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ is, err := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, userToCheck.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsPublicMembership", err)
+ return
+ }
+ if is {
+ ctx.Status(http.StatusNoContent)
+ } else {
+ ctx.NotFound()
+ }
+}
+
+// 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
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: membership publicized
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ userToPublicize := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if userToPublicize.ID != ctx.Doer.ID {
+ ctx.Error(http.StatusForbidden, "", "Cannot publicize another member")
+ return
+ }
+ err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToPublicize.ID, true)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", 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
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ userToConceal := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if userToConceal.ID != ctx.Doer.ID {
+ ctx.Error(http.StatusForbidden, "", "Cannot conceal another member")
+ return
+ }
+ err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToConceal.ID, false)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "ChangeOrgUserStatus", 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
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // description: member removed
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ member := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if err := models.RemoveOrgUser(ctx, ctx.Org.Organization.ID, member.ID); err != nil {
+ ctx.Error(http.StatusInternalServerError, "RemoveOrgUser", err)
+ }
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
new file mode 100644
index 0000000..3623b85
--- /dev/null
+++ b/routers/api/v1/org/org.go
@@ -0,0 +1,559 @@
+// Copyright 2015 The Gogs Authors. All rights reserved.
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "fmt"
+ "net/http"
+
+ activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/organization"
+ "code.gitea.io/gitea/models/perm"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/optional"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/v1/user"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+ "code.gitea.io/gitea/services/org"
+ user_service "code.gitea.io/gitea/services/user"
+)
+
+func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
+ listOptions := utils.GetListOptions(ctx)
+ showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID)
+
+ opts := organization.FindOrgOptions{
+ ListOptions: listOptions,
+ UserID: u.ID,
+ IncludePrivate: showPrivate,
+ }
+ orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "db.FindAndCount[organization.Organization]", err)
+ return
+ }
+
+ apiOrgs := make([]*api.Organization, len(orgs))
+ for i := range orgs {
+ apiOrgs[i] = convert.ToOrganization(ctx, orgs[i])
+ }
+
+ ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
+ ctx.SetTotalCountHeader(maxResults)
+ ctx.JSON(http.StatusOK, &apiOrgs)
+}
+
+// ListMyOrgs list all my orgs
+func ListMyOrgs(ctx *context.APIContext) {
+ // swagger:operation GET /user/orgs organization orgListCurrentUserOrgs
+ // ---
+ // summary: List the current user's organizations
+ // produces:
+ // - application/json
+ // parameters:
+ // - 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/OrganizationList"
+ // "401":
+ // "$ref": "#/responses/unauthorized"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ listUserOrgs(ctx, ctx.Doer)
+}
+
+// ListUserOrgs list user's orgs
+func ListUserOrgs(ctx *context.APIContext) {
+ // swagger:operation GET /users/{username}/orgs organization orgListUserOrgs
+ // ---
+ // summary: List a user's organizations
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: username
+ // in: path
+ // description: username of user
+ // 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/OrganizationList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ listUserOrgs(ctx, ctx.ContextUser)
+}
+
+// GetUserOrgsPermissions get user permissions in organization
+func GetUserOrgsPermissions(ctx *context.APIContext) {
+ // swagger:operation GET /users/{username}/orgs/{org}/permissions organization orgGetUserPermissions
+ // ---
+ // summary: Get user permissions in organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: username
+ // in: path
+ // description: username of user
+ // type: string
+ // required: true
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/OrganizationPermissions"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ var o *user_model.User
+ if o = user.GetUserByParamsName(ctx, ":org"); o == nil {
+ return
+ }
+
+ op := api.OrganizationPermissions{}
+
+ if !organization.HasOrgOrUserVisible(ctx, o, ctx.ContextUser) {
+ ctx.NotFound("HasOrgOrUserVisible", nil)
+ return
+ }
+
+ org := organization.OrgFromUser(o)
+ authorizeLevel, err := org.GetOrgUserMaxAuthorizeLevel(ctx, ctx.ContextUser.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetOrgUserAuthorizeLevel", err)
+ return
+ }
+
+ if authorizeLevel > perm.AccessModeNone {
+ op.CanRead = true
+ }
+ if authorizeLevel > perm.AccessModeRead {
+ op.CanWrite = true
+ }
+ if authorizeLevel > perm.AccessModeWrite {
+ op.IsAdmin = true
+ }
+ if authorizeLevel > perm.AccessModeAdmin {
+ op.IsOwner = true
+ }
+
+ op.CanCreateRepository, err = org.CanCreateOrgRepo(ctx, ctx.ContextUser.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, op)
+}
+
+// GetAll return list of all public organizations
+func GetAll(ctx *context.APIContext) {
+ // swagger:operation Get /orgs organization orgGetAll
+ // ---
+ // summary: Get list of organizations
+ // produces:
+ // - application/json
+ // parameters:
+ // - 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/OrganizationList"
+
+ vMode := []api.VisibleType{api.VisibleTypePublic}
+ if ctx.IsSigned && !ctx.PublicOnly {
+ vMode = append(vMode, api.VisibleTypeLimited)
+ if ctx.Doer.IsAdmin {
+ vMode = append(vMode, api.VisibleTypePrivate)
+ }
+ }
+
+ listOptions := utils.GetListOptions(ctx)
+
+ publicOrgs, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
+ Actor: ctx.Doer,
+ ListOptions: listOptions,
+ Type: user_model.UserTypeOrganization,
+ OrderBy: db.SearchOrderByAlphabetically,
+ Visible: vMode,
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "SearchOrganizations", err)
+ return
+ }
+ orgs := make([]*api.Organization, len(publicOrgs))
+ for i := range publicOrgs {
+ orgs[i] = convert.ToOrganization(ctx, organization.OrgFromUser(publicOrgs[i]))
+ }
+
+ ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
+ ctx.SetTotalCountHeader(maxResults)
+ ctx.JSON(http.StatusOK, &orgs)
+}
+
+// Create api for create organization
+func Create(ctx *context.APIContext) {
+ // swagger:operation POST /orgs organization orgCreate
+ // ---
+ // summary: Create an organization
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: organization
+ // in: body
+ // required: true
+ // schema: { "$ref": "#/definitions/CreateOrgOption" }
+ // responses:
+ // "201":
+ // "$ref": "#/responses/Organization"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateOrgOption)
+ if !ctx.Doer.CanCreateOrganization() {
+ ctx.Error(http.StatusForbidden, "Create organization not allowed", nil)
+ return
+ }
+
+ visibility := api.VisibleTypePublic
+ if form.Visibility != "" {
+ visibility = api.VisibilityModes[form.Visibility]
+ }
+
+ org := &organization.Organization{
+ Name: form.UserName,
+ FullName: form.FullName,
+ Email: form.Email,
+ Description: form.Description,
+ Website: form.Website,
+ Location: form.Location,
+ IsActive: true,
+ Type: user_model.UserTypeOrganization,
+ Visibility: visibility,
+ RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess,
+ }
+ if err := organization.CreateOrganization(ctx, org, ctx.Doer); err != nil {
+ if user_model.IsErrUserAlreadyExist(err) ||
+ db.IsErrNameReserved(err) ||
+ db.IsErrNameCharsNotAllowed(err) ||
+ db.IsErrNamePatternNotAllowed(err) {
+ ctx.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "CreateOrganization", err)
+ }
+ return
+ }
+
+ ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org))
+}
+
+// Get get an organization
+func Get(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org} organization orgGet
+ // ---
+ // summary: Get an organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization to get
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Organization"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
+ ctx.NotFound("HasOrgOrUserVisible", nil)
+ return
+ }
+
+ org := convert.ToOrganization(ctx, ctx.Org.Organization)
+
+ // Don't show Mail, when User is not logged in
+ if ctx.Doer == nil {
+ org.Email = ""
+ }
+
+ ctx.JSON(http.StatusOK, org)
+}
+
+// Edit change an organization's information
+func Edit(ctx *context.APIContext) {
+ // swagger:operation PATCH /orgs/{org} organization orgEdit
+ // ---
+ // summary: Edit an organization
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization to edit
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // required: true
+ // schema:
+ // "$ref": "#/definitions/EditOrgOption"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Organization"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ form := web.GetForm(ctx).(*api.EditOrgOption)
+
+ if form.Email != "" {
+ if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil {
+ ctx.Error(http.StatusInternalServerError, "ReplacePrimaryEmailAddress", err)
+ return
+ }
+ }
+
+ opts := &user_service.UpdateOptions{
+ FullName: optional.Some(form.FullName),
+ Description: optional.Some(form.Description),
+ Website: optional.Some(form.Website),
+ Location: optional.Some(form.Location),
+ Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
+ RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
+ }
+ if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
+ ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, ctx.Org.Organization))
+}
+
+// Delete an organization
+func Delete(ctx *context.APIContext) {
+ // swagger:operation DELETE /orgs/{org} organization orgDelete
+ // ---
+ // summary: Delete an organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: organization that is to be deleted
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if err := org.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
+ ctx.Error(http.StatusInternalServerError, "DeleteOrganization", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+func ListOrgActivityFeeds(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/activities/feeds organization orgListActivityFeeds
+ // ---
+ // summary: List an organization's activity feeds
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the org
+ // type: string
+ // required: true
+ // - name: date
+ // in: query
+ // description: the date of the activities to be found
+ // type: string
+ // format: date
+ // - 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/ActivityFeedsList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ includePrivate := false
+ if ctx.IsSigned {
+ if ctx.Doer.IsAdmin {
+ includePrivate = true
+ } else {
+ org := organization.OrgFromUser(ctx.ContextUser)
+ isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
+ return
+ }
+ includePrivate = isMember
+ }
+ }
+
+ listOptions := utils.GetListOptions(ctx)
+
+ opts := activities_model.GetFeedsOptions{
+ RequestedUser: ctx.ContextUser,
+ Actor: ctx.Doer,
+ IncludePrivate: includePrivate,
+ Date: ctx.FormString("date"),
+ ListOptions: listOptions,
+ }
+
+ feeds, count, err := activities_model.GetFeeds(ctx, opts)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
+ return
+ }
+ ctx.SetTotalCountHeader(count)
+
+ ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
+}
+
+// ListBlockedUsers list the organization's blocked users.
+func ListBlockedUsers(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/list_blocked organization orgListBlockedUsers
+ // ---
+ // summary: List the organization's blocked users
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the org
+ // 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/BlockedUserList"
+
+ utils.ListUserBlockedUsers(ctx, ctx.ContextUser)
+}
+
+// BlockUser blocks a user from the organization.
+func BlockUser(ctx *context.APIContext) {
+ // swagger:operation PUT /orgs/{org}/block/{username} organization orgBlockUser
+ // ---
+ // summary: Blocks a user from the organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the org
+ // type: string
+ // required: true
+ // - name: username
+ // in: path
+ // description: username of the user
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ if ctx.ContextUser.IsOrganization() {
+ ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
+ return
+ }
+
+ utils.BlockUser(ctx, ctx.Org.Organization.AsUser(), ctx.ContextUser)
+}
+
+// UnblockUser unblocks a user from the organization.
+func UnblockUser(ctx *context.APIContext) {
+ // swagger:operation PUT /orgs/{org}/unblock/{username} organization orgUnblockUser
+ // ---
+ // summary: Unblock a user from the organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the org
+ // type: string
+ // required: true
+ // - name: username
+ // in: path
+ // description: username of the user
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ if ctx.ContextUser.IsOrganization() {
+ ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", ctx.ContextUser.Name))
+ return
+ }
+
+ utils.UnblockUser(ctx, ctx.Org.Organization.AsUser(), ctx.ContextUser)
+}
diff --git a/routers/api/v1/org/quota.go b/routers/api/v1/org/quota.go
new file mode 100644
index 0000000..57c41f5
--- /dev/null
+++ b/routers/api/v1/org/quota.go
@@ -0,0 +1,155 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "code.gitea.io/gitea/routers/api/v1/shared"
+ "code.gitea.io/gitea/services/context"
+)
+
+// GetQuota returns the quota information for a given organization
+func GetQuota(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/quota organization orgGetQuota
+ // ---
+ // summary: Get quota information for an organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/QuotaInfo"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.GetQuota(ctx, ctx.Org.Organization.ID)
+}
+
+// CheckQuota returns whether the organization in context is over the subject quota
+func CheckQuota(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/quota/check organization orgCheckQuota
+ // ---
+ // summary: Check if the organization is over quota for a given subject
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/boolean"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ shared.CheckQuota(ctx, ctx.Org.Organization.ID)
+}
+
+// ListQuotaAttachments lists attachments affecting the organization's quota
+func ListQuotaAttachments(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/quota/attachments organization orgListQuotaAttachments
+ // ---
+ // summary: List the attachments affecting the organization's quota
+ // 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/QuotaUsedAttachmentList"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListQuotaAttachments(ctx, ctx.Org.Organization.ID)
+}
+
+// ListQuotaPackages lists packages affecting the organization's quota
+func ListQuotaPackages(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/quota/packages organization orgListQuotaPackages
+ // ---
+ // summary: List the packages affecting the organization's quota
+ // 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/QuotaUsedPackageList"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListQuotaPackages(ctx, ctx.Org.Organization.ID)
+}
+
+// ListQuotaArtifacts lists artifacts affecting the organization's quota
+func ListQuotaArtifacts(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/quota/artifacts organization orgListQuotaArtifacts
+ // ---
+ // summary: List the artifacts affecting the organization's quota
+ // 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/QuotaUsedArtifactList"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ shared.ListQuotaArtifacts(ctx, ctx.Org.Organization.ID)
+}
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
new file mode 100644
index 0000000..bf28d54
--- /dev/null
+++ b/routers/api/v1/org/team.go
@@ -0,0 +1,891 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package org
+
+import (
+ "errors"
+ "net/http"
+
+ "code.gitea.io/gitea/models"
+ activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/models/organization"
+ "code.gitea.io/gitea/models/perm"
+ access_model "code.gitea.io/gitea/models/perm/access"
+ repo_model "code.gitea.io/gitea/models/repo"
+ unit_model "code.gitea.io/gitea/models/unit"
+ "code.gitea.io/gitea/modules/log"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/api/v1/user"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+ org_service "code.gitea.io/gitea/services/org"
+ repo_service "code.gitea.io/gitea/services/repository"
+)
+
+// ListTeams list all the teams of an organization
+func ListTeams(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/teams organization orgListTeams
+ // ---
+ // summary: List an organization's teams
+ // 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/TeamList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
+ ListOptions: utils.GetListOptions(ctx),
+ OrgID: ctx.Org.Organization.ID,
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadTeams", err)
+ return
+ }
+
+ apiTeams, err := convert.ToTeams(ctx, teams, false)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
+ return
+ }
+
+ ctx.SetTotalCountHeader(count)
+ ctx.JSON(http.StatusOK, apiTeams)
+}
+
+// ListUserTeams list all the teams a user belongs to
+func ListUserTeams(ctx *context.APIContext) {
+ // swagger:operation GET /user/teams user userListTeams
+ // ---
+ // summary: List all the teams a user belongs to
+ // produces:
+ // - application/json
+ // parameters:
+ // - 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/TeamList"
+ // "401":
+ // "$ref": "#/responses/unauthorized"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+
+ teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
+ ListOptions: utils.GetListOptions(ctx),
+ UserID: ctx.Doer.ID,
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetUserTeams", err)
+ return
+ }
+
+ apiTeams, err := convert.ToTeams(ctx, teams, true)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
+ return
+ }
+
+ ctx.SetTotalCountHeader(count)
+ ctx.JSON(http.StatusOK, apiTeams)
+}
+
+// GetTeam api for get a team
+func GetTeam(ctx *context.APIContext) {
+ // swagger:operation GET /teams/{id} organization orgGetTeam
+ // ---
+ // summary: Get a team
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team to get
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Team"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ apiTeam, err := convert.ToTeam(ctx, ctx.Org.Team, true)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, apiTeam)
+}
+
+func attachTeamUnits(team *organization.Team, units []string) {
+ unitTypes, _ := unit_model.FindUnitTypes(units...)
+ team.Units = make([]*organization.TeamUnit, 0, len(units))
+ for _, tp := range unitTypes {
+ team.Units = append(team.Units, &organization.TeamUnit{
+ OrgID: team.OrgID,
+ Type: tp,
+ AccessMode: team.AccessMode,
+ })
+ }
+}
+
+func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode {
+ res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap))
+ for unitKey, p := range unitsMap {
+ res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p)
+ }
+ return res
+}
+
+func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) {
+ team.Units = make([]*organization.TeamUnit, 0, len(unitsMap))
+ for unitKey, p := range unitsMap {
+ team.Units = append(team.Units, &organization.TeamUnit{
+ OrgID: team.OrgID,
+ Type: unit_model.TypeFromKey(unitKey),
+ AccessMode: perm.ParseAccessMode(p),
+ })
+ }
+}
+
+func attachAdminTeamUnits(team *organization.Team) {
+ team.Units = make([]*organization.TeamUnit, 0, len(unit_model.AllRepoUnitTypes))
+ for _, ut := range unit_model.AllRepoUnitTypes {
+ up := perm.AccessModeAdmin
+ if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
+ up = perm.AccessModeRead
+ }
+ team.Units = append(team.Units, &organization.TeamUnit{
+ OrgID: team.OrgID,
+ Type: ut,
+ AccessMode: up,
+ })
+ }
+}
+
+// CreateTeam api for create a team
+func CreateTeam(ctx *context.APIContext) {
+ // swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
+ // ---
+ // summary: Create a team
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/CreateTeamOption"
+ // responses:
+ // "201":
+ // "$ref": "#/responses/Team"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateTeamOption)
+ p := perm.ParseAccessMode(form.Permission)
+ if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
+ p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
+ }
+ team := &organization.Team{
+ OrgID: ctx.Org.Organization.ID,
+ Name: form.Name,
+ Description: form.Description,
+ IncludesAllRepositories: form.IncludesAllRepositories,
+ CanCreateOrgRepo: form.CanCreateOrgRepo,
+ AccessMode: p,
+ }
+
+ if team.AccessMode < perm.AccessModeAdmin {
+ if len(form.UnitsMap) > 0 {
+ attachTeamUnitsMap(team, form.UnitsMap)
+ } else if len(form.Units) > 0 {
+ attachTeamUnits(team, form.Units)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
+ return
+ }
+ } else {
+ attachAdminTeamUnits(team)
+ }
+
+ if err := models.NewTeam(ctx, team); err != nil {
+ if organization.IsErrTeamAlreadyExist(err) {
+ ctx.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "NewTeam", err)
+ }
+ return
+ }
+
+ apiTeam, err := convert.ToTeam(ctx, team, true)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ ctx.JSON(http.StatusCreated, apiTeam)
+}
+
+// EditTeam api for edit a team
+func EditTeam(ctx *context.APIContext) {
+ // swagger:operation PATCH /teams/{id} organization orgEditTeam
+ // ---
+ // summary: Edit a team
+ // consumes:
+ // - application/json
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team to edit
+ // type: integer
+ // required: true
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/EditTeamOption"
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Team"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ form := web.GetForm(ctx).(*api.EditTeamOption)
+ team := ctx.Org.Team
+ if err := team.LoadUnits(ctx); err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ if form.CanCreateOrgRepo != nil {
+ team.CanCreateOrgRepo = team.IsOwnerTeam() || *form.CanCreateOrgRepo
+ }
+
+ if len(form.Name) > 0 {
+ team.Name = form.Name
+ }
+
+ if form.Description != nil {
+ team.Description = *form.Description
+ }
+
+ isAuthChanged := false
+ isIncludeAllChanged := false
+ if !team.IsOwnerTeam() && len(form.Permission) != 0 {
+ // Validate permission level.
+ p := perm.ParseAccessMode(form.Permission)
+ if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
+ p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
+ }
+
+ if team.AccessMode != p {
+ isAuthChanged = true
+ team.AccessMode = p
+ }
+
+ if form.IncludesAllRepositories != nil {
+ isIncludeAllChanged = true
+ team.IncludesAllRepositories = *form.IncludesAllRepositories
+ }
+ }
+
+ if team.AccessMode < perm.AccessModeAdmin {
+ if len(form.UnitsMap) > 0 {
+ attachTeamUnitsMap(team, form.UnitsMap)
+ } else if len(form.Units) > 0 {
+ attachTeamUnits(team, form.Units)
+ }
+ } else {
+ attachAdminTeamUnits(team)
+ }
+
+ if err := models.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
+ ctx.Error(http.StatusInternalServerError, "EditTeam", err)
+ return
+ }
+
+ apiTeam, err := convert.ToTeam(ctx, team)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ ctx.JSON(http.StatusOK, apiTeam)
+}
+
+// DeleteTeam api for delete a team
+func DeleteTeam(ctx *context.APIContext) {
+ // swagger:operation DELETE /teams/{id} organization orgDeleteTeam
+ // ---
+ // summary: Delete a team
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team to delete
+ // type: integer
+ // format: int64
+ // required: true
+ // responses:
+ // "204":
+ // description: team deleted
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
+ ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+// GetTeamMembers api for get a team's members
+func GetTeamMembers(ctx *context.APIContext) {
+ // swagger:operation GET /teams/{id}/members organization orgListTeamMembers
+ // ---
+ // summary: List a team's members
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // 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"
+
+ isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
+ return
+ } else if !isMember && !ctx.Doer.IsAdmin {
+ ctx.NotFound()
+ return
+ }
+
+ teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
+ ListOptions: utils.GetListOptions(ctx),
+ TeamID: ctx.Org.Team.ID,
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err)
+ return
+ }
+
+ members := make([]*api.User, len(teamMembers))
+ for i, member := range teamMembers {
+ members[i] = convert.ToUser(ctx, member, ctx.Doer)
+ }
+
+ ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers))
+ ctx.JSON(http.StatusOK, members)
+}
+
+// GetTeamMember api for get a particular member of team
+func GetTeamMember(ctx *context.APIContext) {
+ // swagger:operation GET /teams/{id}/members/{username} organization orgListTeamMember
+ // ---
+ // summary: List a particular member of team
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: username
+ // in: path
+ // description: username of the member to list
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/User"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ u := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ teamID := ctx.ParamsInt64("teamid")
+ isTeamMember, err := organization.IsUserInTeams(ctx, u.ID, []int64{teamID})
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "IsUserInTeams", err)
+ return
+ } else if !isTeamMember {
+ ctx.NotFound()
+ return
+ }
+ ctx.JSON(http.StatusOK, convert.ToUser(ctx, u, ctx.Doer))
+}
+
+// AddTeamMember api for add a member to a team
+func AddTeamMember(ctx *context.APIContext) {
+ // swagger:operation PUT /teams/{id}/members/{username} organization orgAddTeamMember
+ // ---
+ // summary: Add a team member
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: username
+ // in: path
+ // description: username of the user to add
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ u := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if err := models.AddTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
+ ctx.Error(http.StatusInternalServerError, "AddMember", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+// RemoveTeamMember api for remove one member from a team
+func RemoveTeamMember(ctx *context.APIContext) {
+ // swagger:operation DELETE /teams/{id}/members/{username} organization orgRemoveTeamMember
+ // ---
+ // summary: Remove a team member
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: username
+ // in: path
+ // description: username of the user to remove
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ u := user.GetUserByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ if err := models.RemoveTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
+ ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+// GetTeamRepos api for get a team's repos
+func GetTeamRepos(ctx *context.APIContext) {
+ // swagger:operation GET /teams/{id}/repos organization orgListTeamRepos
+ // ---
+ // summary: List a team's repos
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // 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/RepositoryList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ team := ctx.Org.Team
+ teamRepos, err := organization.GetTeamRepositories(ctx, &organization.SearchTeamRepoOptions{
+ ListOptions: utils.GetListOptions(ctx),
+ TeamID: team.ID,
+ })
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
+ return
+ }
+ repos := make([]*api.Repository, len(teamRepos))
+ for i, repo := range teamRepos {
+ permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
+ return
+ }
+ repos[i] = convert.ToRepo(ctx, repo, permission)
+ }
+ ctx.SetTotalCountHeader(int64(team.NumRepos))
+ ctx.JSON(http.StatusOK, repos)
+}
+
+// GetTeamRepo api for get a particular repo of team
+func GetTeamRepo(ctx *context.APIContext) {
+ // swagger:operation GET /teams/{id}/repos/{org}/{repo} organization orgListTeamRepo
+ // ---
+ // summary: List a particular repo of team
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: org
+ // in: path
+ // description: organization that owns the repo to list
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo to list
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Repository"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repo := getRepositoryByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ if !organization.HasTeamRepo(ctx, ctx.Org.Team.OrgID, ctx.Org.Team.ID, repo.ID) {
+ ctx.NotFound()
+ return
+ }
+
+ permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
+}
+
+// getRepositoryByParams get repository by a team's organization ID and repo name
+func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
+ repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.Params(":reponame"))
+ if err != nil {
+ if repo_model.IsErrRepoNotExist(err) {
+ ctx.NotFound()
+ } else {
+ ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
+ }
+ return nil
+ }
+ return repo
+}
+
+// AddTeamRepository api for adding a repository to a team
+func AddTeamRepository(ctx *context.APIContext) {
+ // swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
+ // ---
+ // summary: Add a repository to a team
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: org
+ // in: path
+ // description: organization that owns the repo to add
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo to add
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repo := getRepositoryByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
+ ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
+ return
+ } else if access < perm.AccessModeAdmin {
+ ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
+ return
+ }
+ if err := org_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
+ ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+// RemoveTeamRepository api for removing a repository from a team
+func RemoveTeamRepository(ctx *context.APIContext) {
+ // swagger:operation DELETE /teams/{id}/repos/{org}/{repo} organization orgRemoveTeamRepository
+ // ---
+ // summary: Remove a repository from a team
+ // description: This does not delete the repository, it only removes the
+ // repository from the team.
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: org
+ // in: path
+ // description: organization that owns the repo to remove
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo to remove
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ repo := getRepositoryByParams(ctx)
+ if ctx.Written() {
+ return
+ }
+ if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
+ ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
+ return
+ } else if access < perm.AccessModeAdmin {
+ ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
+ return
+ }
+ if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, repo.ID); err != nil {
+ ctx.Error(http.StatusInternalServerError, "RemoveRepository", err)
+ return
+ }
+ ctx.Status(http.StatusNoContent)
+}
+
+// SearchTeam api for searching teams
+func SearchTeam(ctx *context.APIContext) {
+ // swagger:operation GET /orgs/{org}/teams/search organization teamSearch
+ // ---
+ // summary: Search for teams within an organization
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: org
+ // in: path
+ // description: name of the organization
+ // type: string
+ // required: true
+ // - name: q
+ // in: query
+ // description: keywords to search
+ // type: string
+ // - name: include_desc
+ // in: query
+ // description: include search within team description (defaults to true)
+ // type: boolean
+ // - 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":
+ // description: "SearchResults of a successful search"
+ // schema:
+ // type: object
+ // properties:
+ // ok:
+ // type: boolean
+ // data:
+ // type: array
+ // items:
+ // "$ref": "#/definitions/Team"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ listOptions := utils.GetListOptions(ctx)
+
+ opts := &organization.SearchTeamOptions{
+ Keyword: ctx.FormTrim("q"),
+ OrgID: ctx.Org.Organization.ID,
+ IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
+ ListOptions: listOptions,
+ }
+
+ // Only admin is allowed to search for all teams
+ if !ctx.Doer.IsAdmin {
+ opts.UserID = ctx.Doer.ID
+ }
+
+ teams, maxResults, err := organization.SearchTeam(ctx, opts)
+ if err != nil {
+ log.Error("SearchTeam failed: %v", err)
+ ctx.JSON(http.StatusInternalServerError, map[string]any{
+ "ok": false,
+ "error": "SearchTeam internal failure",
+ })
+ return
+ }
+
+ apiTeams, err := convert.ToTeams(ctx, teams, false)
+ if err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
+ ctx.SetTotalCountHeader(maxResults)
+ ctx.JSON(http.StatusOK, map[string]any{
+ "ok": true,
+ "data": apiTeams,
+ })
+}
+
+func ListTeamActivityFeeds(ctx *context.APIContext) {
+ // swagger:operation GET /teams/{id}/activities/feeds organization orgListTeamActivityFeeds
+ // ---
+ // summary: List a team's activity feeds
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: id
+ // in: path
+ // description: id of the team
+ // type: integer
+ // format: int64
+ // required: true
+ // - name: date
+ // in: query
+ // description: the date of the activities to be found
+ // type: string
+ // format: date
+ // - 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/ActivityFeedsList"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ listOptions := utils.GetListOptions(ctx)
+
+ opts := activities_model.GetFeedsOptions{
+ RequestedTeam: ctx.Org.Team,
+ Actor: ctx.Doer,
+ IncludePrivate: true,
+ Date: ctx.FormString("date"),
+ ListOptions: listOptions,
+ }
+
+ feeds, count, err := activities_model.GetFeeds(ctx, opts)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
+ return
+ }
+ ctx.SetTotalCountHeader(count)
+
+ ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
+}