初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,693 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
secret_model "gitea.dev/models/secret"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/context"
|
||||
secret_service "gitea.dev/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.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiSecrets := make([]*api.Secret, len(secrets))
|
||||
for k, v := range secrets {
|
||||
apiSecrets[k] = &api.Secret{
|
||||
Name: v.Name,
|
||||
Description: v.Description,
|
||||
Created: v.CreatedUnix.AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, opts.PageSize)
|
||||
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.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(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.PathParam("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(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
|
||||
// CreateRegistrationToken returns the token to register org runners
|
||||
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/actions/runners/registration-token organization orgCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// 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"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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,
|
||||
Description: v.Description,
|
||||
}
|
||||
}
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
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.PathParam("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
variable := &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
Description: v.Description,
|
||||
}
|
||||
|
||||
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.PathParam("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(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: successfully created the org-level variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "409":
|
||||
// description: variable name already exists.
|
||||
// "500":
|
||||
// "$ref": "#/responses/error"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||
|
||||
ownerID := ctx.Org.Organization.ID
|
||||
variableName := ctx.PathParam("variablename")
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ownerID,
|
||||
Name: variableName,
|
||||
})
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// 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.PathParam("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if opt.Name == "" {
|
||||
opt.Name = ctx.PathParam("variablename")
|
||||
}
|
||||
|
||||
v.Name = opt.Name
|
||||
v.Data = opt.Value
|
||||
v.Description = opt.Description
|
||||
|
||||
if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ListRunners get org-level runners
|
||||
func (Action) ListRunners(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/runners organization getOrgRunners
|
||||
// ---
|
||||
// summary: Get org-level runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: disabled
|
||||
// in: query
|
||||
// description: filter by disabled status (true or false)
|
||||
// type: boolean
|
||||
// required: false
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RunnerList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRunners(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
// GetRunner get an org-level runner
|
||||
func (Action) GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/runners/{runner_id} organization getOrgRunner
|
||||
// ---
|
||||
// summary: Get an org-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.GetRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an org-level runner
|
||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/actions/runners/{runner_id} organization deleteOrgRunner
|
||||
// ---
|
||||
// summary: Delete an org-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: runner has been deleted
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// UpdateRunner update an org-level runner
|
||||
func (Action) UpdateRunner(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /orgs/{org}/actions/runners/{runner_id} organization updateOrgRunner
|
||||
// ---
|
||||
// summary: Update an org-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
shared.UpdateRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
|
||||
// ---
|
||||
// summary: Get org-level workflow jobs
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - 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/WorkflowJobsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListJobs(ctx, ctx.Org.Organization.ID, 0, 0, nil)
|
||||
}
|
||||
|
||||
func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/runs organization getOrgWorkflowRuns
|
||||
// ---
|
||||
// summary: Get org-level workflow runs
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: event
|
||||
// in: query
|
||||
// description: workflow event name
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: branch
|
||||
// in: query
|
||||
// description: workflow branch
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: actor
|
||||
// in: query
|
||||
// description: triggered by user
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: head_sha
|
||||
// in: query
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - 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/WorkflowRunsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
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{}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
user_service "gitea.dev/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.APIError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = user_service.UploadAvatar(ctx, ctx.Org.Organization.AsUser(), content)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2024 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
func ListBlocks(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/blocks organization organizationListBlocks
|
||||
// ---
|
||||
// summary: List users blocked by the organization
|
||||
// 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"
|
||||
|
||||
shared.ListBlocks(ctx, ctx.Org.Organization.AsUser())
|
||||
}
|
||||
|
||||
func CheckUserBlock(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/blocks/{username} organization organizationCheckUserBlock
|
||||
// ---
|
||||
// summary: Check if a user is blocked by the organization
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to check
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.CheckUserBlock(ctx, ctx.Org.Organization.AsUser())
|
||||
}
|
||||
|
||||
func BlockUser(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /orgs/{org}/blocks/{username} organization organizationBlockUser
|
||||
// ---
|
||||
// summary: Block a user
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to block
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: note
|
||||
// in: query
|
||||
// description: optional note for the block
|
||||
// type: string
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
shared.BlockUser(ctx, ctx.Org.Organization.AsUser())
|
||||
}
|
||||
|
||||
func UnblockUser(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/blocks/{username} organization organizationUnblockUser
|
||||
// ---
|
||||
// summary: Unblock a user
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to unblock
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
shared.UnblockUser(ctx, ctx.Doer, ctx.Org.Organization.AsUser())
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
webhook_service "gitea.dev/services/webhook"
|
||||
)
|
||||
|
||||
// ListHooks list an organization'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.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
apiHook, err := webhook_service.ToHook(ctx.ContextUser.HomeLink(), hook)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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.PathParamInt64("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.PathParamInt64("id"),
|
||||
)
|
||||
}
|
||||
@@ -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 "gitea.dev/models/issues"
|
||||
"gitea.dev/modules/label"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/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.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
count, err := issues_model.CountLabelsByOrgID(ctx, ctx.Org.Organization.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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.APIError(http.StatusUnprocessableEntity, 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.APIErrorInternal(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.PathParam("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.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(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.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrOrgLabelNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(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.APIError(http.StatusUnprocessableEntity, 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.APIErrorInternal(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.PathParamInt64("id")); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"gitea.dev/models/organization"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
org_service "gitea.dev/services/org"
|
||||
)
|
||||
|
||||
// listMembers list an organization's members
|
||||
func listMembers(ctx *context.APIContext, isMember bool) {
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
Doer: ctx.Doer,
|
||||
IsDoerMember: isMember,
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
count, err := organization.CountOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
members, _, err := organization.FindOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiMembers := make([]*api.User, len(members))
|
||||
for i, member := range members {
|
||||
apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiMembers)
|
||||
}
|
||||
|
||||
// ListMembers list an organization's members
|
||||
func ListMembers(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/members organization orgListMembers
|
||||
// ---
|
||||
// summary: List an organization's members
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
var (
|
||||
isMember bool
|
||||
err error
|
||||
)
|
||||
|
||||
if ctx.Doer != nil {
|
||||
isMember, err = ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
listMembers(ctx, isMember)
|
||||
}
|
||||
|
||||
// ListPublicMembers list an organization's public members
|
||||
func ListPublicMembers(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/public_members organization orgListPublicMembers
|
||||
// ---
|
||||
// summary: List an organization's public members
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listMembers(ctx, false)
|
||||
}
|
||||
|
||||
// IsMember check if a user is a member of an organization
|
||||
func IsMember(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/members/{username} organization orgIsMember
|
||||
// ---
|
||||
// summary: Check if a user is a member of an organization
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to check for an organization membership
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: user is a member
|
||||
// "303":
|
||||
// description: redirection to /orgs/{org}/public_members/{username}
|
||||
// "404":
|
||||
// description: user is not a member
|
||||
|
||||
userToCheck := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if ctx.Doer != nil {
|
||||
userIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if userIsMember || ctx.Doer.IsAdmin {
|
||||
userToCheckIsMember, err := ctx.Org.Organization.IsOrgMember(ctx, userToCheck.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if userToCheckIsMember {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
return
|
||||
} else if ctx.Doer.ID == userToCheck.ID {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
redirectURL := setting.AppSubURL + "/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 to check for a public organization membership
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: user is a public member
|
||||
// "404":
|
||||
// description: user is not a public member
|
||||
|
||||
userToCheck := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
is, err := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, userToCheck.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if is {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
func checkCanChangeOrgUserStatus(ctx *context.APIContext, targetUser *user_model.User) {
|
||||
// allow user themselves to change their status, and allow admins to change any user
|
||||
if targetUser.ID == ctx.Doer.ID || ctx.Doer.IsAdmin {
|
||||
return
|
||||
}
|
||||
// allow org owners to change status of members
|
||||
isOwner, err := ctx.Org.Organization.IsOwnedBy(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusInternalServerError, err)
|
||||
} else if !isOwner {
|
||||
ctx.APIError(http.StatusForbidden, "Cannot change member visibility")
|
||||
}
|
||||
}
|
||||
|
||||
// PublicizeMember make a member's membership public
|
||||
func PublicizeMember(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /orgs/{org}/public_members/{username} organization orgPublicizeMember
|
||||
// ---
|
||||
// summary: Publicize a user's membership
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose membership is to be publicized
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: membership publicized
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
userToPublicize := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
checkCanChangeOrgUserStatus(ctx, userToPublicize)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToPublicize.ID, true)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ConcealMember make a member's membership not public
|
||||
func ConcealMember(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/public_members/{username} organization orgConcealMember
|
||||
// ---
|
||||
// summary: Conceal a user's membership
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose membership is to be concealed
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
userToConceal := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
checkCanChangeOrgUserStatus(ctx, userToConceal)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToConceal.ID, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeleteMember remove a member from an organization
|
||||
func DeleteMember(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/members/{username} organization orgDeleteMember
|
||||
// ---
|
||||
// summary: Remove a member from an organization
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to remove from the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: member removed
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
member := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if err := org_service.RemoveOrgUser(ctx, ctx.Org.Organization, member); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package org
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
system_model "gitea.dev/models/system"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/graceful"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
"gitea.dev/services/org"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
opts := organization.FindOrgOptions{
|
||||
ListOptions: listOptions,
|
||||
UserID: u.ID,
|
||||
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiOrgs := make([]*api.Organization, len(orgs))
|
||||
for i := range orgs {
|
||||
apiOrgs[i] = convert.ToOrganization(ctx, orgs[i])
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(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"
|
||||
// "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 the user whose organizations are to be listed
|
||||
// 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 the user whose permissions are to be obtained
|
||||
// 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.GetUserByPathParam(ctx, "org"); o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := api.OrganizationPermissions{}
|
||||
|
||||
if !organization.HasOrgOrUserVisible(ctx, o, ctx.Doer) {
|
||||
ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
|
||||
return
|
||||
}
|
||||
|
||||
org := organization.OrgFromUser(o)
|
||||
authorizeLevel, err := org.GetOrgUserMaxAuthorizeLevel(ctx, ctx.ContextUser.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(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 {
|
||||
vMode = append(vMode, api.VisibleTypeLimited)
|
||||
if ctx.Doer.IsAdmin {
|
||||
vMode = append(vMode, api.VisibleTypePrivate)
|
||||
}
|
||||
}
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
searchOpts := user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
ListOptions: listOptions,
|
||||
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||
OrderBy: db.SearchOrderByAlphabetically,
|
||||
Visible: vMode,
|
||||
}
|
||||
searchOpts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
orgs := make([]*api.Organization, len(publicOrgs))
|
||||
for i := range publicOrgs {
|
||||
orgs[i] = convert.ToOrganization(ctx, organization.OrgFromUser(publicOrgs[i]))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(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.APIError(http.StatusForbidden, nil)
|
||||
return
|
||||
}
|
||||
|
||||
visibility := api.VisibleTypePublic
|
||||
if form.Visibility != "" {
|
||||
visibility = api.VisibilityModes[string(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.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(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.APIErrorNotFound("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)
|
||||
}
|
||||
|
||||
func Rename(ctx *context.APIContext) {
|
||||
// swagger:operation POST /orgs/{org}/rename organization renameOrg
|
||||
// ---
|
||||
// summary: Rename an organization
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: existing org name
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/RenameOrgOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.RenameOrgOption)
|
||||
orgUser := ctx.Org.Organization.AsUser()
|
||||
if err := user_service.RenameUser(ctx, orgUser, form.NewName, ctx.Doer); err != nil {
|
||||
if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// 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 err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
FullName: optional.FromPtr(form.FullName),
|
||||
Description: optional.FromPtr(form.Description),
|
||||
Website: optional.FromPtr(form.Website),
|
||||
Location: optional.FromPtr(form.Location),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, string(optional.FromPtr(form.Visibility).Value())),
|
||||
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(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.APIErrorInternal(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,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.SetTotalCountHeader(count)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
|
||||
}
|
||||
|
||||
func deleteOrgReposBackground(ctx gocontext.Context, org *organization.Organization, repoIDs []int64, doer *user_model.User) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error("panic during org repo deletion: %v, stack: %v", r, log.Stack(2))
|
||||
}
|
||||
}()
|
||||
|
||||
for _, repoID := range repoIDs {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||
if err != nil {
|
||||
desc := fmt.Sprintf("Failed to get repository ID %d in org %s: %v", repoID, org.Name, err)
|
||||
_ = system_model.CreateNotice(ctx, system_model.NoticeRepository, desc)
|
||||
log.Error("GetRepositoryByID failed: %v", desc)
|
||||
continue
|
||||
}
|
||||
if err := repo_service.DeleteRepository(ctx, doer, repo, true); err != nil {
|
||||
desc := fmt.Sprintf("Failed to delete repository %s (ID: %d) in org %s: %v", repo.Name, repo.ID, org.Name, err)
|
||||
_ = system_model.CreateNotice(ctx, system_model.NoticeRepository, desc)
|
||||
log.Error("DeleteRepository failed: %v", desc)
|
||||
continue
|
||||
}
|
||||
log.Info("Successfully deleted repository %s (ID: %d) in org %s", repo.Name, repo.ID, org.Name)
|
||||
}
|
||||
log.Info("Completed deletion of repositories in org %s", org.Name)
|
||||
}
|
||||
|
||||
func DeleteOrgRepos(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /orgs/{org}/repos organization orgDeleteRepos
|
||||
// ---
|
||||
// summary: Delete all repositories in an organization
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "202":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
// Intentionally it only loads repository IDs to avoid loading too much data into memory
|
||||
// There is no need to do pagination here as the number of repositories is expected to be manageable
|
||||
repoIDs, err := repo_model.GetOrgRepositoryIDs(ctx, ctx.Org.Organization.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(repoIDs) == 0 {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
// Start deletion (slow) in background with detached context, so it can continue even if the request is canceled
|
||||
go deleteOrgReposBackground(graceful.GetManager().ShutdownContext(), ctx.Org.Organization, repoIDs, ctx.Doer)
|
||||
|
||||
ctx.Status(http.StatusAccepted)
|
||||
}
|
||||
@@ -0,0 +1,888 @@
|
||||
// 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"
|
||||
|
||||
activities_model "gitea.dev/models/activities"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
unit_model "gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
org_service "gitea.dev/services/org"
|
||||
repo_service "gitea.dev/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"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
|
||||
ListOptions: listOptions,
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiTeams, err := convert.ToTeams(ctx, teams, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
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"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
|
||||
ListOptions: listOptions,
|
||||
UserID: ctx.Doer.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiTeams, err := convert.ToTeams(ctx, teams, true)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
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.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, apiTeam)
|
||||
}
|
||||
|
||||
func attachTeamUnits(team *organization.Team, defaultAccessMode perm.AccessMode, 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: defaultAccessMode,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
team := &organization.Team{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Name: form.Name,
|
||||
Description: form.Description,
|
||||
IncludesAllRepositories: form.IncludesAllRepositories,
|
||||
CanCreateOrgRepo: form.CanCreateOrgRepo,
|
||||
AccessMode: teamPermission,
|
||||
}
|
||||
|
||||
if team.AccessMode < perm.AccessModeAdmin {
|
||||
if len(form.UnitsMap) > 0 {
|
||||
attachTeamUnitsMap(team, form.UnitsMap)
|
||||
} else if len(form.Units) > 0 {
|
||||
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
|
||||
attachTeamUnits(team, unitPerm, form.Units)
|
||||
} else {
|
||||
ctx.APIErrorInternal(errors.New("units permission should not be empty"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
attachAdminTeamUnits(team)
|
||||
}
|
||||
|
||||
if err := org_service.NewTeam(ctx, team); err != nil {
|
||||
if organization.IsErrTeamAlreadyExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
apiTeam, err := convert.ToTeam(ctx, team, true)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(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 {
|
||||
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
|
||||
if team.AccessMode != teamPermission {
|
||||
isAuthChanged = true
|
||||
team.AccessMode = teamPermission
|
||||
}
|
||||
|
||||
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 {
|
||||
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
|
||||
attachTeamUnits(team, unitPerm, form.Units)
|
||||
}
|
||||
} else {
|
||||
attachAdminTeamUnits(team)
|
||||
}
|
||||
|
||||
if err := org_service.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiTeam, err := convert.ToTeam(ctx, team)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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 := org_service.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isMember && !ctx.Doer.IsAdmin {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
||||
ListOptions: listOptions,
|
||||
TeamID: ctx.Org.Team.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
members := make([]*api.User, len(teamMembers))
|
||||
for i, member := range teamMembers {
|
||||
members[i] = convert.ToUser(ctx, member, ctx.Doer)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(ctx.Org.Team.NumMembers), listOptions.PageSize)
|
||||
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 user whose data is to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/User"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
u := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
teamID := ctx.PathParamInt64("teamid")
|
||||
isTeamMember, err := organization.IsUserInTeams(ctx, u.ID, []int64{teamID})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isTeamMember {
|
||||
ctx.APIErrorNotFound()
|
||||
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 to a team
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
u := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if err := org_service.AddTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(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 from a team
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
u := user.GetContextUserByPathParam(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := org_service.RemoveTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
||||
ctx.APIErrorInternal(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
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
|
||||
ListOptions: listOptions,
|
||||
TeamID: team.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos := make([]*api.Repository, len(teamRepos))
|
||||
for i, repo := range teamRepos {
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
ctx.SetLinkHeader(int64(team.NumRepos), listOptions.PageSize)
|
||||
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.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(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.PathParam("reponame"))
|
||||
if err != nil {
|
||||
if repo_model.IsErrRepoNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(err)
|
||||
return
|
||||
} else if access < perm.AccessModeAdmin {
|
||||
ctx.APIError(http.StatusForbidden, "Must have admin-level access to the repository")
|
||||
return
|
||||
}
|
||||
if err := repo_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
|
||||
ctx.APIErrorInternal(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.APIErrorInternal(err)
|
||||
return
|
||||
} else if access < perm.AccessModeAdmin {
|
||||
ctx.APIError(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.APIErrorInternal(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.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(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 := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
|
||||
}
|
||||
Reference in New Issue
Block a user