Files
jmiller 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
fix: distinguish unknown preset from DB errors in ApplyIssueStatusPreset
db.ErrNotExist returns 404, other errors return 500 instead of
masking all errors as 404.

Claude-Session: https://claude.ai/code/session_011AAFzotGMf3ayvXhEmStCd
2026-06-28 03:34:07 -05:00

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)
}