cf25eef480
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 13s
Universal: Auto Version Bump / Version Bump (push) Successful in 18s
Generic: Project CI / Lint & Validate (pull_request) Successful in 35s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m11s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
db.ErrNotExist returns 404, other errors return 500 instead of masking all errors as 404. Claude-Session: https://claude.ai/code/session_011AAFzotGMf3ayvXhEmStCd
288 lines
7.8 KiB
Go
288 lines
7.8 KiB
Go
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package org
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
|
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
|
org_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
|
api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
|
)
|
|
|
|
// checkOrgVisibility returns true if the current user can view org metadata.
|
|
// Public orgs are visible to everyone. Private/limited orgs require authentication.
|
|
func checkOrgVisibility(ctx *context.APIContext) bool {
|
|
if ctx.Org.Organization.Visibility == api.VisibleTypePublic {
|
|
return true
|
|
}
|
|
if ctx.Doer == nil {
|
|
ctx.APIErrorNotFound()
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ListIssueStatuses returns active issue status definitions for an org.
|
|
func ListIssueStatuses(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/issue-statuses organization orgListIssueStatuses
|
|
// ---
|
|
// summary: List an organization's issue status definitions
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: "IssueStatusDefList"
|
|
// schema:
|
|
// type: array
|
|
// items:
|
|
// "$ref": "#/definitions/IssueStatusDef"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
if !checkOrgVisibility(ctx) {
|
|
return
|
|
}
|
|
|
|
defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
result := make([]*api.IssueStatusDef, 0, len(defs))
|
|
for _, d := range defs {
|
|
result = append(result, &api.IssueStatusDef{
|
|
ID: d.ID,
|
|
Name: d.Name,
|
|
Color: d.Color,
|
|
Description: d.Description,
|
|
ClosesIssue: d.ClosesIssue,
|
|
IsRequired: d.IsRequired,
|
|
SortOrder: d.SortOrder,
|
|
})
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// ListIssuePriorities returns active issue priority definitions for an org.
|
|
func ListIssuePriorities(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/issue-priorities organization orgListIssuePriorities
|
|
// ---
|
|
// summary: List an organization's issue priority definitions
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: "IssuePriorityDefList"
|
|
// schema:
|
|
// type: array
|
|
// items:
|
|
// "$ref": "#/definitions/IssuePriorityDef"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
if !checkOrgVisibility(ctx) {
|
|
return
|
|
}
|
|
|
|
defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
result := make([]*api.IssuePriorityDef, 0, len(defs))
|
|
for _, d := range defs {
|
|
result = append(result, &api.IssuePriorityDef{
|
|
ID: d.ID,
|
|
Name: d.Name,
|
|
Color: d.Color,
|
|
Description: d.Description,
|
|
SortOrder: d.SortOrder,
|
|
IsDefault: d.IsDefault,
|
|
})
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// ListIssueTypes returns active issue type definitions for an org.
|
|
func ListIssueTypes(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/issue-types organization orgListIssueTypes
|
|
// ---
|
|
// summary: List an organization's issue type definitions
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: "IssueTypeDefList"
|
|
// schema:
|
|
// type: array
|
|
// items:
|
|
// "$ref": "#/definitions/IssueTypeDef"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
if !checkOrgVisibility(ctx) {
|
|
return
|
|
}
|
|
|
|
defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
result := make([]*api.IssueTypeDef, 0, len(defs))
|
|
for _, d := range defs {
|
|
result = append(result, &api.IssueTypeDef{
|
|
ID: d.ID,
|
|
Name: d.Name,
|
|
Color: d.Color,
|
|
Description: d.Description,
|
|
SortOrder: d.SortOrder,
|
|
IsDefault: d.IsDefault,
|
|
})
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// ListIssueStatusPresets returns the available status preset templates.
|
|
func ListIssueStatusPresets(ctx *context.APIContext) {
|
|
// swagger:operation GET /orgs/{org}/issue-statuses/presets organization orgListIssueStatusPresets
|
|
// ---
|
|
// summary: List available issue status presets
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: "StatusPresetList"
|
|
|
|
result := make([]*api.StatusPreset, 0, len(issues_model.StatusPresetNames()))
|
|
for _, name := range issues_model.StatusPresetNames() {
|
|
preset := issues_model.StatusPresets[name]
|
|
statuses := make([]*api.StatusPresetEntry, 0, len(preset.Statuses))
|
|
for _, s := range preset.Statuses {
|
|
statuses = append(statuses, &api.StatusPresetEntry{
|
|
Name: s.Name,
|
|
Color: s.Color,
|
|
Description: s.Description,
|
|
ClosesIssue: s.ClosesIssue,
|
|
IsRequired: s.IsRequired,
|
|
})
|
|
}
|
|
result = append(result, &api.StatusPreset{
|
|
Name: preset.Name,
|
|
Description: preset.Description,
|
|
Statuses: statuses,
|
|
})
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// ApplyIssueStatusPreset applies a status preset to an organization.
|
|
func ApplyIssueStatusPreset(ctx *context.APIContext) {
|
|
// swagger:operation POST /orgs/{org}/issue-statuses/presets/{preset} organization orgApplyIssueStatusPreset
|
|
// ---
|
|
// summary: Apply a status preset to an organization
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: name of the organization
|
|
// type: string
|
|
// required: true
|
|
// - name: preset
|
|
// in: path
|
|
// description: preset name
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// description: "StatusPresetApplied"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
presetName := ctx.PathParam("preset")
|
|
if err := issues_model.ApplyStatusPreset(ctx, ctx.Org.Organization.ID, presetName); err != nil {
|
|
if db.IsErrNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
} else {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
return
|
|
}
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// CopyIssueStatusesFromOrg copies status definitions from another organization.
|
|
func CopyIssueStatusesFromOrg(ctx *context.APIContext) {
|
|
// swagger:operation POST /orgs/{org}/issue-statuses/copy/{source_org} organization orgCopyIssueStatuses
|
|
// ---
|
|
// summary: Copy issue statuses from another organization
|
|
// parameters:
|
|
// - name: org
|
|
// in: path
|
|
// description: target organization name
|
|
// type: string
|
|
// required: true
|
|
// - name: source_org
|
|
// in: path
|
|
// description: source organization name to copy from
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// description: "StatusesCopied"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
sourceOrgName := ctx.PathParam("source_org")
|
|
sourceOrg, err := org_model.GetOrgByName(ctx, sourceOrgName)
|
|
if err != nil {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
|
|
if sourceOrg.Visibility != api.VisibleTypePublic && !ctx.Doer.IsAdmin {
|
|
isMember, err := org_model.IsOrganizationMember(ctx, sourceOrg.ID, ctx.Doer.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
if !isMember {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := issues_model.CopyStatusesFromOrg(ctx, sourceOrg.ID, ctx.Org.Organization.ID); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|