From e68b9d00a6e05b3a941f63ffb696f91e554ac5ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.3. Signed-off-by: Daniel Baumann --- routers/api/v1/org/action.go | 473 +++++++++++++++++++++++ routers/api/v1/org/avatar.go | 80 ++++ routers/api/v1/org/hook.go | 189 +++++++++ routers/api/v1/org/label.go | 258 +++++++++++++ routers/api/v1/org/member.go | 325 ++++++++++++++++ routers/api/v1/org/org.go | 559 +++++++++++++++++++++++++++ routers/api/v1/org/quota.go | 155 ++++++++ routers/api/v1/org/team.go | 891 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 2930 insertions(+) create mode 100644 routers/api/v1/org/action.go create mode 100644 routers/api/v1/org/avatar.go create mode 100644 routers/api/v1/org/hook.go create mode 100644 routers/api/v1/org/label.go create mode 100644 routers/api/v1/org/member.go create mode 100644 routers/api/v1/org/org.go create mode 100644 routers/api/v1/org/quota.go create mode 100644 routers/api/v1/org/team.go (limited to 'routers/api/v1/org') 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)) +} -- cgit v1.2.3