6bd9548b2a
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 23s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m4s
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report Issues (pull_request) Has been cancelled
- CustomFieldDef now has owner_id (org) and scope (issue/repo) - Issue sidebar loads fields by org owner_id, not repo_id - Org Settings > Custom Fields page for managing field definitions - Repo Settings > Metadata page for filling in repo-scoped values - Migration v345 adds owner_id, scope, entity_id, entity_type columns - Per-repo custom field management replaced by org-level - Replaces .mokogitea/manifest.xml with database-backed metadata Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package org
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
|
)
|
|
|
|
const tplOrgCustomFields templates.TplName = "org/settings/custom_fields"
|
|
|
|
// SettingsCustomFields shows the org-level custom fields management page.
|
|
func SettingsCustomFields(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("org.settings.custom_fields")
|
|
ctx.Data["PageIsOrgSettings"] = true
|
|
ctx.Data["PageIsSettingsCustomFields"] = true
|
|
|
|
fields, err := issues_model.GetAllCustomFieldsByOwner(ctx, ctx.Org.Organization.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetAllCustomFieldsByOwner", err)
|
|
return
|
|
}
|
|
ctx.Data["CustomFields"] = fields
|
|
|
|
ctx.HTML(http.StatusOK, tplOrgCustomFields)
|
|
}
|
|
|
|
// SettingsCustomFieldsCreatePost creates a new org-level custom field.
|
|
func SettingsCustomFieldsCreatePost(ctx *context.Context) {
|
|
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
|
|
scope := issues_model.CustomFieldScope(ctx.FormString("scope"))
|
|
if scope != issues_model.CustomFieldScopeIssue && scope != issues_model.CustomFieldScopeRepo {
|
|
scope = issues_model.CustomFieldScopeIssue
|
|
}
|
|
|
|
field := &issues_model.CustomFieldDef{
|
|
OwnerID: ctx.Org.Organization.ID,
|
|
RepoID: 0, // org-level
|
|
Scope: scope,
|
|
Name: ctx.FormString("name"),
|
|
FieldType: issues_model.CustomFieldType(ctx.FormString("field_type")),
|
|
Description: ctx.FormString("description"),
|
|
Options: ctx.FormString("options"),
|
|
Required: ctx.FormString("required") == "on",
|
|
SortOrder: sortOrder,
|
|
IsActive: true,
|
|
}
|
|
|
|
if field.Name == "" {
|
|
ctx.Flash.Error("Field name is required")
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/custom-fields")
|
|
return
|
|
}
|
|
|
|
if err := issues_model.CreateCustomFieldDef(ctx, field); err != nil {
|
|
ctx.ServerError("CreateCustomFieldDef", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.custom_field_created"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/custom-fields")
|
|
}
|
|
|
|
// SettingsCustomFieldsEditPost updates an org-level custom field.
|
|
func SettingsCustomFieldsEditPost(ctx *context.Context) {
|
|
id := ctx.PathParamInt64("id")
|
|
field, err := issues_model.GetCustomFieldDefByID(ctx, id)
|
|
if err != nil {
|
|
ctx.ServerError("GetCustomFieldDefByID", err)
|
|
return
|
|
}
|
|
if field.OwnerID != ctx.Org.Organization.ID {
|
|
ctx.NotFound(nil)
|
|
return
|
|
}
|
|
|
|
field.Name = ctx.FormString("name")
|
|
field.FieldType = issues_model.CustomFieldType(ctx.FormString("field_type"))
|
|
field.Description = ctx.FormString("description")
|
|
field.Options = ctx.FormString("options")
|
|
field.Required = ctx.FormString("required") == "on"
|
|
field.IsActive = ctx.FormString("is_active") == "on"
|
|
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
|
|
field.SortOrder = sortOrder
|
|
|
|
if err := issues_model.UpdateCustomFieldDef(ctx, field); err != nil {
|
|
ctx.ServerError("UpdateCustomFieldDef", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.custom_field_updated"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/custom-fields")
|
|
}
|
|
|
|
// SettingsCustomFieldsDeletePost deletes an org-level custom field.
|
|
func SettingsCustomFieldsDeletePost(ctx *context.Context) {
|
|
id := ctx.PathParamInt64("id")
|
|
field, err := issues_model.GetCustomFieldDefByID(ctx, id)
|
|
if err != nil {
|
|
ctx.ServerError("GetCustomFieldDefByID", err)
|
|
return
|
|
}
|
|
if field.OwnerID != ctx.Org.Organization.ID {
|
|
ctx.NotFound(nil)
|
|
return
|
|
}
|
|
|
|
if err := issues_model.DeleteCustomFieldDef(ctx, id); err != nil {
|
|
ctx.ServerError("DeleteCustomFieldDef", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("org.settings.custom_field_deleted"))
|
|
ctx.Redirect(ctx.Org.OrgLink + "/settings/custom-fields")
|
|
}
|