Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f329466d1c | |||
| 05a89339e1 | |||
| 75e640dd17 | |||
| 09f17439ec | |||
| 9d45a767e7 | |||
| ece24c6d38 | |||
| e0d4f5fd15 | |||
| c67e7373fb | |||
| cbaca15cda | |||
| 245b5a8e6a | |||
| d61680d5b3 |
@@ -4,7 +4,7 @@
|
||||
<name>MokoGitea</name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>Moko fork of Gitea - adding project board REST API endpoints and custom enhancements</description>
|
||||
<version>06.14.00</version>
|
||||
<version>06.15.00</version>
|
||||
<version-prefix>v1.26.1+MOKO</version-prefix>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokoplatform.Automation
|
||||
# VERSION: 06.14.00
|
||||
# VERSION: 06.15.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
+19
-21
@@ -2,7 +2,21 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [06.14.00] --- 2026-06-11
|
||||
## [06.15.00] --- 2026-06-12
|
||||
|
||||
## [06.15.00] --- 2026-06-12
|
||||
|
||||
* FEATURES
|
||||
* feat(custom-fields): required flag UI and API validation (#597, PR #612)
|
||||
* Required checkbox in org custom field settings
|
||||
* Red asterisk indicator on required fields
|
||||
* API returns 422 when required custom fields are missing
|
||||
* Validation runs before issue creation (no orphaned issues)
|
||||
* feat(issues): make status_id, priority_id, type_id required on issue create (#598, PR #613)
|
||||
* `CreateIssueOption` fields changed from optional `*int64` to `int64`
|
||||
* Auto-assigns org defaults when value is 0
|
||||
* MCP `gitea_issue_create` now requires these fields (pass 0 for defaults)
|
||||
* Explicit metadata errors now return 500 instead of being silently discarded
|
||||
|
||||
## [06.14.00] --- 2026-06-11
|
||||
|
||||
@@ -11,14 +25,16 @@
|
||||
* fix(ui): raw file button opens in new tab with rel="noopener noreferrer" (#581, PR #600)
|
||||
* fix: update server feed generation bugs (#601, PR #605)
|
||||
* default targetplatform changed from `(5|6)\\..*` to `6\\..*` for Joomla 6 compat
|
||||
* `<client>` element changed from string to numeric `0`/`1`
|
||||
* `<client>` uses string values `site`/`administrator` per Joomla update spec (#611)
|
||||
* pre-release version suffix number preserved (e.g. `-rc2` not `-rc`)
|
||||
* feed generator uses `FullElementName()` for auto-constructed element names
|
||||
* fix: wiki API sub-page support and content response (#606, #607, PR #608)
|
||||
* wiki routes use wildcard to support pages with path separators
|
||||
* `ListWikiPages` returns pages in subdirectories
|
||||
* error logging for empty content_base64 responses
|
||||
* fix: remove `swapoff -a` from deploy workflow that caused MySQL crashes
|
||||
* fix: deploy workflow clones wrong repo and runs swapoff (#609)
|
||||
* removed `swapoff -a` that crashed MySQL during deploys
|
||||
* fixed source repo URL from MokoGitea to MokoGitea-APP
|
||||
|
||||
* MCP
|
||||
* metadata update tool now exposes element_name, display_name, description, license_name, language fields
|
||||
@@ -43,21 +59,3 @@ All notable changes to MokoGitea are documented here. Versions follow the format
|
||||
|
||||
* MIGRATIONS
|
||||
* migration 354: add wiki_mode and wiki_url columns to user table for org wiki settings
|
||||
|
||||
## [v1.26.1-moko.06.12] - 2026-06-07
|
||||
|
||||
* FEATURES
|
||||
* feat(security): dependency vulnerability scanner - parses go.mod, package.json, composer.json, requirements.txt and checks against OSV.dev API (#551)
|
||||
* feat(cdn): built-in CDN for release asset delivery via cdn.mokoconsulting.tech with per-asset public/private toggles (#561)
|
||||
* feat(cdn): IP/CIDR and referrer domain allowlists for CDN abuse prevention
|
||||
* feat(cdn): releases in update streams excluded from CDN (update server takes precedence)
|
||||
|
||||
* FIXES
|
||||
* fix(licensing): hide "Require license key" option for Joomla update servers (Joomla limitation)
|
||||
* fix(settings): remove duplicate description from manifest page (#559)
|
||||
|
||||
* INFRASTRUCTURE
|
||||
* chore: rename moko-platform to MokoPlatform across codebase (#548)
|
||||
* CDN CNAME: cdn.mokoconsulting.tech with auto-TLS via Let's Encrypt
|
||||
* Nginx reverse proxy for CDN hostname on production server
|
||||
* DreamHost MCP server path and API key configured
|
||||
|
||||
@@ -114,10 +114,10 @@ type CreateIssueOption struct {
|
||||
Closed bool `json:"closed"`
|
||||
// custom field values keyed by field name
|
||||
CustomFields map[string]string `json:"custom_fields,omitempty"`
|
||||
// org-level issue metadata IDs
|
||||
StatusID *int64 `json:"status_id,omitempty"`
|
||||
PriorityID *int64 `json:"priority_id,omitempty"`
|
||||
TypeID *int64 `json:"type_id,omitempty"`
|
||||
// org-level issue metadata IDs (auto-assigned from org defaults when 0)
|
||||
StatusID int64 `json:"status_id"`
|
||||
PriorityID int64 `json:"priority_id"`
|
||||
TypeID int64 `json:"type_id"`
|
||||
}
|
||||
|
||||
// EditIssueOption options for editing an issue
|
||||
|
||||
@@ -2976,6 +2976,8 @@
|
||||
"org.settings.custom_field_options": "Options (JSON)",
|
||||
"org.settings.custom_field_options_help": "For dropdown fields, enter options as a JSON array.",
|
||||
"org.settings.custom_field_description": "Description",
|
||||
"org.settings.custom_field_required": "Required",
|
||||
"org.settings.custom_field_required_help": "When checked, this field must be filled in when creating or editing issues.",
|
||||
"org.settings.custom_field_created": "Custom field created.",
|
||||
"org.settings.custom_field_updated": "Custom field updated.",
|
||||
"org.settings.custom_field_deleted": "Custom field deleted.",
|
||||
|
||||
@@ -722,6 +722,22 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
form.Labels = make([]int64, 0)
|
||||
}
|
||||
|
||||
// Validate required custom fields BEFORE creating the issue to avoid
|
||||
// leaving orphaned issues when validation fails.
|
||||
customFieldDefs, defErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
|
||||
if defErr != nil {
|
||||
ctx.APIErrorInternal(defErr)
|
||||
return
|
||||
}
|
||||
for _, def := range customFieldDefs {
|
||||
if def.Required {
|
||||
if v, ok := form.CustomFields[def.Name]; !ok || strings.TrimSpace(v) == "" {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("custom field %q is required", def.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := issue_service.NewIssue(ctx, ctx.Repo.Repository, issue, form.Labels, nil, assigneeIDs, form.Projects); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
@@ -733,35 +749,30 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// Save custom field values if provided (resolve field names to IDs).
|
||||
if len(form.CustomFields) > 0 {
|
||||
defs, defErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
|
||||
if defErr != nil {
|
||||
ctx.APIErrorInternal(defErr)
|
||||
return
|
||||
}
|
||||
if len(defs) > 0 {
|
||||
vals := make(map[int64]string)
|
||||
for _, def := range defs {
|
||||
if v, ok := form.CustomFields[def.Name]; ok {
|
||||
vals[def.ID] = v
|
||||
}
|
||||
// Save custom field values (reuse defs from validation above).
|
||||
if len(customFieldDefs) > 0 && len(form.CustomFields) > 0 {
|
||||
vals := make(map[int64]string)
|
||||
for _, def := range customFieldDefs {
|
||||
if v, ok := form.CustomFields[def.Name]; ok {
|
||||
vals[def.ID] = v
|
||||
}
|
||||
if len(vals) > 0 {
|
||||
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
|
||||
ctx.APIErrorInternal(setErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(vals) > 0 {
|
||||
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
|
||||
ctx.APIErrorInternal(setErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set org-level issue metadata (status/priority/type).
|
||||
// If not provided, auto-assign the org default.
|
||||
if form.StatusID != nil && *form.StatusID > 0 {
|
||||
_ = issues_model.SetIssueStatusID(ctx, issue.ID, *form.StatusID)
|
||||
// Use provided value if > 0, otherwise auto-assign org default.
|
||||
if form.StatusID > 0 {
|
||||
if err := issues_model.SetIssueStatusID(ctx, issue.ID, form.StatusID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Auto-assign first non-closing status.
|
||||
if defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
|
||||
for _, d := range defs {
|
||||
if !d.ClosesIssue {
|
||||
@@ -771,8 +782,11 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if form.PriorityID != nil && *form.PriorityID > 0 {
|
||||
_ = issues_model.SetIssuePriorityID(ctx, issue.ID, *form.PriorityID)
|
||||
if form.PriorityID > 0 {
|
||||
if err := issues_model.SetIssuePriorityID(ctx, issue.ID, form.PriorityID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
|
||||
for _, d := range defs {
|
||||
@@ -783,8 +797,11 @@ func CreateIssue(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if form.TypeID != nil && *form.TypeID > 0 {
|
||||
_ = issues_model.SetIssueTypeID(ctx, issue.ID, *form.TypeID)
|
||||
if form.TypeID > 0 {
|
||||
if err := issues_model.SetIssueTypeID(ctx, issue.ID, form.TypeID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
|
||||
for _, d := range defs {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<tbody>
|
||||
{{range .CustomFields}}
|
||||
<tr>
|
||||
<td><strong>{{.Name}}</strong>{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}</td>
|
||||
<td><strong>{{.Name}}</strong>{{if .Required}} <span class="tw-text-red" data-tooltip-content="Required">*</span>{{end}}{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}</td>
|
||||
<td>{{if eq .Scope "issue"}}{{svg "octicon-issue-opened" 14}} Issue{{else}}{{svg "octicon-repo" 14}} Repo{{end}}</td>
|
||||
<td><code>{{.FieldType}}</code></td>
|
||||
<td>{{if .Options}}<code class="tw-text-xs">{{.Options}}</code>{{else}}<span class="text grey">-</span>{{end}}</td>
|
||||
@@ -79,6 +79,13 @@
|
||||
<input name="description" placeholder="Help text shown to users">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="required">
|
||||
<label>{{ctx.Locale.Tr "org.settings.custom_field_required"}}</label>
|
||||
</div>
|
||||
<p class="help">{{ctx.Locale.Tr "org.settings.custom_field_required_help"}}</p>
|
||||
</div>
|
||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "org.settings.custom_field_add"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user