Compare commits

...

298 Commits

Author SHA1 Message Date
jmiller f3ce51d629 Merge pull request 'chore: final version update' (#560) from chore/final-version into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 02:21:06 +00:00
Jonathan Miller 1f505b48c7 chore: update wiki version to v1.26.1-moko.06.11.01
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m49s
2026-06-06 21:20:37 -05:00
jmiller afda7abcbe Merge pull request 'fix(settings): remove duplicate description from manifest' (#559) from fix/manifest-desc-dedup into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 02:08:31 +00:00
Jonathan Miller 798d9c3ae0 fix(settings): remove duplicate description from manifest page
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m40s
Description is already managed in repo general settings. The manifest
now reads it from the repository object instead of a separate form field.
2026-06-06 21:07:39 -05:00
jmiller e0f22dd397 Merge pull request 'chore: wiki version update' (#558) from chore/wiki-version-update into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-07 01:57:10 +00:00
Jonathan Miller ecda05aa46 chore: update wiki version to v1.26.1-moko.06.11.00 and roadmap
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m26s
2026-06-06 20:56:25 -05:00
jmiller 9eadade2f4 Merge pull request 'feat: Issue Types settings + MCP SSE + npm auto-publish' (#557) from feat/type-settings-mcp-sse-npm into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-07 00:53:45 +00:00
Jonathan Miller 2cc4f7c047 feat: org settings for Issue Types + MCP SSE hosted + npm auto-publish
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m9s
- Org settings page for managing Issue Type definitions (CRUD)
- MCP SSE endpoint deployed at git.mokoconsulting.tech/mcp/
- npm auto-publish workflow on MCP source changes to main
2026-06-06 19:52:55 -05:00
jmiller 74a5fe2b80 Merge pull request 'fix(auth): login form with OAuth on all error pages' (#556) from fix/error-pages-login into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
2026-06-06 23:26:43 +00:00
Jonathan Miller 50c472991a fix(auth): add login form with OAuth to all error pages
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m33s
- 403: OAuth buttons moved below password form (matches regular login)
- 404: Login form with OAuth added for unauthenticated users
- Both pages load OAuth2 providers and show Sign In with Google etc.
2026-06-06 18:25:51 -05:00
jmiller 7dabf844a8 Merge pull request 'fix(auth): show OAuth providers on 403 login form' (#547) from fix/403-oauth-login into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-06 23:16:41 +00:00
Jonathan Miller 7d03541201 fix(auth): show OAuth providers on 403 login form
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m13s
The 403 Access Denied page login form now shows OAuth2 provider
buttons (Sign in with Google, etc.) alongside the username/password
form. Previously only showed password login even when OAuth was
configured.
2026-06-06 18:15:25 -05:00
jmiller d4a2c33c37 Merge pull request 'chore: changelog + MCP type/security tools' (#546) from chore/changelog-mcp-update into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-06 22:51:55 +00:00
Jonathan Miller e59290802a chore: update changelog and MCP with type/security tools
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m32s
- Add v1.26.1-moko.06.10 changelog entry with all features
- Add gitea_org_issue_types_list, gitea_issue_set_type, gitea_security_alerts to MCP
- Add type_id param to issue create
2026-06-06 17:51:09 -05:00
jmiller 1d857d8205 Merge pull request 'chore: update wiki' (#544) from chore/wiki-mcp-update into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
2026-06-06 22:26:54 +00:00
Jonathan Miller 4f9aeb7b85 chore: update wiki - version, first-class fields, security, roadmap
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m43s
- Update version to v1.26.1-moko.06.10.00
- Document 13 default statuses including Pending states
- Document status replacing close button in comment form
- Add Security scanning, MCP, Wiki folders to features list
- Update roadmap with completed and planned items
- Add features/ folder link to pages table
2026-06-06 17:26:19 -05:00
jmiller 1178eaec62 Merge pull request 'feat(issues): first-class Type field + list badges' (#543) from feat/issue-type-first-class into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
2026-06-06 22:13:40 +00:00
Jonathan Miller dd1454c3cf feat(issues): first-class Type field + status/priority/type badges in issue list
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m10s
- IssueTypeDef model with auto-seed defaults (Bug, Feature, Enhancement, Task, Documentation, Security)
- Migration v350 adding issue_type_def table + type_id on issues
- Type dropdown in issue sidebar
- Type, Priority, Status colored badges in issue list view
- Status/Priority/Type definitions loaded in issue list handler
2026-06-06 17:12:44 -05:00
jmiller c539bed4d3 Merge pull request 'fix(ui): dashboard issue count badges' (#542) from fix/dashboard-badges into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-06 21:56:28 +00:00
Jonathan Miller 135b37edf1 fix(ui): use badge labels instead of strong tags for dashboard issue counts
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m20s
The sidebar counts (In your repositories, Assigned to you, etc.)
were rendering as bold text concatenated with the label. Now uses
ui small label spans for proper badge styling.
2026-06-06 16:54:26 -05:00
jmiller 48cf445e79 Merge pull request 'feat(security): add Security tab to repo navigation' (#541) from feat/508-security-scanner into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
2026-06-06 21:36:36 +00:00
Jonathan Miller 72708b5a99 feat(security): add Security tab to repo navigation (#508)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m19s
Add a top-level Security tab in the repo header (visible to admins
only) showing alerts, scan controls, and severity badges. Links to
settings page for scanner configuration. Alert file paths link
directly to the source file.
2026-06-06 16:35:55 -05:00
jmiller 948860e8ac Merge pull request 'feat(security): built-in security scanning platform (#508)' (#540) from feat/508-security-scanner into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 21:24:11 +00:00
Jonathan Miller f7c1904625 feat(security): built-in security scanning platform (#508)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m44s
Add a pluggable security scanning framework with secret detection
as the first scanner module. Scans run on push to default branch
and on-demand via the Security settings page.

Includes:
- Scanner interface for pluggable scanner types
- Secret scanner with 15 built-in patterns (AWS, GitHub, Stripe, etc.)
- SecurityAlert model with fingerprint-based dedup
- SecurityScannerConfig per-repo settings
- Migration v349 for security tables
- Repo settings Security page with alerts table
- Scan Now button for on-demand scanning
- Alert resolve/dismiss actions
- Push-time scanning in post-receive hook
2026-06-06 16:23:08 -05:00
jmiller e3b2df4aac Merge pull request 'fix(wiki): folder listing template' (#539) from feat/79-wiki-folders into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-06 20:52:19 +00:00
Jonathan Miller 6cd4a19ed6 fix(wiki): render folder listing template instead of start page
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m4s
When navigating to a wiki folder with no index file, Wiki() handler
now renders the wiki view template with IsWikiFolder flag instead of
falling back to the empty wiki start page.
2026-06-06 15:51:24 -05:00
jmiller 4d73c6a939 Merge pull request 'fix(wiki): directory check before raw redirect' (#538) from feat/79-wiki-folders into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-06 20:35:25 +00:00
Jonathan Miller df91ed2aac fix(wiki): check directory before file lookup to prevent raw redirect
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m0s
Directory paths were being found by wikiEntryByName as non-.md entries,
triggering a redirect to /wiki/raw/. Now checks for directories first
and handles index file lookup before the file/raw detection.
2026-06-06 15:34:49 -05:00
jmiller 8639d85fe7 Merge pull request 'fix(wiki): type mismatch in folder listing' (#537) from feat/79-wiki-folders into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
2026-06-06 20:28:57 +00:00
Jonathan Miller e7a79d973e fix(wiki): type mismatch in folder listing
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 54s
2026-06-06 15:28:20 -05:00
jmiller 6c5394107e Merge pull request 'fix(wiki): proper display names in tree' (#536) from feat/79-wiki-folders into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 20:25:41 +00:00
Jonathan Miller 0a158e9ec3 fix(wiki): use GitPathToWebPath for proper display names in tree
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 57s
Fixes the .- dash marker showing in sidebar tree and folder listings.
Uses the proper path conversion functions instead of raw TrimSuffix.
2026-06-06 15:24:55 -05:00
jmiller 48c354fbb4 Merge pull request 'fix(wiki): preserve slashes in page titles' (#535) from feat/79-wiki-folders into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 20:16:52 +00:00
Jonathan Miller 41c42b968e fix(wiki): preserve slashes in page titles for folder creation
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m0s
UserTitleToWebPath now splits on / and sanitizes each segment
independently, preserving the directory structure. This allows
creating pages like "features/Custom-Fields" as actual nested files.
2026-06-06 15:15:57 -05:00
jmiller d64dc7cf45 Merge pull request 'feat(wiki): hierarchical folder navigation (#79)' (#534) from feat/79-wiki-folders into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
2026-06-06 20:10:24 +00:00
Jonathan Miller 6010841ee7 feat(wiki): hierarchical folder navigation with sidebar tree (#79)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m45s
Support real subdirectories in wiki instead of escaping / to %2F.
When navigating to a folder, tries README.md, Home.md, index.md
as index pages. If none found, shows a file/folder listing.

Includes:
- Stop escaping / in WebPathFromRequest (wiki_path.go)
- Folder detection with index file fallback
- Auto-generated sidebar folder tree
- Breadcrumb navigation for nested paths
- Folder listing view when no index page exists
- CSS for tree sidebar and folder listing
2026-06-06 15:09:23 -05:00
jmiller 3857f1339d chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-06-06 19:51:10 +00:00
jmiller 328ff92c52 Merge pull request 'chore: update wiki pages' (#533) from chore/wiki-update into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 19:50:33 +00:00
Jonathan Miller af7d6d78a8 chore: update wiki - roadmap, version, add pending pages
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Successful in 1m20s
- Update version to v1.26.1-moko.06.07.03
- Rewrite roadmap with current features and priorities
- Add pending wiki pages (branding, deployment, API docs)
2026-06-06 14:48:42 -05:00
jmiller 01ef500793 chore: sync .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-06-06 19:48:35 +00:00
Jonathan Miller 1438dc7838 fix(issues): closed issues show reopen-only for non-admins
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Regular users/issue posters see only a Reopen button on closed issues.
Admins and team members with write permission get the full status
dropdown including Reopen option.
2026-06-06 14:21:39 -05:00
jmiller a6245ff075 Merge pull request 'feat(issues): status dropdown replaces close button' (#528) from feat/status-replaces-close into dev
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: Build & Release / Promote to RC (pull_request) Failing after 12s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 24s
PR RC Release / Build RC Release (pull_request) Failing after 32s
2026-06-06 19:13:28 +00:00
Jonathan Miller a4b7b5276c feat(issues): status dropdown replaces close button for issues
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been skipped
Move the status dropdown from sidebar to the comment form footer,
replacing the close/reopen button. Status selections that have
closes_issue=true auto-close, non-closing statuses auto-reopen.
Falls back to standard close/reopen button for PRs and repos
without org statuses.
2026-06-06 14:12:27 -05:00
jmiller 64b3dbe50b Merge pull request 'fix(issues): auto-seed default statuses and priorities' (#526) from fix/auto-seed-status-priority into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 31s
PR RC Release / Build RC Release (pull_request) Failing after 53s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m15s
2026-06-06 18:41:59 +00:00
Jonathan Miller 22586b7a06 fix(issues): auto-seed default statuses and priorities for orgs
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Failing after 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 50s
Status and priority are first-class fields, not custom fields. They
must always show in the sidebar without requiring manual setup. When
an org has no definitions, the standard presets are auto-created on
first access.
2026-06-06 13:40:53 -05:00
jmiller eccb0de243 Merge pull request 'feat(mcp): public release with SSE, npm, Docker (#523)' (#524) from feat/523-mcp-public into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 9s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 32s
PR RC Release / Build RC Release (pull_request) Failing after 54s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 38s
2026-06-06 17:56:28 +00:00
Jonathan Miller f80c99db3c feat(mcp): public release with SSE transport, npm package, Docker support (#523)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 52s
- Add SSE transport (sse.ts) for hosted deployments
- Env var config (GITEA_URL/GITEA_TOKEN) for zero-config setup
- Dockerfile for containerized SSE mode
- npm publishing as @mokoconsulting/mokogitea-mcp
- README with quick start, tool reference, config options
- Server factory (server.ts) for SSE reuse
2026-06-06 12:55:21 -05:00
jmiller d32074581d Merge pull request 'fix(mcp): deduplicate issue creation and add status/priority fields' (#521) from fix/mcp-dedup-priority into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 25s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 33s
2026-06-06 17:39:59 +00:00
Jonathan Miller 65432aaec6 fix(mcp): deduplicate issue creation and add status/priority fields
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 25s
- gitea_issue_create searches by title before creating to prevent duplicates
- If duplicate found, updates existing issue instead
- Added status_id and priority_id parameters to issue create
- Status and priority set via dedicated endpoints after create/update
2026-06-06 12:39:17 -05:00
jmiller 01da6a48b1 Merge pull request 'chore: add MokoGitea MCP server source to repo' (#519) from chore/mcp-source into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Failing after 27s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 27s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 43s
2026-06-06 17:29:59 +00:00
Jonathan Miller 3e86c5181e chore: add MokoGitea MCP server source to repo
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 24s
Source reference copy of the mokogitea_api MCP server with tools for
manifest, issue statuses, and issue priorities API endpoints.
Runtime lives in moko-platform/mcp/servers/mokogitea_api/.
2026-06-06 12:27:30 -05:00
jmiller 1ffe31e360 Merge pull request 'fix: rename Priority field to PriorityDef to avoid redeclaration' (#517) from feat/509-issue-priority into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Failing after 25s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 36s
2026-06-06 17:00:04 +00:00
Jonathan Miller e2e80de6fa fix: rename Priority field to PriorityDef to avoid redeclaration
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 18s
The Issue struct already has a Priority int field at line 74.
2026-06-06 11:58:53 -05:00
jmiller 759a8f590c Merge pull request 'feat(issues): org-level priority field (#509)' (#516) from feat/509-issue-priority into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 16:53:56 +00:00
Jonathan Miller 55c2f81c58 feat(issues): org-level priority field with customizable levels (#509)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 30s
Add org-level issue priority definitions that appear in the issue
sidebar. Each priority has a name, color, sort order, and optional
default flag. Follows the same architecture as custom statuses (#502).

Includes:
- IssuePriorityDef model with CRUD operations
- Migration v348 adding issue_priority_def table + priority_id on issues
- Org settings UI for managing priorities
- Issue sidebar dropdown for selecting priority

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 11:52:44 -05:00
jmiller b3ee5cc18a Merge pull request 'fix: replace non-ASCII em dashes in CLAUDE.md and manifest.xml' (#514) from fix/ascii-cleanup into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
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 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Failing after 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 24s
PR RC Release / Build RC Release (pull_request) Failing after 35s
2026-06-06 16:22:58 +00:00
Jonathan Miller 4e1a90c4e4 fix: replace non-ASCII em dashes with ASCII in CLAUDE.md and manifest.xml
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 20s
Prevents hook JSON validation failures caused by non-ASCII characters
in files read during stop hooks.
2026-06-06 11:22:11 -05:00
jmiller 7532b9ff55 Merge pull request 'feat(settings): manifest auto-sync on push + wiki pages' (#510) from feat/315-manifest-settings into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Failing after 32s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m10s
2026-06-06 14:48:01 +00:00
Jonathan Miller dd6e114c70 chore: move CLAUDE.md to .mokogitea/ directory
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Failing after 0s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 25s
Also includes:
- Auto-sync manifest.xml to DB on push to default branch
- Wiki pages for custom fields, custom statuses, manifest settings
- Updated wiki home page with all current features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 09:43:15 -05:00
Jonathan Miller 1f6af9dd0a chore: move CLAUDE.md to .mokogitea/ directory
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Relocate CLAUDE.md from repo root to .mokogitea/ per project convention.
Content updated with focused, repo-specific architecture and rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 09:31:19 -05:00
jmiller 2d9ca59599 Merge pull request 'feat(settings): repo manifest settings with auto-migration and API (#315)' (#504) from feat/315-manifest-settings into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 14:15:08 +00:00
jmiller 7e615516eb Merge pull request 'feat(issues): custom status definitions with automated actions (#502)' (#503) from feat/502-custom-issue-statuses into dev
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
2026-06-06 14:13:35 +00:00
Jonathan Miller 34fe0c5934 fix(api): use correct APIContext error methods for manifest endpoint
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Failing after 1s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 32s
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 09:08:23 -05:00
Jonathan Miller 3aaa7c0843 feat(settings): repo manifest settings with auto-migration and API (#315)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
Add a "Manifest" page in repo settings that stores moko-platform manifest
fields (identity, governance, build) in the database. Includes:

- RepoManifest model with all manifest.xml fields
- Migration v347 adding repo_manifest table
- Auto-detect and migrate .mokogitea/manifest.xml on first settings visit
- Repo settings UI with Identity/Governance/Build sections
- REST API: GET/PUT /api/v1/repos/{owner}/{repo}/manifest
  for Actions workflows and moko-platform CLI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 09:02:23 -05:00
Jonathan Miller c568e199ed feat(issues): custom status definitions with automated actions (#502)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 27s
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
Add org-level custom issue status definitions that appear in the issue
sidebar. Each status has a name, color, description, and an optional
"closes issue" flag that automatically closes/reopens the issue when
the status is selected.

Includes:
- IssueStatusDef model with CRUD operations
- Migration v346 adding issue_status_def table + status_id on issues
- Org settings UI for managing statuses
- Issue sidebar dropdown for selecting status
- Auto close/reopen when status has closes_issue flag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:24:44 -05:00
jmiller 37ae3c5ec5 chore: add .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-06-06 12:31:54 +00:00
jmiller b9937fabd9 Merge pull request 'feat(ui): tabbed view for root markdown files alongside README (#500)' (#501) from feat/500-root-file-tabs into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
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
2026-06-06 11:50:12 +00:00
Jonathan Miller 9313ae2731 feat(ui): tabbed view for root markdown files alongside README (#500)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
Add tabs above the README rendering on the repo home page for well-known
root files (LICENSE, CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CHANGELOG),
similar to how GitHub surfaces these files for easy discovery.

Tabs only appear when both a README and at least one other well-known file
exist. Clicking a tab renders that file inline, replacing the README.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:41:05 -05:00
Jonathan Miller 2b5a4dd11c chore: resolve changelog conflict after main sync
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
2026-06-06 11:23:29 +00:00
Jonathan Miller 413f7160b3 chore: sync dev with main (v1.26.1-moko.06.04) 2026-06-06 11:21:40 +00:00
gitea-actions[bot] 685d2211a8 chore(release): build 05.47.00 [skip ci] 2026-06-06 11:08:47 +00:00
jmiller e80781fa6e Merge pull request 'release: v1.26.1-moko.06.04' (#498) from rc/v1.26.1-moko.06.04 into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m56s
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
2026-06-06 11:07:57 +00:00
Jonathan Miller 33076e4e1b fix(issues): address review findings for custom field search
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
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Branch Policy Check / Verify merge target (pull_request) Successful in 17s
Generic: Repo Health / Access control (pull_request) Failing after 17s
Universal: PR Check / Validate PR (pull_request) Failing after 22s
PR RC Release / Build RC Release (pull_request) Failing after 35s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
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
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
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Clear custom field filters when field definitions fail to load,
  preventing invisible filtering with no UI to undo
- Log malformed options JSON instead of silently dropping fields
- Extract parseCustomFieldQueryParams helper (web) and
  parseAPICustomFieldFilters helper (API) to deduplicate cf_ parsing
- API returns 400 for non-numeric or non-positive cf_ field IDs
- Validate field IDs are positive in web handler
- Add entity_type='issue' to subquery to prevent cross-entity matching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:06:32 -05:00
Jonathan Miller a3c6f54ad3 feat(issues): advanced search with custom field filters (#496)
Add the ability to filter issues by custom field values throughout
the entire search stack:

- DB: applyCustomFieldCondition joins custom_field_value with AND
  semantics (all specified fields must match)
- Indexer: CustomFieldFilters map passed through SearchOptions and
  ToDBOptions
- Web: parse cf_{fieldID}={value} query params, show dropdown
  filters in the issue list sidebar for org-level fields
- API: both SearchIssues and ListIssues accept cf_ query params

Closes #496

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:06:32 -05:00
jmiller cfdb9b4f0a Merge pull request 'feat(issues): advanced search with custom field filters' (#497) from feat/issue-custom-field-search into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Failing after 12s
PR RC Release / Build RC Release (pull_request) Failing after 21s
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
2026-06-06 04:07:21 +00:00
Jonathan Miller 0109a2db12 fix(issues): address review findings for custom field search
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 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
- Clear custom field filters when field definitions fail to load,
  preventing invisible filtering with no UI to undo
- Log malformed options JSON instead of silently dropping fields
- Extract parseCustomFieldQueryParams helper (web) and
  parseAPICustomFieldFilters helper (API) to deduplicate cf_ parsing
- API returns 400 for non-numeric or non-positive cf_ field IDs
- Validate field IDs are positive in web handler
- Add entity_type='issue' to subquery to prevent cross-entity matching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-05 17:06:18 -05:00
Jonathan Miller ad4451f23c feat(issues): advanced search with custom field filters (#496)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
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
Add the ability to filter issues by custom field values throughout
the entire search stack:

- DB: applyCustomFieldCondition joins custom_field_value with AND
  semantics (all specified fields must match)
- Indexer: CustomFieldFilters map passed through SearchOptions and
  ToDBOptions
- Web: parse cf_{fieldID}={value} query params, show dropdown
  filters in the issue list sidebar for org-level fields
- API: both SearchIssues and ListIssues accept cf_ query params

Closes #496

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-05 00:27:56 -05:00
Jonathan Miller fe6ca172f6 chore: merge main back into dev after v1.26.1-moko.06.03 release
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-05 00:07:28 -05:00
gitea-actions[bot] bcd207cd51 chore(release): build 05.46.00 [skip ci] 2026-06-05 04:05:05 +00:00
jmiller 1913b4c8c2 Merge pull request 'release: v1.26.1-moko.06.03' (#495) from rc/v1.26.1-moko.06.03 into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 23s
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
2026-06-05 04:04:31 +00:00
jmiller 5ca1c888c0 Merge pull request 'feat(custom-fields): template pre-fill + feed generator migration' (#494) from feat/issue-template-custom-fields into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
PR RC Release / Build RC Release (pull_request) Failing after 23s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m22s
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
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
2026-06-05 03:15:16 +00:00
Jonathan Miller 8e0388c9d8 fix(custom-fields): log errors instead of silently discarding them
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
- saveCustomFieldsFromForm: log GetCustomFieldsByOwner errors
- resolveExtensionMetadata: log DB errors on custom field lookup
- NewIssue/ViewIssue: log errors from GetCustomFieldsByOwner and
  GetCustomFieldValuesMap instead of blank-assigning
- Composer: fix misleading comment about override source

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 22:14:10 -05:00
Jonathan Miller cd4c701cb6 fix(custom-fields): address code review findings
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
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
- API: return 500 on GetCustomFieldsByOwner failure instead of silently
  swallowing the error
- resolveExtensionMetadata: add DownloadGating/KeyPrefix to metadata
  struct instead of mutating the caller's cfg pointer (side effect)
- resolveExtensionMetadata: add Description custom field mapping
- Composer: use meta.PHPMinimum instead of bypassing the cascade
- Web form: flash error on custom field save failure instead of silent log

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 21:59:50 -05:00
Jonathan Miller b72f88e78b docs(changelog): add #492 and #493 entries
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Generic: Repo Health / Access control (pull_request) Successful in 37s
PR RC Release / Build RC Release (pull_request) Successful in 43s
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 21:53:04 -05:00
Jonathan Miller 1935889f6b feat(updateserver): resolve extension metadata from custom fields with config fallback
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
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
Add resolveExtensionMetadata() with cascading priority: org-level
repo-scoped custom fields → update_stream_config table → repo-derived
defaults. All six feed generators (Joomla, WordPress, Composer, Drupal,
PrestaShop, WHMCS) now use this unified resolver. Repos can be migrated
to custom fields gradually since the config table remains as fallback.

Ref #492

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 21:48:14 -05:00
Jonathan Miller 9ebe1b26b1 feat(custom-fields): pre-fill custom fields from issue template YAML frontmatter
Add `custom_fields` map to IssueTemplate struct so templates can specify
default values (e.g. `Priority: Medium`). On new issue form, org-level
issue-scoped fields appear in the sidebar with template defaults pre-selected.
NewIssuePost saves the values after issue creation. The API create issue
endpoint also accepts `custom_fields` by name.

Closes #493

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 21:47:15 -05:00
gitea-actions[bot] 1322b5e905 chore(release): build 05.45.00 [skip ci] 2026-06-05 00:55:48 +00:00
jmiller 4d43553c91 Merge pull request 'fix(migration): set issue_id default for custom field API' (#491) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 26s
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
2026-06-05 00:55:09 +00:00
Jonathan Miller 3aec6c2cae fix(migration): set issue_id default to 0 for new custom_field_value inserts
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
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 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 58s
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
The old issue_id column has NOT NULL without a default, causing inserts
via the new entity_id-based API to fail. Migration now ALTERs the
column to DEFAULT 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 19:51:56 -05:00
gitea-actions[bot] 558b36da8c chore(release): build 05.44.00 [skip ci] 2026-06-05 00:21:33 +00:00
jmiller e42214930a Merge pull request 'feat(api): custom fields API endpoints' (#490) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 26s
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
2026-06-05 00:20:54 +00:00
Jonathan Miller 539619be2f feat(api): custom fields API for org definitions, repo metadata, and issue values
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
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 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 55s
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
API endpoints:
- GET/POST/DELETE /api/v1/orgs/{org}/custom-fields — org field definitions
- GET/PUT /api/v1/repos/{owner}/{repo}/metadata — repo-scoped field values
- GET/PUT /api/v1/repos/{owner}/{repo}/issues/{index}/custom-fields — issue values

All endpoints use field names as keys (not IDs) for ergonomic access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 19:20:21 -05:00
gitea-actions[bot] c124527ca2 chore(release): build 05.43.00 [skip ci] 2026-06-05 00:12:58 +00:00
jmiller 8fce854f18 Merge pull request 'feat(custom-fields): org-level definitions with issue and repo scopes' (#489) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 1m36s
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
2026-06-05 00:12:19 +00:00
Jonathan Miller 6bd9548b2a feat(custom-fields): move to org-level definitions with issue and repo scopes
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>
2026-06-04 19:11:22 -05:00
gitea-actions[bot] e1b0c74d24 chore(release): build 05.42.00 [skip ci] 2026-06-04 23:47:00 +00:00
jmiller 96661dcb7c Merge pull request 'fix(updateserver): use client=0 for packages (#482)' (#488) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
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
2026-06-04 23:46:16 +00:00
Jonathan Miller 5665bc545e fix(updateserver): use client=0 for packages to fix Joomla extension matching (#482)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
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 25s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m5s
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
Joomla matches updates to installed extensions via element+type+client_id.
Packages in #__extensions have client_id=0. Omitting <client> caused
Joomla to default to client_id=1, resulting in extension_id=0 in
#__updates and updates not appearing.

Fix: output <client>0</client> for package types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:44:46 -05:00
gitea-actions[bot] 6c4a4ca819 chore(release): build 05.41.00 [skip ci] 2026-06-04 23:37:33 +00:00
jmiller 15188fc0ea Merge pull request 'fix(downloads): signed-in users bypass download gating' (#487) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 3m10s
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
2026-06-04 23:36:54 +00:00
Jonathan Miller df58aacc30 fix(downloads): signed-in users with repo access bypass download gating
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 1m29s
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
Download gating only applies to anonymous/external clients (Joomla
update checker). Users who are signed in and have permission to
access the repo can always download releases regardless of gating.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:35:57 -05:00
gitea-actions[bot] 659f3f2537 chore(release): build 05.40.00 [skip ci] 2026-06-04 23:25:30 +00:00
jmiller 26376b7d11 Merge pull request 'fix(ui): remove package count from Licenses tab' (#486) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 51s
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
2026-06-04 23:24:53 +00:00
Jonathan Miller 6575d3fce2 fix(ui): remove package count badge from Licenses tab
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
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 7s
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 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m19s
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:24:20 -05:00
gitea-actions[bot] b297ea2204 chore(release): build 05.39.00 [skip ci] 2026-06-04 23:19:05 +00:00
jmiller 161ca23836 Merge pull request 'fix(updateserver): derive maintainer from org profile, infourl from support_url' (#485) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m14s
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
2026-06-04 23:18:29 +00:00
Jonathan Miller d553c87a9d fix(updateserver): derive maintainer from org profile, infourl from support_url
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
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 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m2s
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
- Maintainer name: org FullName (from org profile)
- Maintainer URL: org Website (from org profile)
- Info URL: support_url (product page), falls back to releases page
- Removes dependency on separate maintainer/maintainer_url/info_url
  fields in update_stream_config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:17:51 -05:00
gitea-actions[bot] f2ec3d5c02 chore(release): build 05.38.00 [skip ci] 2026-06-04 23:14:43 +00:00
jmiller b3acbc9789 Merge pull request 'fix(licenses): fix key generation modal not passing package_id' (#484) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 1m31s
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
2026-06-04 23:14:08 +00:00
Jonathan Miller 178e8fffe2 fix(licenses): fix key generation modal not passing package_id
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 22s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m3s
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
The generate key modal's hidden package_id input was always empty
because show-modal doesn't wire data attributes to hidden inputs.
Fix: pass package_id via the form action URL query string instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 18:13:10 -05:00
jmiller ac1a726c3b chore: update changelog [skip ci] 2026-06-04 22:52:22 +00:00
Jonathan Miller 2f767e91cb chore: update changelog with today's fixes
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 17:50:58 -05:00
gitea-actions[bot] 5cb5cec7ef chore(release): build 05.37.00 [skip ci] 2026-06-04 22:43:42 +00:00
jmiller 5209dea127 Merge pull request 'fix(licenses): remove master key banner, sort master first' (#481) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 24s
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
2026-06-04 22:43:04 +00:00
Jonathan Miller 5c22bb04b5 fix(licenses): remove master key banner, sort master keys first in list
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
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 24s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m0s
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
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
Remove the dedicated master key segment at the top of the Licenses
page. Master keys now appear first in the keys table via ORDER BY
is_internal DESC.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 17:42:05 -05:00
gitea-actions[bot] 73ec0b52f6 chore(release): build 05.36.00 [skip ci] 2026-06-04 21:45:05 +00:00
jmiller 49299c6a32 Merge pull request 'feat(ui): Update Server tab + hide licenses when no gating' (#480) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 25s
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
2026-06-04 21:44:28 +00:00
Jonathan Miller d6d0d5a11f feat(ui): hide license sections on Update Server page when no gating
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (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
When download_gating is off, the Update Server page only shows feed
URLs. License packages, keys, master key, and modals are hidden since
they're not relevant without download gating.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 16:43:41 -05:00
Jonathan Miller c948696488 feat(ui): show Update Server tab when no gating, Licenses tab when gated
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
- No gating: tab shows as "Update Server" with broadcast icon
- Gated (prerelease/all): tab shows as "Licenses" with key icon and
  package count badge
- Licensing disabled: tab hidden entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 16:38:49 -05:00
gitea-actions[bot] 03b3a10541 chore(release): build 05.35.00 [skip ci] 2026-06-04 21:34:11 +00:00
jmiller 81aab5d9ea Merge pull request 'fix(updateserver): only show downloadkey when downloads are gated' (#479) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 25s
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
2026-06-04 21:33:31 +00:00
Jonathan Miller 635a13d277 fix(updateserver): only show downloadkey when downloads are gated
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 10s
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 26s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m0s
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
Show <downloadkey> only when download_gating is prerelease or all.
No gating means no license keys are needed, so don't prompt for one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 16:33:04 -05:00
gitea-actions[bot] 3a836b69d9 chore(release): build 05.34.00 [skip ci] 2026-06-04 21:29:17 +00:00
jmiller 3e1e179bf0 Merge pull request 'fix(updateserver): always show downloadkey when licensing enabled' (#478) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 24s
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
2026-06-04 21:28:42 +00:00
Jonathan Miller 6be3e5c879 fix(updateserver): always show downloadkey when licensing enabled
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 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 22s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m3s
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
Include <downloadkey prefix="dlid="> in Joomla update XML whenever
Update Server is enabled for the repo, not just when require_key is
set. This ensures Joomla shows the Download Key field in Update Sites
even when downloads are currently public.

Also corrected joomlaTagName comment with Joomla source reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 16:28:14 -05:00
gitea-actions[bot] ed909919f6 chore(release): build 05.33.00 [skip ci] 2026-06-04 21:20:45 +00:00
jmiller e339646067 Merge pull request 'fix(build): restore build/ directory (required for generate-go)' (#477) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m16s
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
2026-06-04 21:20:04 +00:00
Jonathan Miller a86a9afb1a Revert "chore: remove build/ directory from tracking"
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
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 25s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m20s
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
This reverts commit 10e76cf033.
2026-06-04 16:18:13 -05:00
gitea-actions[bot] eaf581071d chore(release): build 05.32.00 [skip ci] 2026-06-04 19:34:12 +00:00
jmiller 7da7e35d89 Merge pull request 'feat(issues): merge custom fields sidebar + joomla tag docs to main' (#476) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 24s
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
2026-06-04 19:33:35 +00:00
Jonathan Miller 5a80b8da33 docs(updateserver): correct joomlaTagName comment with Joomla source reference
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 21s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 58s
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
Joomla's Update.php maps tags via STABILITY_ + strtoupper(tag).
Valid values: dev, alpha, beta, rc, stable. Full names like
"development" silently fall back to STABILITY_STABLE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 14:29:26 -05:00
jmiller 0b21fe859e Merge pull request 'feat(issues): custom fields in issue sidebar' (#473) from feat/custom-fields-sidebar into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
2026-06-04 18:49:41 +00:00
Jonathan Miller 4ec0db8658 feat(issues): show custom fields in issue sidebar with inline editing
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
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
- Load custom field definitions and values in ViewIssue handler
- New sidebar template displays dropdown fields with onchange submit
- POST handler at /issues/{id}/custom-fields/{field_id} saves values
- Dropdown options parsed from JSON and passed to template
- Non-dropdown fields display as read-only text
- Section appears between Labels and Milestone in sidebar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 13:48:22 -05:00
Jonathan Miller 10e76cf033 chore: remove build/ directory from tracking
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
Build artifacts are created by CI, not tracked in source.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 13:33:35 -05:00
jmiller 45af52611c chore: consolidate changelog to minor versions [skip ci] 2026-06-04 18:07:45 +00:00
jmiller a5bda0f9a6 Merge pull request 'chore: consolidate changelog to minor versions' (#471) from chore/changelog-consolidation into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
PR RC Release / Build RC Release (pull_request) Failing after 20s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
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
2026-06-04 17:51:05 +00:00
Jonathan Miller a053126bd9 chore: consolidate changelog to minor version numbers only
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
Merge patch-level entries into their minor version, drop .00 suffixes,
and add today's Update Server and license UI fixes to moko.06.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:50:01 -05:00
gitea-actions[bot] 9fff67ab57 chore(release): build 05.31.00 [skip ci] 2026-06-04 17:25:06 +00:00
jmiller 3eb649a1a6 Merge pull request 'fix(updateserver): merge version fix to main' (#470) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 28s
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
2026-06-04 17:24:24 +00:00
jmiller f091f4cab3 Merge pull request 'fix(updateserver): version from asset filename takes priority' (#469) from fix/update-xml-version-priority into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
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 1m5s
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
2026-06-04 17:24:12 +00:00
Jonathan Miller 0de02fdce5 fix(updateserver): prevent stream name tag from overriding asset-derived version
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
When the tag is a stream name (e.g. "release-candidate"), the version
extracted from the asset filename was being overwritten by the release
title version. Remove the isStreamName check since the priority chain
(filename -> tag -> title) already handles this correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:22:40 -05:00
gitea-actions[bot] 06f8ab3d1a chore(release): build 05.30.00 [skip ci] 2026-06-04 17:16:24 +00:00
jmiller 3918e8ef9a Merge pull request 'fix(updateserver): merge XML fixes to main' (#468) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m16s
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
2026-06-04 17:15:48 +00:00
jmiller 480aaa088a Merge pull request 'fix(updateserver): extract version from asset filename, omit client for packages' (#467) from fix/joomla-update-xml into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
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 22s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m3s
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
2026-06-04 17:15:29 +00:00
Jonathan Miller f0aa2c3034 fix(updateserver): extract version from asset filename, omit client for packages
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 0s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
- Version now extracted from the zip asset filename first (most
  accurate), falling back to tag name then release title. Fixes
  mismatch where title version was updated but asset was stale.
- Omit <client> element for package extension types (packages manage
  their own sub-extension clients per Joomla spec).
- Make Client field omitempty so empty string doesn't render empty tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 12:14:29 -05:00
jmiller 01d38e13f9 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:58:28 +00:00
jmiller 04338fe159 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:56:32 +00:00
jmiller 0389410efc chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:40:57 +00:00
jmiller ddababa6fa chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:38:59 +00:00
jmiller 3e156e8307 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:32:19 +00:00
jmiller 546245c9bb chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:29:32 +00:00
jmiller 26bb906a96 chore: remove updates.xml [skip ci] 2026-06-04 15:27:32 +00:00
jmiller 6509bd1eb7 chore: remove updates.xml [skip ci] 2026-06-04 15:27:25 +00:00
jmiller 6ac7c0c774 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:19:07 +00:00
jmiller 53c86c9b17 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:14:04 +00:00
jmiller 4a687a9438 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-06-04 14:23:31 +00:00
jmiller 0187f9814f chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-06-04 14:20:43 +00:00
gitea-actions[bot] 92ca601aa6 chore: update channels for 05.29.00 [skip ci] 2026-06-04 14:19:43 +00:00
gitea-actions[bot] e866d16ee6 chore(release): build 05.29.00 [skip ci] 2026-06-04 14:19:07 +00:00
jmiller 5a1772b026 Merge pull request 'fix(ui): merge Update Server rename to main' (#466) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
Deploy MokoGitea / deploy (push) Failing after 28s
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
2026-06-04 14:18:24 +00:00
jmiller 969015a87a Merge pull request 'fix(ui): rename Licensing to Update Server across settings' (#465) from fix/rename-licensing-to-update-server into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
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 27s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m11s
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
2026-06-04 14:17:56 +00:00
Jonathan Miller ee20006b15 fix(ui): rename "Licensing" to "Update Server" across settings UI
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Generic: Repo Health / Access control (push) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
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
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
The update server system works without license keys — the primary
feature is serving update feeds. Rename all UI labels from "Licensing"
to "Update Server" to reflect this. License key management remains
under the Licenses tab.

- Repo settings nav: "Licensing & Updates" -> "Update Server"
- Advanced settings toggle: "Enable licensing" -> "Enable Update Server"
- Org settings nav: "Licensing & Update Streams" -> "Update Server"
- Icon changed from octicon-key to octicon-broadcast
- Updated help text to emphasize update feeds over licensing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 09:16:49 -05:00
jmiller ce38bab2cf chore: sync updates.xml 05.28.00 from main [skip ci] 2026-06-04 14:08:45 +00:00
gitea-actions[bot] aba8021344 chore: update channels for 05.28.00 [skip ci] 2026-06-04 14:08:44 +00:00
gitea-actions[bot] 7cbbfb7505 chore(release): build 05.28.00 [skip ci] 2026-06-04 14:08:10 +00:00
jmiller 7f45e98630 Merge pull request 'feat(licenses): merge domain restriction to main' (#464) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 23s
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
2026-06-04 14:07:38 +00:00
jmiller 74194f4283 Merge pull request 'feat(licenses): add domain restriction to packages and key generation' (#463) from feat/license-domain-restriction into dev
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Repo Health / Site Health (push) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 27s
Generic: Repo Health / Access control (push) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m35s
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
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
2026-06-04 14:06:57 +00:00
Jonathan Miller e4ea1303ea feat(licenses): add domain restriction to packages and key generation
Generic: Repo Health / Site Health (push) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
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
- Add DomainRestriction field to LicensePackage model with migration
- Packages can define default allowed domains (comma-separated)
- Key generation form now includes licensee name, email, and domain
  fields in a proper modal instead of a tiny inline form
- Keys inherit domain restriction from their package if not overridden
- Package create/edit forms include domain restriction input
- Domain enforcement already exists in heartbeat validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 09:05:44 -05:00
jmiller 7d6cc3152d chore: sync updates.xml 05.27.00 from main [skip ci] 2026-06-04 13:50:31 +00:00
gitea-actions[bot] 42b0ff182c chore: update channels for 05.27.00 [skip ci] 2026-06-04 13:50:26 +00:00
gitea-actions[bot] 32d5a292c7 chore(release): build 05.27.00 [skip ci] 2026-06-04 13:49:44 +00:00
jmiller 33ba1159c3 Merge pull request 'fix(licenses): merge license UI fixes to main' (#462) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 58s
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
2026-06-04 13:49:05 +00:00
jmiller cd45824a0d Merge pull request 'fix(licenses): fix master key visibility and package creation at repo level' (#461) from fix/licenses-ui into dev
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 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 24s
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m13s
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
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
2026-06-04 13:48:50 +00:00
Jonathan Miller bab1acdfe3 fix(licenses): fix master key visibility, package creation, and template structure
Generic: Repo Health / Site Health (push) Has been skipped
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 (push) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
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 (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) 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
- Always show master key prefix and status in a dedicated segment with
  a Regenerate button that deactivates the old key and creates a new one
- Fix broken <details> structure where </details> was inside an {{if}}
  block, causing malformed HTML
- Move create package form into a proper modal instead of a broken
  details/summary toggle
- Add copy button for all key prefixes (not just full keys)
- Add POST /licenses/master-key/regenerate route and handler
- Add locale keys for regenerate master key feature

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 08:47:11 -05:00
gitea-actions[bot] e59837b250 chore: update channels for 05.26.00 [skip ci] 2026-06-04 13:11:23 +00:00
gitea-actions[bot] 5c1e1cc8cc chore(release): build 05.26.00 [skip ci] 2026-06-04 13:10:51 +00:00
jmiller 6ea5dd37aa Merge pull request 'fix(settings): merge advanced settings UI to main' (#459) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 24s
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
2026-06-04 13:10:14 +00:00
jmiller 099f30d05b Merge pull request 'fix(settings): add licensing toggle to Advanced Settings, clean up UI' (#458) from fix/advanced-settings-ui into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
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 25s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m5s
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
2026-06-04 13:09:59 +00:00
Jonathan Miller cbaf289657 fix(settings): add licensing toggle to Advanced Settings, clean up UI
Generic: Repo Health / Site Health (push) 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 (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
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) Successful in 2s
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 (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) 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
- Add licensing enable/disable checkbox to Advanced Settings page so
  users can toggle licensing without needing the separate Licensing
  settings page
- Restore LicensingEnabled gate on Licensing nav item (shows only when
  licensing is enabled, as intended)
- Replace inline dividers with Fomantic UI dividing headers for cleaner
  visual section separation
- Fix redirect after saving advanced settings to go back to /advanced
  instead of /settings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 08:09:23 -05:00
gitea-actions[bot] 619295f469 chore: update channels for 05.25.00 [skip ci] 2026-06-04 13:03:07 +00:00
gitea-actions[bot] eced91be74 chore(release): build 05.25.00 [skip ci] 2026-06-04 13:02:37 +00:00
jmiller 902e3b5edd Merge pull request 'fix(settings): merge licensing nav fix to main' (#457) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 26s
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
2026-06-04 13:01:56 +00:00
jmiller 95d4259a26 Merge pull request 'fix(settings): always show Licensing nav item in repo settings' (#456) from fix/licensing-nav-always-visible into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 21s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m2s
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
2026-06-04 13:01:10 +00:00
Jonathan Miller 64dc6f28fa fix(settings): always show Licensing nav item in repo settings
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
The Licensing nav item was gated behind LicensingEnabled, creating a
chicken-and-egg problem: users couldn't reach the settings page to
enable licensing because the nav link was hidden until licensing was
already enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:58:17 -05:00
jmiller cea3418894 chore: sync updates.xml 05.24.00 from main [skip ci] 2026-06-04 12:54:29 +00:00
gitea-actions[bot] b6671ee1f9 chore: update channels for 05.24.00 [skip ci] 2026-06-04 12:54:28 +00:00
gitea-actions[bot] a52835b8ee chore(release): build 05.24.00 [skip ci] 2026-06-04 12:53:56 +00:00
jmiller c64bafbe80 Merge pull request 'fix(settings): merge nav highlight fix to main' (#455) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m0s
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
2026-06-04 12:53:22 +00:00
jmiller e088589af7 Merge pull request 'fix(settings): prevent double-highlight on Advanced Settings nav item' (#454) from fix/advanced-settings-nav-highlight into dev
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 2s
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) Failing after 9s
Generic: Repo Health / Site Health (push) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 26s
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m11s
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
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
2026-06-04 12:52:51 +00:00
Jonathan Miller c90edc3efc fix(settings): prevent double-highlight on Advanced Settings nav item
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
SettingsCtxData middleware sets PageIsSettingsOptions=true for all
settings routes including /advanced. The advanced handler now explicitly
clears it so only the Advanced Settings nav item is highlighted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:50:41 -05:00
jmiller dda0f6d4ed chore: sync updates.xml 05.23.00 from main [skip ci] 2026-06-04 12:40:18 +00:00
gitea-actions[bot] 21fb789d3c chore: update channels for 05.23.00 [skip ci] 2026-06-04 12:40:16 +00:00
gitea-actions[bot] 558bf37fce chore(release): build 05.23.00 [skip ci] 2026-06-04 12:39:40 +00:00
jmiller 746f1a5a50 Merge pull request 'fix(build): merge UTF-8 fix to main' (#453) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 3m26s
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
2026-06-04 12:39:06 +00:00
jmiller 9cceb5da0b Merge pull request 'fix(build): replace invalid UTF-8 character in API comment' (#452) from fix/utf8-comment into dev
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
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 22s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 2m33s
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
2026-06-04 12:38:19 +00:00
Jonathan Miller fb5002d317 fix(build): replace invalid UTF-8 character in API comment
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
The em dash in the TODO comment was encoded as invalid UTF-8, causing
a Go compiler error. Replace with ASCII hyphen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:37:42 -05:00
jmiller f2482a712c chore: sync updates.xml 05.22.00 from main [skip ci] 2026-06-04 12:31:29 +00:00
gitea-actions[bot] 349a326881 chore: update channels for 05.22.00 [skip ci] 2026-06-04 12:31:28 +00:00
gitea-actions[bot] 7dc598104b chore(release): build 05.22.00 [skip ci] 2026-06-04 12:31:01 +00:00
jmiller 402166589b Merge pull request 'fix(build): merge custom fields build fix to main' (#451) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 22s
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
2026-06-04 12:30:27 +00:00
jmiller 6c9a26ebd3 Merge pull request 'fix(build): remove stale custom field API routes and dead code' (#450) from fix/custom-fields-build into dev
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 2s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
PR RC Release / Build RC Release (pull_request) Successful in 21s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 5m27s
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
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
2026-06-04 12:28:18 +00:00
Jonathan Miller c15582aa64 fix(build): remove stale custom field API routes, structs, and migration
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
PR RC Release / Build RC Release (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 4s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
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
Comment out custom-fields API routes in api.go that referenced handler
functions from the deleted routers/api/v1/repo/custom_field.go. Remove
the unreferenced modules/structs/custom_field.go and the duplicate
v1_25/v323 migration (superseded by v1_27/v343).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:26:16 -05:00
jmiller 03dee5af39 chore: sync updates.xml 05.21.00 from main [skip ci] 2026-06-04 12:05:07 +00:00
gitea-actions[bot] 2827fa0a4c chore: update channels for 05.21.00 [skip ci] 2026-06-04 12:05:05 +00:00
gitea-actions[bot] 979d6f5964 chore(release): build 05.21.00 [skip ci] 2026-06-04 12:04:33 +00:00
jmiller 396220368f Merge pull request 'fix(build): remove stale custom field API' (#449) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 37s
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
2026-06-04 12:04:01 +00:00
Jonathan Miller 877f39d4f4 fix(build): remove stale custom field API file from prior session
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
PR RC Release / Build RC Release (pull_request) Successful in 21s
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 6m5s
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
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
The old API file references non-existent struct names and fields.
Will be rebuilt properly after the web UI is complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 07:02:40 -05:00
jmiller 0492ea399e chore: sync updates.xml 05.20.00 from main [skip ci] 2026-06-04 11:57:57 +00:00
gitea-actions[bot] 53b2d5b754 chore: update channels for 05.20.00 [skip ci] 2026-06-04 11:55:24 +00:00
gitea-actions[bot] 5db84e3932 chore(release): build 05.20.00 [skip ci] 2026-06-04 11:54:44 +00:00
jmiller 02cb4ae1a1 Merge pull request 'fix(build): custom field API function names' (#448) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m13s
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
2026-06-04 11:54:05 +00:00
Jonathan Miller 75f05e1c80 fix(build): update custom field API to match renamed model functions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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) Successful in 27s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 6m17s
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
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 06:52:56 -05:00
jmiller 23eb52cafb chore: sync updates.xml 05.19.00 from main [skip ci] 2026-06-04 11:50:10 +00:00
gitea-actions[bot] 7f2aaa84bd chore: update channels for 05.19.00 [skip ci] 2026-06-04 11:50:07 +00:00
gitea-actions[bot] 6f16459e13 chore(release): build 05.19.00 [skip ci] 2026-06-04 11:49:23 +00:00
jmiller 5cf91a12bc Merge pull request 'feat(issues): custom fields foundation' (#447) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 25s
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
2026-06-04 11:48:39 +00:00
Jonathan Miller c7d8f6066f feat(issues): custom fields foundation — model, migration, settings UI (#8)
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 2s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
PR RC Release / Build RC Release (pull_request) Failing after 19s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 6m35s
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-04 06:46:33 -05:00
jmiller 6b0ec5196a chore: sync .mokogitea/workflows/repo-health.yml from moko-platform [skip ci] 2026-06-03 09:37:15 +00:00
jmiller 8741096fb4 chore: sync .mokogitea/workflows/repo-health.yml from moko-platform [skip ci] 2026-06-03 03:11:01 +00:00
gitea-actions[bot] 0ab3b7dbd7 chore: update channels for 05.18.00 [skip ci] 2026-06-03 03:00:25 +00:00
gitea-actions[bot] 02495327ee chore(release): build 05.18.00 [skip ci] 2026-06-03 02:59:47 +00:00
jmiller 6f5c40716d Merge pull request 'fix(updates): default Joomla target to 5/6, correct URL mapping' (#446) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 5m19s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-03 02:59:09 +00:00
Jonathan Miller e5aa0c343d fix(updates): default Joomla target version to 5/6
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 7s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 25s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 4m31s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (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
Generic: Repo Health / Release configuration (push) Has been cancelled
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 21:57:03 -05:00
jmiller ab3a65abdf chore: sync updates.xml 05.17.00 from main [skip ci] 2026-06-03 02:56:36 +00:00
gitea-actions[bot] 1eff03ab21 chore: update channels for 05.17.00 [skip ci] 2026-06-03 02:56:34 +00:00
gitea-actions[bot] e3e2cb4543 chore(release): build 05.17.00 [skip ci] 2026-06-03 02:56:05 +00:00
jmiller a15139f70b Merge pull request 'fix(updates): correct infourl/maintainerurl mapping' (#445) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 22s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-03 02:55:31 +00:00
Jonathan Miller ba0d180e39 fix(updates): correct infourl/maintainerurl mapping
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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
PR RC Release / Build RC Release (pull_request) Failing after 19s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 6m52s
Generic: Repo Health / Release configuration (push) Has been cancelled
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 / Release configuration (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
<infourl> = InfoURL field (product/release info page), fallback /releases
<maintainerurl> = SupportURL field (support site), fallback MaintainerURL, fallback org profile

Previously SupportURL was mapped to <infourl> which was wrong.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 21:54:01 -05:00
gitea-actions[bot] abf961dd1e chore: update channels for 05.16.00 [skip ci] 2026-06-03 01:33:13 +00:00
gitea-actions[bot] b34381e8da chore(release): build 05.16.00 [skip ci] 2026-06-03 01:32:42 +00:00
jmiller f9653411a7 Merge pull request 'docs: CHANGELOG and wiki update for v1.26.1-moko.06.02.00 final' (#444) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 23s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-03 01:32:08 +00:00
Jonathan Miller 44107d6485 docs: update CHANGELOG and wiki for v1.26.1-moko.06.02.00 final
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
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 7s
PR RC Release / Build RC Release (pull_request) Failing after 21s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m6s
Generic: Repo Health / Release configuration (push) Has been cancelled
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 / Release configuration (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
Changelog: comprehensive entry covering all features, security
fixes, platform feeds, UI changes, and settings restructure.

Wiki: all 7 platform feeds now listed as Production. Revision 1.5
added covering sub-orgs, visibility modes, settings pages, and
security hardening.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 20:31:06 -05:00
jmiller 82c3c11053 chore: sync updates.xml 05.15.00 from main [skip ci] 2026-06-03 00:15:06 +00:00
gitea-actions[bot] cd2e8b4d34 chore: update channels for 05.15.00 [skip ci] 2026-06-03 00:15:04 +00:00
gitea-actions[bot] acf9b4a4da chore(release): build 05.15.00 [skip ci] 2026-06-03 00:14:33 +00:00
jmiller 23af404ae4 Merge pull request 'fix(licenses): explicit xorm column names for UpdateStreamConfig' (#443) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 21s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-03 00:14:01 +00:00
Jonathan Miller ff6d1bf3c9 fix(licenses): add explicit xorm column names for all UpdateStreamConfig fields
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 19s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 1m23s
Generic: Repo Health / Release configuration (push) Has been cancelled
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 / Release configuration (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
xorm auto-maps CamelCase to snake_case by splitting on each
uppercase letter. MaintainerURL became maintainer_u_r_l instead
of maintainer_url, causing DB reads to return empty values.

Added explicit column name tags to all multi-word fields:
SupportURL, KeyPrefix, ExtensionName, DisplayName, ExtensionType,
MaintainerURL, InfoURL, TargetVersion, PHPMinimum, LicensingEnabled,
RequireKey, FeedVisibility, DownloadGating, StreamMode, CustomStreams.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 19:12:54 -05:00
jmiller a5b4f24b48 Merge pull request 'feat(licenses): ancestor-aware org license handler' (#442) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 3m3s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 23:47:38 +00:00
jmiller 9832f8a7bb chore: add .mokogitea/workflows/auto-release.yml from moko-platform [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
2026-06-02 23:47:37 +00:00
Jonathan Miller 9506a19ab8 feat(licenses): use ancestor-aware functions in org license handler
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 5s
Generic: Repo Health / Site Health (push) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 20s
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (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
Generic: Repo Health / Release configuration (push) Has been cancelled
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
Org licenses handler now uses ListLicensePackagesWithAncestors,
ListLicenseKeysWithAncestors, and SearchLicenseKeysWithAncestors
to show packages and keys from parent orgs in the hierarchy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 18:46:33 -05:00
jmiller f485f14615 Merge pull request 'fix(ui): icons on user settings navbar' (#441) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Deploy MokoGitea / deploy (push) Failing after 1m1s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 22:32:18 +00:00
Jonathan Miller d0e3b3dfd8 fix(ui): add octicon icons to user settings navbar
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 22s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (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
Generic: Repo Health / Release configuration (push) Has been cancelled
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
Profile (person), Account (shield-lock), Notifications (bell),
Appearance (paintbrush), Security (lock), Blocked Users (blocked),
Applications (apps), SSH/GPG Keys (key), Actions (play),
Packages (package), Webhooks (webhook), Organizations (organization),
Repositories (repo).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-02 17:10:45 -05:00
jmiller 3231ac2707 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-02 21:51:52 +00:00
jmiller b2d2a3b622 Merge pull request 'fix(licenses): allow anonymous download paths on licensed repos' (#440) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Deploy MokoGitea / deploy (push) Failing after 1m11s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 20:51:12 +00:00
jmiller ba9907ba41 Merge pull request 'fix(updates): feed always public, downloads gated separately' (#439) from dev into main
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Deploy MokoGitea / deploy (push) Failing after 25s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 20:46:14 +00:00
Moko Consulting b1b64a3b4e chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Access control (push) Successful in 3s
Generic: Repo Health / Site Health (push) Has been skipped
Deploy MokoGitea / deploy (push) Failing after 27s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 20:37:36 +00:00
Moko Consulting 3cddb46053 chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Deploy MokoGitea / deploy (push) Failing after 29s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 20:37:28 +00:00
Moko Consulting 0a0cc16528 chore(ci): add CI issue reporter for auto-filing gate failures
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Repo Health / Site Health (push) Has been skipped
Deploy MokoGitea / deploy (push) Failing after 29s
Generic: Repo Health / Release configuration (push) Has been cancelled
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
2026-06-02 20:37:17 +00:00
jmiller da41d7072f Merge pull request 'fix(licenses): restrict downloadsPublic to release paths only' (#438) from dev into main
Deploy MokoGitea / deploy (push) Failing after 17s
2026-06-02 20:31:27 +00:00
jmiller cb3817f5bc Merge pull request 'fix(licenses): allow anonymous downloads when download_gating=none' (#437) from dev into main
Deploy MokoGitea / deploy (push) Failing after 47s
2026-06-02 20:26:46 +00:00
jmiller f657f58fbb Merge pull request 'fix(ui): octicon-settings to octicon-gear' (#436) from dev into main
Deploy MokoGitea / deploy (push) Failing after 41s
2026-06-02 19:47:07 +00:00
jmiller 1709566fa6 Merge pull request 'fix(ui): section headers with dividers, icons on all settings navbar items' (#435) from dev into main
Deploy MokoGitea / deploy (push) Failing after 1m23s
2026-06-02 19:34:36 +00:00
jmiller 95c136d838 Merge pull request 'feat(settings): dedicated advanced settings page at /settings/advanced' (#434) from dev into main
Deploy MokoGitea / deploy (push) Failing after 17s
2026-06-02 19:25:37 +00:00
jmiller 48f32ae961 Merge pull request 'feat(settings): accordion layout for advanced settings' (#433) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m1s
2026-06-02 19:14:08 +00:00
jmiller dce87fcb5d Merge pull request 'feat(settings): licensing settings page + navbar restructure' (#432) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m40s
2026-06-02 19:03:52 +00:00
jmiller 7004170d64 Merge pull request 'fix(ui): login form on 403 page + visibility badge right-aligned' (#431) from dev into main
Deploy MokoGitea / deploy (push) Failing after 19s
2026-06-02 18:56:17 +00:00
jmiller c045c6abfc Merge pull request 'fix(ui): visibility badge floated right of title' (#430) from dev into main
Deploy MokoGitea / deploy (push) Failing after 22s
2026-06-02 18:51:55 +00:00
jmiller ce35e3a603 Merge pull request 'fix(build): UpdateRepositoryColsWithAutoTime' (#429) from dev into main
Deploy MokoGitea / deploy (push) Failing after 22s
2026-06-02 18:46:56 +00:00
jmiller 3e4cb4d2e5 Merge pull request 'feat(repos): three-level visibility Public/Private/Hidden' (#428) from dev into main
Deploy MokoGitea / deploy (push) Failing after 23s
2026-06-02 18:44:15 +00:00
jmiller ba361c609f Merge pull request 'fix(licenses): RequireUnitReader allows LicensedReadOnly' (#427) from dev into main
Deploy MokoGitea / deploy (push) Failing after 21s
2026-06-02 15:45:48 +00:00
jmiller 7aaf8dcbb7 Merge pull request 'fix(licenses): bypass attachment perm check for licensed downloads' (#426) from dev into main
Deploy MokoGitea / deploy (push) Failing after 26s
2026-06-02 15:17:40 +00:00
jmiller 128b120ad9 Merge pull request 'fix(licenses): allow downloads on private repos with license key' (#425) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m3s
2026-06-02 15:13:47 +00:00
jmiller 3f817babd3 Merge pull request 'fix(ui): styled 403 Access Denied page matching 404 layout' (#424) from dev into main
Deploy MokoGitea / deploy (push) Failing after 20s
2026-06-02 15:06:02 +00:00
jmiller 6290ff07e4 Merge pull request 'fix(security): 403 for all users on private repos' (#423) from dev into main
Deploy MokoGitea / deploy (push) Failing after 1m1s
2026-06-02 14:57:50 +00:00
jmiller c4e51ff55c Merge pull request 'fix(licenses): licensed private repos allow release viewing for signed-in users' (#422) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m10s
2026-06-02 14:52:29 +00:00
jmiller b707c5aff9 Merge pull request 'fix(updates): allow update feeds on private repos' (#421) from dev into main
Deploy MokoGitea / deploy (push) Failing after 2m48s
2026-06-02 14:37:42 +00:00
jmiller 2db1f4eaf6 Merge pull request 'fix(security): 403 Access Denied for signed-in users on private repos' (#420) from dev into main
Deploy MokoGitea / deploy (push) Failing after 1m10s
2026-06-02 14:27:19 +00:00
jmiller 25499fb183 Merge pull request 'fix(build): unused import in drupal.go' (#419) from dev into main
Deploy MokoGitea / deploy (push) Failing after 21s
2026-06-02 14:10:45 +00:00
jmiller 7c15301228 Merge pull request 'feat(updates): PrestaShop, Drupal, WHMCS update feeds (#352, #353, #355)' (#418) from dev into main
Deploy MokoGitea / deploy (push) Failing after 21s
2026-06-02 14:08:33 +00:00
jmiller e4718f5036 Merge pull request 'feat(updates): Composer feed (#354), hide Actions/Licenses tabs for guests' (#417) from dev into main
Deploy MokoGitea / deploy (push) Failing after 2m59s
2026-06-02 14:02:23 +00:00
jmiller 581bfa5f31 Merge pull request 'feat(licenses): key prefix (#406), header button (#408), open feed (#409)' (#416) from dev into main
Deploy MokoGitea / deploy (push) Failing after 18s
2026-06-02 13:52:27 +00:00
jmiller 8ae663e15e Merge pull request 'SECURITY: fix release download gating and require login for actions' (#415) from dev into main
Deploy MokoGitea / deploy (push) Failing after 1m11s
2026-06-02 13:41:10 +00:00
jmiller 4bc962adbf Merge pull request 'fix(build): permanent fixes for recurring build errors' (#414) from dev into main
Deploy MokoGitea / deploy (push) Failing after 20s
2026-06-02 13:35:03 +00:00
jmiller ca841716db Merge pull request 'SECURITY: require login for licenses page' (#413) from dev into main
Deploy MokoGitea / deploy (push) Failing after 25s
2026-06-02 13:26:17 +00:00
jmiller 117daf51c3 Merge pull request 'fix(build): org list API and unused import' (#412) from dev into main
Deploy MokoGitea / deploy (push) Failing after 23s
2026-06-02 13:22:10 +00:00
jmiller a2e0735a26 Merge pull request 'feat(orgs): enterprise sub-org hierarchy (#410)' (#411) from dev into main
Deploy MokoGitea / deploy (push) Failing after 1m18s
2026-06-02 13:15:30 +00:00
jmiller 1a46a8f14f Merge pull request 'fix(build): EditReleaseForm UpdateStream field' (#405) from dev into main
Deploy MokoGitea / deploy (push) Failing after 19s
2026-06-02 12:56:36 +00:00
jmiller b18519e8b9 Merge pull request 'fix(build): pass ctx to WordPress changelog builder' (#404) from dev into main
Deploy MokoGitea / deploy (push) Failing after 1m32s
2026-06-02 12:48:26 +00:00
jmiller 94649efed0 Merge pull request 'feat(updates): manual stream mapping, version extraction fixes, feed visibility' (#403) from dev into main
Deploy MokoGitea / deploy (push) Failing after 22s
2026-06-02 12:43:29 +00:00
jmiller a52ac1bf61 Merge pull request 'feat(licenses): full commercial license management system v1.26.1-moko.06.02.00' (#402) from dev into main
Deploy MokoGitea / deploy (push) Failing after 19s
feat(licenses): full commercial license management system v1.26.1-moko.06.02.00 (#402)
2026-06-02 12:00:19 +00:00
jmiller 5da4b3b314 Merge pull request 'fix(build): remove unused imports' (#377) from dev into main
Deploy MokoGitea / deploy (push) Failing after 21s
2026-05-31 18:51:42 +00:00
jmiller 75e2a21b89 Merge pull request 'chore: merge dev into main — Issue.Ref deprecation, stale TODO cleanup' (#376) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m45s
2026-05-31 18:39:57 +00:00
jmiller e82fe7d021 Merge pull request 'fix(cron): add missing translation for cleanup_expired_license_keys' (#375) from dev into main
Deploy MokoGitea / deploy (push) Failing after 21s
2026-05-31 18:34:46 +00:00
jmiller 24a9bfb30d Merge pull request 'fix(docker): disable openssh s6 service in Dockerfile' (#374) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m30s
2026-05-31 17:14:36 +00:00
jmiller 257908e083 Merge pull request 'chore: merge dev into main — tech-debt, namespace migration, combo-multiselect' (#373) from dev into main
Deploy MokoGitea / deploy (push) Failing after 6m39s
2026-05-31 17:12:39 +00:00
jmiller 2c3aad51af Merge pull request 'fix(build): Go 1.23 maps.Values slices.Collect' (#371) from dev into main
Deploy MokoGitea / deploy (push) Failing after 25s
2026-05-31 16:38:42 +00:00
jmiller 66a6a2afc1 Merge pull request 'fix(build): Go 1.23 maps.Values compatibility' (#370) from dev into main
Deploy MokoGitea / deploy (push) Failing after 22s
2026-05-31 16:31:39 +00:00
jmiller 74935e3bed Merge pull request 'fix(licenses): remove duplicate DeleteLicenseKey (build fix)' (#358) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m39s
2026-05-31 16:07:13 +00:00
jmiller bc95ecf4d5 Merge pull request 'feat(updates): extension metadata settings, tab visibility, platform support' (#356) from dev into main
Deploy MokoGitea / deploy (push) Failing after 28s
2026-05-31 16:01:49 +00:00
jmiller a35fb4695c Merge pull request 'chore: sync dev to main (namespace rename + all fixes)' (#348) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m18s
2026-05-31 15:40:34 +00:00
jmiller 70c31a4953 Merge pull request 'fix(updates): correct dlid prefix and Joomla standard alignment' (#345) from dev into main
Deploy MokoGitea / deploy (push) Failing after 2m32s
2026-05-31 15:31:09 +00:00
jmiller 6c913abbda Merge pull request 'feat(licenses): plaintext key storage with copy buttons' (#342) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m35s
2026-05-31 15:08:02 +00:00
jmiller 878671ebc9 Merge pull request 'feat(licenses): platform enforcement, key deletion, expired key cleanup' (#340) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m4s
2026-05-31 15:03:46 +00:00
jmiller c7cfcf894b Merge pull request 'fix(licenses): remove repo unit requirement causing 404s' (#339) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m35s
2026-05-31 14:48:30 +00:00
jmiller bbe3e570fe Merge pull request 'chore: migrate namespace from git. to code.mokoconsulting.tech' (#337) from chore/namespace-migration into main
Deploy MokoGitea / deploy (push) Failing after 4m27s
2026-05-31 14:46:21 +00:00
Jonathan Miller 26bbe690fd chore: migrate namespace from git. to code.mokoconsulting.tech (#336)
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 7s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 28s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Update all URLs in manifest.xml and updates.xml to use the new
code.mokoconsulting.tech domain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 09:45:58 -05:00
jmiller bfa9043bc8 Merge pull request 'feat(licenses): UI/UX cleanup, permissions system, and key management improvements' (#306) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m35s
2026-05-31 14:22:04 +00:00
jmiller b1a9b09f5b Merge pull request 'chore: merge dev into main — toggle fix' (#295) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m59s
2026-05-31 04:22:47 +00:00
144 changed files with 11620 additions and 1411 deletions
+42
View File
@@ -0,0 +1,42 @@
# MokoGitea
Fork of Gitea -- self-hosted Git service at git.mokoconsulting.tech. Go backend + TypeScript frontend.
## Quick Reference
| Field | Value |
|---|---|
| **Language** | Go 1.26+ / TypeScript |
| **Module** | `code.mokoconsulting.tech/MokoConsulting/MokoGitea` |
| **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoGitea Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea/wiki) |
## Commands
```bash
make help # List all available targets
make fmt # Format .go files
make lint-go # Lint Go code
make lint-js # Lint TypeScript
make tidy # After go.mod changes
make build # Build binary
# Testing
go test -run '^TestName$' ./modulepath/ # Single Go test
pnpm exec vitest <path-filter> # Single JS test
GITEA_TEST_E2E_FLAGS='<filepath>' make test-e2e # Single Playwright test
```
## Rules
- Add current year copyright header on new `.go` files
- No trailing whitespace in edited files
- Conventional Commits for commit messages and PR titles
- Never force-push, amend, or squash unless asked -- use new commits
- Preserve existing code comments
- TypeScript: use `!` (non-null assertion) not `?.`/`??` when value is known to exist
- CSS: prefer `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins
- Add `Co-Authored-By` lines to all commits
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
- **Attribution**: `Authored-by: Moko Consulting`
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
+2 -2
View File
@@ -3,8 +3,8 @@
<identity>
<name>MokoGitea</name>
<org>MokoConsulting</org>
<description>Moko fork of Gitea adding project board REST API endpoints and custom enhancements</description>
<version>05.14.00</version>
<description>Moko fork of Gitea -- adding project board REST API endpoints and custom enhancements</description>
<version>05.47.00</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+129
View File
@@ -0,0 +1,129 @@
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
DEFGROUP: gitea-api-mcp.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
-->
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- **Renamed** package from `@mokoconsulting/gitea-api-mcp` to `@mokoconsulting/mokogitea-api-mcp` to distinguish Moko's forked Gitea MCP from upstream
- **Renamed** McpServer name and bin entry to `mokogitea-api-mcp`
## [0.0] - 2026-05-07
### Added
#### User / Auth (3 tools)
- `gitea_me` -- Get the authenticated user info
- `gitea_user_orgs` -- List organizations the authenticated user belongs to
- `gitea_user_repos` -- List repositories owned by the authenticated user
#### Repositories (8 tools)
- `gitea_repo_get` -- Get repository details
- `gitea_repo_create` -- Create a new repository
- `gitea_repo_delete` -- Delete a repository
- `gitea_repo_edit` -- Edit repository settings
- `gitea_repo_fork` -- Fork a repository
- `gitea_repo_search` -- Search repositories
- `gitea_org_repos` -- List repositories in an organization
- `gitea_list_connections` -- List configured Gitea connections
#### File Contents (5 tools)
- `gitea_file_get` -- Get file contents from a repository
- `gitea_dir_get` -- Get directory contents (file listing) from a repository
- `gitea_file_create_or_update` -- Create or update a file in a repository
- `gitea_file_delete` -- Delete a file from a repository
- `gitea_tree_get` -- Get the git tree for a repository (recursive file listing)
#### Branches (4 tools)
- `gitea_branches_list` -- List branches in a repository
- `gitea_branch_get` -- Get a specific branch
- `gitea_branch_create` -- Create a new branch
- `gitea_branch_delete` -- Delete a branch
#### Commits (2 tools)
- `gitea_commits_list` -- List commits in a repository
- `gitea_commit_get` -- Get a specific commit
#### Issues (7 tools)
- `gitea_issues_list` -- List issues in a repository
- `gitea_issue_get` -- Get a single issue by number
- `gitea_issue_create` -- Create a new issue
- `gitea_issue_update` -- Update an issue
- `gitea_issue_comments_list` -- List comments on an issue
- `gitea_issue_comment_create` -- Add a comment to an issue
- `gitea_issue_search` -- Search issues across all repositories
#### Labels (2 tools)
- `gitea_labels_list` -- List labels in a repository
- `gitea_label_create` -- Create a label
#### Milestones (2 tools)
- `gitea_milestones_list` -- List milestones in a repository
- `gitea_milestone_create` -- Create a milestone
#### Pull Requests (6 tools)
- `gitea_pulls_list` -- List pull requests
- `gitea_pull_get` -- Get a single pull request
- `gitea_pull_create` -- Create a pull request
- `gitea_pull_merge` -- Merge a pull request
- `gitea_pull_files` -- List files changed in a pull request
- `gitea_pull_review_create` -- Create a pull request review
#### Releases (5 tools)
- `gitea_releases_list` -- List releases
- `gitea_release_get` -- Get a single release by ID
- `gitea_release_latest` -- Get the latest release
- `gitea_release_create` -- Create a new release
- `gitea_release_delete` -- Delete a release
#### Tags (3 tools)
- `gitea_tags_list` -- List tags
- `gitea_tag_create` -- Create a tag
- `gitea_tag_delete` -- Delete a tag
#### Actions (2 tools)
- `gitea_actions_runs_list` -- List workflow runs for a repository
- `gitea_actions_run_get` -- Get a specific workflow run
#### Organizations (3 tools)
- `gitea_org_get` -- Get organization details
- `gitea_org_teams_list` -- List teams in an organization
- `gitea_org_members_list` -- List members of an organization
#### Users (2 tools)
- `gitea_user_get` -- Get a user profile
- `gitea_users_search` -- Search users
#### Webhooks (2 tools)
- `gitea_webhooks_list` -- List webhooks for a repository
- `gitea_webhook_create` -- Create a webhook
#### Wiki (2 tools)
- `gitea_wiki_pages_list` -- List wiki pages
- `gitea_wiki_page_get` -- Get a wiki page
#### Notifications (2 tools)
- `gitea_notifications_list` -- List notifications for the authenticated user
- `gitea_notifications_read` -- Mark all notifications as read
#### Generic (2 tools)
- `gitea_api_request` -- Make a raw API request to any Gitea v1 endpoint
- `gitea_list_connections` -- List configured Gitea connections
### Infrastructure
- Multi-connection config support via `~/.gitea-api-mcp.json`
- Token-based authentication (Gitea native `Authorization: token` header)
- Built on `node:https` / `node:http` (zero HTTP dependencies)
- MCP SDK v1.12.x with stdio transport
[0.0.1]: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp/releases/tag/v0.0.1
+18
View File
@@ -0,0 +1,18 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production=false
COPY tsconfig.json ./
COPY src/ ./src/
RUN npx tsc && npm prune --production
EXPOSE 3100
ENV PORT=3100
ENV NODE_ENV=production
# SSE mode by default for Docker deployments
CMD ["node", "dist/sse.js"]
+116
View File
@@ -0,0 +1,116 @@
# MokoGitea MCP Server
A comprehensive [Model Context Protocol](https://modelcontextprotocol.io) server for [Gitea](https://gitea.com) and [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/MokoGitea). 120+ tools for repos, issues, PRs, projects, releases, custom fields, statuses, priorities, and manifests.
Works with any Gitea instance. MokoGitea-specific features degrade gracefully on vanilla Gitea.
## Quick Start
### npx (no install)
```bash
GITEA_URL=https://gitea.example.com GITEA_TOKEN=your_token npx @mokoconsulting/mokogitea-mcp
```
### Claude Code
Add to `.claude.json`:
```json
{
"mcpServers": {
"mokogitea": {
"command": "npx",
"args": ["@mokoconsulting/mokogitea-mcp"],
"env": {
"GITEA_URL": "https://gitea.example.com",
"GITEA_TOKEN": "your_token"
}
}
}
}
```
### Docker (SSE mode)
```bash
docker run -p 3100:3100 \
-e GITEA_URL=https://gitea.example.com \
-e GITEA_TOKEN=your_token \
mokoconsulting/mokogitea-mcp
```
Connect MCP client to `http://localhost:3100/sse`.
### Multi-instance config
Create `~/.mcp_mokogitea.json`:
```json
{
"defaultConnection": "production",
"connections": {
"production": { "baseUrl": "https://gitea.example.com", "token": "your_token" },
"dev": { "baseUrl": "https://dev.gitea.example.com", "token": "dev_token" }
}
}
```
## Configuration
| Method | Use Case |
|--------|----------|
| `GITEA_URL` + `GITEA_TOKEN` env vars | Single instance, quick setup |
| `~/.mcp_mokogitea.json` config file | Multiple instances |
| `GITEA_API_MCP_CONFIG` env var | Custom config path |
| `GITEA_INSECURE=true` | Skip TLS verification |
## Tools (120+)
### Repositories
`gitea_repo_create` `gitea_repo_get` `gitea_repo_edit` `gitea_repo_delete` `gitea_repo_search` `gitea_repo_fork` `gitea_repo_generate` `gitea_repo_languages` `gitea_repo_contributors` `gitea_repo_topics` `gitea_repo_topics_set`
### Issues
`gitea_issue_create` (dedup by title) `gitea_issue_get` `gitea_issue_update` `gitea_issues_list` `gitea_issue_search` `gitea_issue_comment_create` `gitea_issue_comments_list` `gitea_issue_labels_set` `gitea_issue_bulk_set_status`
### Pull Requests
`gitea_pull_create` `gitea_pull_get` `gitea_pulls_list` `gitea_pull_merge` `gitea_pull_files` `gitea_pull_review_create`
### Branches and Tags
`gitea_branches_list` `gitea_branch_create` `gitea_branch_delete` `gitea_branch_get` `gitea_tags_list` `gitea_tag_create` `gitea_tag_delete`
### Releases
`gitea_releases_list` `gitea_release_create` `gitea_release_get` `gitea_release_latest` `gitea_release_delete` `gitea_release_asset_upload` `gitea_release_asset_delete`
### Files and Trees
`gitea_file_get` `gitea_file_create_or_update` `gitea_file_delete` `gitea_dir_get` `gitea_tree_get` `gitea_bulk_file_push`
### Projects
`gitea_project_list` `gitea_project_create` `gitea_project_get` `gitea_project_update` `gitea_project_delete` `gitea_project_overview` `gitea_project_columns_list` `gitea_project_column_create` `gitea_project_column_delete` `gitea_project_cards_list` `gitea_project_card_add` `gitea_project_card_move` `gitea_project_card_remove`
### Organizations
`gitea_org_get` `gitea_org_repos` `gitea_org_members_list` `gitea_org_teams_list` `gitea_org_labels_list` `gitea_org_label_create`
### Wiki
`gitea_wiki_pages_list` `gitea_wiki_page_get`
### MokoGitea Extensions
`gitea_manifest_get` `gitea_manifest_update` `gitea_org_custom_fields_list` `gitea_org_custom_field_create` `gitea_org_custom_field_delete` `gitea_issue_custom_fields_get` `gitea_issue_custom_fields_set` `gitea_org_issue_statuses_list` `gitea_issue_set_status` `gitea_org_issue_priorities_list` `gitea_issue_set_priority`
### Admin and Other
`gitea_me` `gitea_users_search` `gitea_user_get` `gitea_notifications_list` `gitea_notifications_read` `gitea_commits_list` `gitea_commit_get` `gitea_compare` `gitea_webhooks_list` `gitea_webhook_create` `gitea_admin_users_list` `gitea_admin_orgs_list` `gitea_admin_cron_list` `gitea_admin_cron_run` `gitea_list_connections`
## SSE Server
For hosted deployments:
```
GET / Server info
GET /sse SSE connection endpoint
POST /message Tool call messages
GET /health Health check
```
## License
GPL-3.0-or-later - [Moko Consulting](https://mokoconsulting.tech)
+13
View File
@@ -0,0 +1,13 @@
{
"defaultConnection": "moko",
"connections": {
"moko": {
"baseUrl": "https://git.mokoconsulting.tech",
"token": "your-gitea-access-token"
},
"github-mirror": {
"baseUrl": "https://gitea.example.com",
"token": "your-other-token"
}
}
}
+1198
View File
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
{
"name": "@mokoconsulting/mokogitea-mcp",
"version": "1.1.0",
"description": "MCP server for Gitea and MokoGitea - 120+ tools for repos, issues, PRs, projects, releases, custom fields, statuses, priorities, and manifests",
"type": "module",
"main": "dist/index.js",
"bin": {
"mokogitea-mcp": "dist/index.js",
"mokogitea-mcp-sse": "dist/sse.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/index.js",
"start:sse": "node dist/sse.js",
"setup": "node scripts/setup.mjs",
"clean": "rm -rf dist/"
},
"keywords": [
"mcp",
"gitea",
"mokogitea",
"model-context-protocol",
"claude",
"ai",
"git",
"self-hosted",
"api",
"devops"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"zod": "^3.24.4"
},
"devDependencies": {
"@types/node": "^22.15.3",
"typescript": "^5.8.3"
},
"engines": {
"node": ">=20.0.0"
},
"license": "GPL-3.0-or-later",
"author": "Moko Consulting <hello@mokoconsulting.tech>",
"homepage": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api",
"repository": {
"type": "git",
"url": "https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api.git"
},
"files": [
"dist/",
"config.example.json",
"README.md",
"LICENSE"
],
"publishConfig": {
"access": "public"
}
}
+15
View File
@@ -0,0 +1,15 @@
# mcp_mokogitea_api PowerShell Profile
# Source this with: . ./profile.ps1
$env:MCP_ROOT = $PSScriptRoot
$env:TEMP = 'A:\temp'
$env:TMP = 'A:\temp'
function mcp { Set-Location $PSScriptRoot }
function mcp-src { Set-Location (Join-Path $PSScriptRoot 'src') }
function mcp-build { Set-Location $PSScriptRoot; npm run build }
function mcp-dev { Set-Location $PSScriptRoot; npm run dev }
Write-Host "mcp_mokogitea_api profile loaded" -ForegroundColor Cyan
Write-Host " Commands: mcp-build, mcp-dev" -ForegroundColor DarkGray
Write-Host " Navigate: mcp, mcp-src" -ForegroundColor DarkGray
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env node
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* BRIEF: Interactive setup — prompts for Gitea connection details
*/
import { createInterface } from 'node:readline/promises';
import { readFile, writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { homedir } from 'node:os';
const CONFIG_PATH = resolve(homedir(), '.gitea-api-mcp.json');
const rl = createInterface({ input: process.stdin, output: process.stdout });
async function prompt(q, d) { const a = await rl.question(`${q}${d ? ` [${d}]` : ''}: `); return a.trim() || d || ''; }
async function promptRequired(q) { let a = ''; while (!a) { a = (await rl.question(`${q}: `)).trim(); if (!a) console.log(' Required.'); } return a; }
async function main() {
console.log('\n=== gitea-api-mcp Setup ===\n');
let existing = null;
try { existing = JSON.parse(await readFile(CONFIG_PATH, 'utf-8')); console.log(`Existing: ${Object.keys(existing.connections).join(', ')}\n`); } catch {}
const name = await prompt('Connection name', 'moko');
const baseUrl = await promptRequired('Gitea URL (e.g. https://git.mokoconsulting.tech)');
const token = await promptRequired('Access token (Settings > Applications > Generate Token)');
const insecure = (await prompt('Skip TLS verification? (y/N)', 'N')).toLowerCase() === 'y';
const conn = { baseUrl: baseUrl.replace(/\/+$/, ''), token };
if (insecure) conn.insecure = true;
const config = existing ?? { defaultConnection: name, connections: {} };
config.connections[name] = conn;
if (!existing) config.defaultConnection = name;
else if ((await prompt(`Set "${name}" as default? (y/N)`, 'N')).toLowerCase() === 'y') config.defaultConnection = name;
await writeFile(CONFIG_PATH, JSON.stringify(config, null, '\t') + '\n', 'utf-8');
console.log(`\nConfig written to ${CONFIG_PATH}\n`);
rl.close();
}
main().catch(e => { console.error(e.message); rl.close(); process.exit(1); });
+120
View File
@@ -0,0 +1,120 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: gitea-api-mcp.Client
* INGROUP: gitea-api-mcp
* REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
* PATH: /src/client.ts
* VERSION: 01.00.00
* BRIEF: HTTP client for Gitea REST API v1
*/
import * as https from 'node:https';
import * as http from 'node:http';
import type { GiteaConnection, ApiResponse } from './types.js';
const API_PREFIX = '/api/v1';
const TIMEOUT_MS = 30_000;
export class GiteaClient {
private readonly base_url: string;
private readonly headers: Record<string, string>;
private readonly insecure: boolean;
constructor(conn: GiteaConnection) {
this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX;
this.headers = {
'Authorization': `token ${conn.token}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
this.insecure = conn.insecure ?? false;
}
async get(endpoint: string, params?: Record<string, string>): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint, params), 'GET');
}
async post(endpoint: string, body?: unknown): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'POST', body);
}
async patch(endpoint: string, body: unknown): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'PATCH', body);
}
async put(endpoint: string, body: unknown): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'PUT', body);
}
async delete(endpoint: string): Promise<ApiResponse> {
return this.request(this.buildUrl(endpoint), 'DELETE');
}
private buildUrl(endpoint: string, params?: Record<string, string>): string {
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
const url = new URL(`${this.base_url}${path}`);
if (params) {
for (const [key, value] of Object.entries(params)) {
url.searchParams.set(key, value);
}
}
return url.toString();
}
private request(url: string, method: string, body?: unknown): Promise<ApiResponse> {
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const is_https = parsed.protocol === 'https:';
const transport = is_https ? https : http;
const options: https.RequestOptions = {
hostname: parsed.hostname,
port: parsed.port || (is_https ? 443 : 80),
path: parsed.pathname + parsed.search,
method,
headers: { ...this.headers },
timeout: TIMEOUT_MS,
};
if (this.insecure && is_https) {
options.rejectUnauthorized = false;
}
const payload = body !== undefined ? JSON.stringify(body) : undefined;
if (payload) {
(options.headers as Record<string, string>)['Content-Length'] = Buffer.byteLength(payload).toString();
}
const req = transport.request(options, (res) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf-8');
let data: unknown;
try {
data = JSON.parse(raw);
} catch {
data = raw;
}
resolve({ status: res.statusCode ?? 0, data });
});
});
req.on('error', (err) => reject(err));
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});
if (payload) {
req.write(payload);
}
req.end();
});
}
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { homedir } from 'node:os';
import type { GiteaConfig, GiteaConnection } from './types.js';
const CONFIG_FILENAME = '.mcp_mokogitea.json';
export async function loadConfig(): Promise<GiteaConfig> {
// Priority 1: Environment variables (zero-config single instance)
if (process.env.GITEA_URL && process.env.GITEA_TOKEN) {
const conn: GiteaConnection = {
baseUrl: process.env.GITEA_URL,
token: process.env.GITEA_TOKEN,
insecure: process.env.GITEA_INSECURE === 'true',
};
return {
connections: { default: conn },
defaultConnection: 'default',
};
}
// Priority 2: Config file
const config_path = process.env.GITEA_API_MCP_CONFIG
? resolve(process.env.GITEA_API_MCP_CONFIG)
: resolve(homedir(), CONFIG_FILENAME);
try {
const raw = await readFile(config_path, 'utf-8');
const parsed = JSON.parse(raw) as Partial<GiteaConfig>;
if (!parsed.connections || Object.keys(parsed.connections).length === 0) {
throw new Error('No connections defined in config');
}
return {
connections: parsed.connections,
defaultConnection: parsed.defaultConnection ?? Object.keys(parsed.connections)[0],
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(
`Failed to load config from ${config_path}: ${message}\n` +
`Option 1: Set GITEA_URL and GITEA_TOKEN environment variables\n` +
`Option 2: Create ${config_path} - see config.example.json for format`,
);
}
}
export function getConnection(config: GiteaConfig, name?: string): GiteaConnection {
const key = name ?? config.defaultConnection;
const conn = config.connections[key];
if (!conn) {
throw new Error(
`Connection "${key}" not found. Available: ${Object.keys(config.connections).join(', ')}`,
);
}
return conn;
}
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Creates a configured MCP server instance for use by both stdio and SSE transports.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { GiteaConfig } from './types.js';
// Import index.ts to register all tools on its exported `server` singleton,
// then re-export a factory that initializes config and returns the server.
import { server, initConfig } from './index.js';
export function createMcpServer(cfg: GiteaConfig): McpServer {
initConfig(cfg);
return server;
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// SSE transport entry point for MokoGitea MCP server.
// Run with: node dist/sse.js
// Or: GITEA_URL=https://gitea.example.com GITEA_TOKEN=xxx node dist/sse.js
//
// Listens on PORT (default 3100) and serves SSE at /sse with POST at /message.
import { createServer } from 'node:http';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { createMcpServer } from './server.js';
import { loadConfig } from './config.js';
const PORT = parseInt(process.env.PORT ?? '3100', 10);
async function main(): Promise<void> {
const config = await loadConfig();
const transports = new Map<string, SSEServerTransport>();
const httpServer = createServer(async (req, res) => {
// CORS headers for browser clients
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// Health check
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', tools: 120 }));
return;
}
// SSE endpoint - client connects here
if (req.url === '/sse' && req.method === 'GET') {
const transport = new SSEServerTransport('/message', res);
const sessionId = transport.sessionId;
transports.set(sessionId, transport);
const server = createMcpServer(config);
await server.connect(transport);
req.on('close', () => {
transports.delete(sessionId);
});
return;
}
// Message endpoint - client sends tool calls here
if (req.url?.startsWith('/message') && req.method === 'POST') {
const url = new URL(req.url, `http://${req.headers.host}`);
const sessionId = url.searchParams.get('sessionId');
if (!sessionId || !transports.has(sessionId)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid or missing sessionId' }));
return;
}
const transport = transports.get(sessionId)!;
await transport.handlePostMessage(req, res);
return;
}
// Root - info page
if (req.url === '/' || req.url === '') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
name: '@mokoconsulting/mokogitea-mcp',
version: '1.1.0',
description: 'MCP server for Gitea and MokoGitea - 120+ tools',
endpoints: {
sse: '/sse',
message: '/message',
health: '/health',
},
docs: 'https://git.mokoconsulting.tech/MokoConsulting/mcp_mokogitea_api',
}));
return;
}
res.writeHead(404);
res.end('Not found');
});
httpServer.listen(PORT, () => {
process.stderr.write(`MokoGitea MCP SSE server listening on port ${PORT}\n`);
process.stderr.write(` SSE: http://localhost:${PORT}/sse\n`);
process.stderr.write(` Health: http://localhost:${PORT}/health\n`);
});
}
main().catch((err) => {
process.stderr.write(`Fatal: ${err}\n`);
process.exit(1);
});
+37
View File
@@ -0,0 +1,37 @@
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: gitea-api-mcp.Types
* INGROUP: gitea-api-mcp
* REPO: https://git.mokoconsulting.tech/MokoConsulting/gitea-api-mcp
* PATH: /src/types.ts
* VERSION: 01.00.00
* BRIEF: TypeScript type definitions for Gitea API MCP server
*/
export interface GiteaConnection {
baseUrl: string;
token: string;
/** Skip TLS certificate verification (self-signed certs) */
insecure?: boolean;
}
export interface GitHubBackupConfig {
token: string;
org: string;
}
export interface GiteaConfig {
connections: Record<string, GiteaConnection>;
defaultConnection: string;
github?: GitHubBackupConfig;
}
export interface ApiResponse {
status: number;
data: unknown;
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+324
View File
@@ -0,0 +1,324 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml
#
# +========================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+
# | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | |
# | Platform-specific: |
# | joomla: XML manifest, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream |
# | |
# +========================================================================+
name: "Universal: Build & Release"
on:
pull_request:
types: [opened, closed]
branches:
- main
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: false
type: choice
default: release
options:
- release
- promote-rc
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions:
contents: write
jobs:
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
promote-rc:
name: Promote to RC
runs-on: release
if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: Rename branch to rc
run: |
php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git
run: |
git fetch origin rc
git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
name: Build & Release Pipeline
runs-on: release
if: >-
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: |
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found — aborting release"
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
run: |
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: "Publish stable release"
run: |
php ${MOKO_CLI}/release_publish.php \
--path . --stability stable --bump minor --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}"
- name: Update release notes from CHANGELOG.md
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Stable release"
else
NOTES="Stable release"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- name: "Step 10: Push main to GitHub mirror"
if: >-
steps.version.outputs.skip != 'true' &&
secrets.GH_MIRROR_TOKEN != ''
continue-on-error: true
run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
git fetch origin main --depth=1
git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
|| echo "WARNING: GitHub mirror push failed"
- name: "Step 11: Delete rc branch and recreate dev from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \
&& echo "Deleted rc branch" || echo "rc branch not found"
# Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
# Recreate dev from main (now includes version bump + changelog promotion)
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/branches" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
MAIN_SHA=$(git rev-parse HEAD)
# Delete old version branch if it exists (same version re-release)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Create version/XX.YY.ZZ from main
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
PLATFORM="${{ steps.platform.outputs.platform }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 05.14.00
# VERSION: 05.47.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+51
View File
@@ -0,0 +1,51 @@
name: Publish MCP to npm
on:
push:
branches: [main]
paths:
- '.mokogitea/mcp/**'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install and build
working-directory: .mokogitea/mcp
run: |
npm ci
npx tsc
- name: Check version change
id: version
working-directory: .mokogitea/mcp
run: |
LOCAL_VERSION=$(node -p "require('./package.json').version")
NPM_VERSION=$(npm view @mokoconsulting/mokogitea-mcp version 2>/dev/null || echo "0.0.0")
if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION"
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "Version unchanged: $LOCAL_VERSION"
fi
- name: Publish to npm
if: steps.version.outputs.changed == 'true'
working-directory: .mokogitea/mcp
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to Gitea registry
if: steps.version.outputs.changed == 'true'
working-directory: .mokogitea/mcp
run: |
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.GITEA_TOKEN }}
+244
View File
@@ -105,6 +105,19 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Check for merge conflict markers
run: |
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
if [ -n "$CONFLICTS" ]; then
echo "::error::Merge conflict markers found in source files"
echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "No conflict markers found"
- name: Detect platform
id: platform
run: |
@@ -134,6 +147,98 @@ jobs:
echo "PHP lint: ${ERRORS} error(s)"
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
- name: Joomla JEXEC guard check
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
while IFS= read -r -d '' file; do
# Skip vendor, node_modules, and index.html stub files
case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
# Check first 10 lines for JEXEC or JPATH guard
if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
echo "::error file=${file}::Missing JEXEC guard: ${file}"
ERRORS=$((ERRORS + 1))
fi
done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0)
if [ "$ERRORS" -gt 0 ]; then
echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard"
echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY
echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "JEXEC guard: OK"
- name: Joomla directory listing protection
if: steps.platform.outputs.platform == 'joomla'
run: |
MISSING=0
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && exit 0
while IFS= read -r dir; do
if [ ! -f "${dir}/index.html" ]; then
echo "::warning::Missing index.html in ${dir} (directory listing protection)"
MISSING=$((MISSING + 1))
fi
done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*")
if [ "$MISSING" -gt 0 ]; then
echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY
echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY
fi
echo "Directory protection: ${MISSING} missing (advisory)"
- name: Joomla script file and asset checks
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && exit 0
MANIFEST_DIR=$(dirname "$MANIFEST")
# Check scriptfile exists if declared
SCRIPTFILE=$(sed -n 's/.*<scriptfile>\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
if [ -n "$SCRIPTFILE" ]; then
if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
echo "::error::Manifest declares <scriptfile>${SCRIPTFILE}</scriptfile> but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}"
ERRORS=$((ERRORS + 1))
else
echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)"
fi
fi
# Require joomla.asset.json and validate it
ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$ASSET_JSON" ]; then
echo "::error::joomla.asset.json not found — Joomla asset system is required"
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || {
echo "::error::joomla.asset.json is not valid JSON"
ERRORS=$((ERRORS + 1))
}
fi
echo "joomla.asset.json: valid"
fi
# Validate all XML files in src/ are well-formed
XML_ERRORS=0
if command -v php &> /dev/null; then
while IFS= read -r -d '' xmlfile; do
if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then
XML_ERRORS=$((XML_ERRORS + 1))
fi
done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0)
fi
if [ "$XML_ERRORS" -gt 0 ]; then
echo "::error::${XML_ERRORS} XML file(s) are malformed"
ERRORS=$((ERRORS + 1))
else
echo "XML well-formedness: OK"
fi
[ "$ERRORS" -gt 0 ] && exit 1
echo "Joomla asset checks: OK"
- name: Validate platform manifest
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
@@ -151,6 +256,13 @@ jobs:
for ELEMENT in name version description; do
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
done
# Block legacy raw/branch update server URLs on MokoGitea
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
if [ -n "$RAW_URLS" ]; then
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
echo "$RAW_URLS"
exit 1
fi
echo "Joomla manifest valid"
;;
dolibarr)
@@ -183,6 +295,138 @@ jobs:
;;
esac
- name: Validate Joomla language files
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
WARNINGS=0
# Require both en-GB and en-US language directories
LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$LANG_ROOT" ]; then
echo "No language/ directory found — skipping"
exit 0
fi
if [ ! -d "$LANG_ROOT/en-GB" ]; then
echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)"
ERRORS=$((ERRORS + 1))
fi
if [ ! -d "$LANG_ROOT/en-US" ]; then
echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)"
ERRORS=$((ERRORS + 1))
fi
# Check that en-GB and en-US have matching .ini files
if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then
for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do
[ ! -f "$GB_INI" ] && continue
US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")"
if [ ! -f "$US_INI" ]; then
echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US"
ERRORS=$((ERRORS + 1))
fi
done
for US_INI in "$LANG_ROOT/en-US"/*.ini; do
[ ! -f "$US_INI" ] && continue
GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")"
if [ ! -f "$GB_INI" ]; then
echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB"
ERRORS=$((ERRORS + 1))
fi
done
fi
# Find all .ini language files
INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
if [ -z "$INI_FILES" ]; then
echo "No .ini language files found"
[ "$ERRORS" -gt 0 ] && exit 1
exit 0
fi
echo "Found $(echo "$INI_FILES" | wc -l) language file(s)"
for FILE in $INI_FILES; do
FNAME=$(basename "$FILE")
LINENUM=0
SEEN_KEYS=""
while IFS= read -r line || [ -n "$line" ]; do
LINENUM=$((LINENUM + 1))
# Skip empty lines and comments
[ -z "$line" ] && continue
echo "$line" | grep -qE '^\s*;' && continue
echo "$line" | grep -qE '^\s*$' && continue
# Must match KEY="VALUE" format
if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then
echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}"
ERRORS=$((ERRORS + 1))
continue
fi
# Extract key and check for duplicates
KEY=$(echo "$line" | sed 's/=.*//')
if echo "$SEEN_KEYS" | grep -qx "$KEY"; then
echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}"
ERRORS=$((ERRORS + 1))
fi
SEEN_KEYS="${SEEN_KEYS}
${KEY}"
done < "$FILE"
echo " ${FILE}: checked ${LINENUM} lines"
done
# Cross-check en-GB vs en-US key consistency
GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1)
US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then
for GB_FILE in "$GB_DIR"/*.ini; do
[ ! -f "$GB_FILE" ] && continue
FNAME=$(basename "$GB_FILE")
US_FILE="$US_DIR/$FNAME"
[ ! -f "$US_FILE" ] && continue
GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort)
US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort)
# Keys in en-GB but not en-US
MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_US" ]; then
echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:"
echo "$MISSING_US" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
# Keys in en-US but not en-GB
MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_GB" ]; then
echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:"
echo "$MISSING_GB" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
done
fi
{
echo "### Language File Validation"
echo "| Metric | Count |"
echo "|---|---|"
echo "| Files checked | $(echo "$INI_FILES" | wc -l) |"
echo "| Errors | ${ERRORS} |"
echo "| Warnings | ${WARNINGS} |"
} >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "::error::Language validation failed with ${ERRORS} error(s)"
exit 1
fi
echo "Language files: OK (${WARNINGS} warning(s))"
- name: Check changelog has unreleased entry
run: |
if [ ! -f "CHANGELOG.md" ]; then
+243
View File
@@ -0,0 +1,243 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
name: "Universal: Pre-Release"
on:
pull_request:
types: [closed]
branches:
- dev
pull_request_target:
types: [synchronize, opened, reopened]
branches:
- main
workflow_dispatch:
inputs:
stability:
description: 'Pre-release channel'
required: true
type: choice
options:
- development
- alpha
- beta
- release-candidate
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
jobs:
build:
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
echo Using pre-installed /opt/moko-platform
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
fi
- name: Detect platform
id: platform
run: |
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
STABILITY="release-candidate"
else
STABILITY="${{ inputs.stability || 'development' }}"
fi
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
beta) SUFFIX="-beta"; TAG="beta" ;;
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
case "$STABILITY" in
release-candidate) BUMP="minor" ;;
*) BUMP="patch" ;;
esac
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
# Set stability suffix and verify consistency
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
# Append suffix for output
if [ -n "$SUFFIX" ]; then
VERSION="${VERSION}${SUFFIX}"
fi
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
# Auto-detect element via manifest_element.php
php ${MOKO_CLI}/manifest_element.php \
--path . --version "$VERSION" --stability "$STABILITY" \
--repo "${GITEA_REPO}" --github-output
# Read back element outputs
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Create release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_create.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease
- name: Update release notes from CHANGELOG.md
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
if [ -f "CHANGELOG.md" ]; then
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
else
NOTES="Release ${VERSION}"
fi
# Update release body via API
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ]; then
python3 -c "
import json, urllib.request
body = open('/dev/stdin').read()
payload = json.dumps({'body': body}).encode()
req = urllib.request.Request(
'${API_BASE}/releases/${RELEASE_ID}',
data=payload, method='PATCH',
headers={
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
'Content-Type': 'application/json'
})
urllib.request.urlopen(req)
" <<< "$NOTES"
echo "Release notes updated from CHANGELOG.md"
fi
- name: Build package and upload
id: package
run: |
VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_package.php \
--path . --version "$VERSION" --tag "$TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# updates.xml is generated dynamically by MokoGitea license server
# No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
php ${MOKO_CLI}/release_cascade.php \
--stability "${{ steps.meta.outputs.stability }}" \
--token "${TOKEN}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+9 -115
View File
@@ -11,7 +11,7 @@
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 09.23.00
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
# ============================================================================
name: "Generic: Repo Health"
@@ -24,13 +24,12 @@ on:
workflow_dispatch:
inputs:
profile:
description: 'Validation profile: all, release, scripts, or repo'
description: 'Validation profile: all, scripts, or repo'
required: true
default: all
type: choice
options:
- all
- release
- scripts
- repo
pull_request:
@@ -40,10 +39,6 @@ permissions:
contents: read
env:
# Release policy - Repository Variables Only
RELEASE_REQUIRED_REPO_VARS: RS_FTP_PATH_SUFFIX
RELEASE_OPTIONAL_REPO_VARS: DEV_FTP_SUFFIX
# Scripts governance policy
SCRIPTS_REQUIRED_DIRS:
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
@@ -138,101 +133,6 @@ jobs:
printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}"
exit 1
release_config:
name: Release configuration
needs: access_check
if: ${{ needs.access_check.outputs.allowed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Guardrails release vars
env:
PROFILE_RAW: ${{ github.event.inputs.profile }}
RS_FTP_PATH_SUFFIX: ${{ vars.RS_FTP_PATH_SUFFIX }}
DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
run: |
set -euo pipefail
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
*)
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
{
printf '%s\n' '### Release configuration (Repository Variables)'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' 'Status: SKIPPED'
printf '%s\n' 'Reason: profile excludes release validation'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
IFS=',' read -r -a required <<< "${RELEASE_REQUIRED_REPO_VARS}"
IFS=',' read -r -a optional <<< "${RELEASE_OPTIONAL_REPO_VARS}"
missing=()
missing_optional=()
for k in "${required[@]}"; do
v="${!k:-}"
[ -z "${v}" ] && missing+=("${k}")
done
for k in "${optional[@]}"; do
v="${!k:-}"
[ -z "${v}" ] && missing_optional+=("${k}")
done
{
printf '%s\n' '### Release configuration (Repository Variables)'
printf '%s\n' "Profile: ${profile}"
printf '%s\n' '| Variable | Status |'
printf '%s\n' '|---|---|'
printf '%s\n' "| RS_FTP_PATH_SUFFIX | ${RS_FTP_PATH_SUFFIX:-NOT SET} |"
printf '%s\n' "| DEV_FTP_SUFFIX | ${DEV_FTP_SUFFIX:-NOT SET} |"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if [ "${#missing_optional[@]}" -gt 0 ]; then
{
printf '%s\n' '### Missing optional repository variables'
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
if [ "${#missing[@]}" -gt 0 ]; then
{
printf '%s\n' '### Missing required repository variables'
for m in "${missing[@]}"; do printf '%s\n' "- ${m}"; done
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository variables.'
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
{
printf '%s\n' '### Repository variables validation result'
printf '%s\n' 'Status: OK'
printf '%s\n' 'All required repository variables present.'
printf '%s\n' ''
printf '%s\n' '**Note**: Organization secrets (RS_FTP_HOST, RS_FTP_USER, etc.) are validated at deployment time, not in repository health checks.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
scripts_governance:
name: Scripts governance
needs: access_check
@@ -256,14 +156,14 @@ jobs:
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
all|scripts|repo) ;;
*)
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then
if [ "${profile}" = 'repo' ]; then
{
printf '%s\n' '### Scripts governance'
printf '%s\n' "Profile: ${profile}"
@@ -370,14 +270,14 @@ jobs:
profile="${PROFILE_RAW:-all}"
case "${profile}" in
all|release|scripts|repo) ;;
all|scripts|repo) ;;
*)
printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
exit 1
;;
esac
if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then
if [ "${profile}" = 'scripts' ]; then
{
printf '%s\n' '### Repository health'
printf '%s\n' "Profile: ${profile}"
@@ -704,7 +604,7 @@ jobs:
printf '%s\n' '| Domain | Status | Notes |'
printf '%s\n' '|---|---|---|'
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
printf '%s\n' '| Release variables | OK | Repository variables validation |'
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |'
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
@@ -773,11 +673,10 @@ jobs:
report-issues:
name: "Report Issues"
runs-on: ubuntu-latest
needs: [access_check, release_config, scripts_governance, repo_health]
needs: [access_check, scripts_governance, repo_health]
if: >-
always() &&
(needs.release_config.result == 'failure' ||
needs.scripts_governance.result == 'failure' ||
(needs.scripts_governance.result == 'failure' ||
needs.repo_health.result == 'failure')
steps:
@@ -803,10 +702,6 @@ jobs:
fi
}
report_gate "Release Configuration" \
"${{ needs.release_config.result }}" \
"Required repository variables are missing (RS_FTP_PATH_SUFFIX). Check repository settings."
report_gate "Scripts Governance" \
"${{ needs.scripts_governance.result }}" \
"Scripts directory policy violations detected. Review required and allowed directories."
@@ -814,4 +709,3 @@ jobs:
report_gate "Repository Health" \
"${{ needs.repo_health.result }}" \
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
-16
View File
@@ -1,16 +0,0 @@
- Use `make help` to find available development targets
- Run `make fmt` to format `.go` files, and run `make lint-go` to lint them
- Run `make lint-js` to lint `.ts` files
- Run `make tidy` after any `go.mod` changes
- Run single go tests with `go test -run '^TestName$' ./modulepath/`
- Run single js test files with `pnpm exec vitest <path-filter>`
- Run single playwright e2e test files with `GITEA_TEST_E2E_FLAGS='<filepath>' make test-e2e`
- Add the current year into the copyright header of new `.go` files
- Ensure no trailing whitespace in edited files
- Use Conventional Commits format for commit messages and PR titles (e.g. `type(scope): subject`)
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
- Preserve existing code comments, do not remove or rewrite comments that are still relevant
- In TypeScript, use `!` (non-null assertion) instead of `?.`/`??` when a value is known to always exist
- For CSS layout, prefer `flex-*` helpers over per-child `tw-ml-*` / `tw-mr-*` margins; fall back to `tw-*` utilities when specificity requires `!important`
- Include authorship attribution in issue and pull request comments
- Add `Co-Authored-By` lines to all commits, indicating name and model used
+135 -474
View File
@@ -1,72 +1,157 @@
# Changelog
This changelog goes through the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com).
## [v1.26.1-moko.06.02.00] - 2026-06-02
All notable changes to MokoGitea are documented here. Versions follow the format
`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.03`).
## [v1.26.1-moko.06.10] - 2026-06-06
* FEATURES
* feat(issues): first-class Type field with 12 auto-seeded defaults (Bug, Feature, Enhancement, Task, Documentation, Security, Roadmap, Client, Dolibarr, Infrastructure, Joomla, WaaS)
* feat(issues): first-class Status field with 13 auto-seeded defaults including 7 Pending states
* feat(issues): first-class Priority field with 4 auto-seeded defaults (Critical, High, Medium, Low)
* feat(issues): Type/Status/Priority colored badges in issue list view
* feat(issues): status dropdown replaces close/reopen button in comment form
* feat(security): built-in security scanning platform with secret scanner (15 patterns)
* feat(security): Security tab in repo navigation with alerts, scan controls
* feat(wiki): hierarchical folder navigation with sidebar tree and breadcrumbs
* feat(ui): well-known file tabs (README/LICENSE/CONTRIBUTING/SECURITY/CHANGELOG)
* feat(settings): repo manifest settings with REST API and auto-sync on push
* feat(mcp): public MCP server published to npm (@mokoconsulting/mokogitea-mcp)
* feat(mcp): SSE transport, env var config, Docker support, 120+ tools
* feat(mcp): issue dedup on create, type_id/status_id/priority_id params
* MIGRATIONS
* All org labels migrated to first-class Type/Status/Priority fields and deleted
* Type custom field (id=9) migrated to type_id and deleted
* Status custom field (id=1) deleted (replaced by first-class field)
* Priority labels migrated to priority_id
* Pending labels migrated to status definitions
* Scope labels migrated to type definitions
* Manifests populated for all 61 repos via API
* FIXES
* fix(ui): dashboard issue count badges use label spans instead of strong tags
* fix(wiki): directory check before raw redirect for folder navigation
* fix(wiki): proper display names in sidebar tree (strip dash markers)
* fix: replace non-ASCII em dashes with hyphens for hook compatibility
* fix: hookify __init__.py for stop hook JSON validation
* INFRASTRUCTURE
* npm: @mokoconsulting/mokogitea-mcp@1.1.0 and @mokoconsulting/mokowaas-mcp@1.0.0
* MCP servers consolidated under moko-platform/mcp/servers/
* Remote MCP repos renamed to hyphens
* Wiki restructured into features/, api/, operations/ folders
* Swagger API docs enabled at /api/swagger
## [v1.26.1-moko.06.04] - 2026-06-06
* FEATURES
* feat(licenses): full commercial license management system
* Package archiving with soft-delete and collapsible archived section
* Search keys by customer, domain, key number, email, or payment ref
* Download gating (none/prerelease/all modes)
* Feed visibility (public/no-download/hidden modes)
* Domain lock grace period (DomainLockHours)
* Domain restriction on packages and keys (comma-separated allowed domains)
* RepoScope enforcement — packages scoped to specific repos
* Joomla changelog XML endpoint (/changelog.xml)
* WordPress PUC-compatible update feed (/updates/wordpress.json)
* SHA256 checksums from sidecar files in Joomla updates.xml
* Joomla-standard tag values (dev/alpha/beta/rc/stable)
* Configurable license key prefix per organization
* Master key auto-generates, sorts first in key list
* License package creation at repo level via modal
* Key generation modal with licensee name, email, and domain fields
* Manual release-to-stream mapping with UI selector
* Double confirmation modals for permanent deletion
* Combolist channel picker (replaces checkboxes)
* Extension metadata in repo settings (per-repo override)
* API: package CRUD, key revoke, key renew, settings GET/PUT
* API: purchase webhook with PaymentRef idempotency
* API: public validation endpoint (no auth)
* Migration v340: all new columns synced
* feat(updates): infourl defaults to release listing page
* feat(updates): downloadkey prefix matches Akeeba pattern (dlid=)
* fix(licenses): expanded delete permissions to org owners + site admins
* fix(licenses): no-download mode shows release notes but hides files
* fix(licenses): releases require login in hidden feed visibility mode
* Migration v340-v344: all new columns synced
* feat(updates): Update Server system (renamed from "Licensing")
* Joomla XML with SHA256, changelog URL, version from asset filename
* Dolibarr JSON with channel filtering
* WordPress PUC-compatible JSON (plugin-update-checker)
* Composer packages.json
* PrestaShop module update XML
* Drupal update status XML
* WHMCS module update JSON
* Feed always public — downloads gated separately
* Stream-name tags supported alongside version tags
* Omit `<client>` for package extension types
* `<downloadkey>` only when download_gating is prerelease or all
* Version extracted from asset filename (matches actual download)
* Joomla tag values verified: dev, alpha, beta, rc, stable
* feat(orgs): enterprise sub-org hierarchy with parent-child relationships
* feat(repos): three-level visibility — Public (200), Private (403), Hidden (404)
* feat(settings): Update Server settings page with enable toggle in Advanced Settings
* feat(settings): advanced settings on dedicated page with dividing headers
* feat(settings): icons on all settings navbars (repo, org, user, admin)
* feat(ui): styled 403 Access Denied page with inline login form
* feat(issues): custom fields with inline editing in issue sidebar
* feat(issues): pre-fill custom fields from issue template YAML frontmatter (#493)
* Templates specify `custom_fields:` map (field name → default value)
* New issue sidebar shows org-level fields with template defaults pre-selected
* API create issue accepts `custom_fields` map by name
* feat(updateserver): resolve extension metadata from org-level custom fields (#492)
* Cascading fallback: custom fields → config table → repo-derived defaults
* All six generators updated (Joomla, WordPress, Composer, Drupal, PrestaShop, WHMCS)
* Repos can be migrated to custom fields gradually
* feat(ui): two-in-one Update Server / Licenses tab
* No gating: shows "Update Server" tab with feed URLs only
* Gated: shows "Licenses" tab with full key management
* `<downloadkey>` only appears when downloads are gated
* SECURITY
* fix(security): ownership guards on all API handlers (cross-org prevention)
* fix(security): RepoScope JSON parsing (substring matching bug)
* fix(security): CSRF tokens in delete confirmation modals
* fix(security): XSS escaping in WordPress changelog HTML
* fix(security): require login for licenses and actions pages
* fix(security): 403 for all users on private repos (not 404)
* fix(security): licensed private repos allow release viewing for signed-in users
* fix(security): anonymous download access respects download_gating setting
* FIXES
* fix(licenses): explicit xorm column names for UpdateStreamConfig fields
* fix(licenses): feed always public when licensing enabled
* fix(settings): prevent double-highlight on Advanced Settings nav item
* fix(settings): redirect back to /settings/advanced after save
* fix(build): remove stale custom field API routes and dead code
* fix(build): replace invalid UTF-8 character in API comment
* fix(build): permanent fixes for AI migration, feed/file.go, unused imports
* fix(updateserver): version extracted from asset filename (not release title)
* fix(updateserver): omit `<client>` for package types per Joomla spec
* fix(updateserver): `<downloadkey>` only shown when downloads are gated
* fix(updateserver): prevent stream name tag from overriding asset-derived version
* fix(build): restore build/ directory after accidental deletion
* fix(licenses): master key banner removed, master keys sort first in table
* fix(issues): issue sidebar loads org-level fields instead of legacy repo-level fields
## [v1.26.1-moko.05.15.00] - 2026-05-31
## [v1.26.1-moko.05] - 2026-05-31
* BREAKING CHANGES
* Deprecated Issue.Ref branch selector UI (#307)
* Removed branch/tag selector from issue sidebar and new issue form
* Removed ref badge from issue lists
* Removed POST /ref web route and UpdateIssueRef handler
* DB column and commit-close logic preserved for backward compatibility
* API create/edit still accept `ref` field (no-op) for backward compat
* FEATURES
* feat(ui): add generic combo-multiselect component (#361)
* feat(ui): generic combo-multiselect component (#361)
* Reusable dropdown with search, checkable items, and selected-items display
* Template: `shared/combolist.tmpl` — accepts Items, Name, Title, SelectedValues
* Decoupled from issue sidebar — works in any form context
* Template: `shared/combolist.tmpl`
* feat(updates): extension metadata settings for update feed generation
* feat(licenses): platform enforcement, key deletion, expired key cleanup
* feat(licenses): store keys in plaintext, show full key with copy button
* feat(actions): rebrand actions bot user to mokogitea-actions (#233, #234)
* Backward-compatible: recognizes github-actions[bot], gitea-actions[bot]
* feat(actions): actions bot user in branch protection whitelist (#233, #234)
* WhitelistActionsUser, MergeWhitelistActionsUser, ForcePushAllowlistActionsUser
* TECH DEBT
* chore: full namespace migration from git.mokoconsulting.tech to code.mokoconsulting.tech (#336, #337, #344)
* Go module path, all imports, template URLs, workflow configs (2,276 files)
* chore: full namespace migration to code.mokoconsulting.tech (#336, #337, #344)
* fix(blame): set HasSourceRenderedToggle for renderable files (#344)
* fix(settings): translate team permission strings via data-locale attributes (#344)
* fix(settings): translate team permission strings via data-locale (#344)
* fix(dropzone): use relative path for non-image attachment markdown links (#344)
* fix(templates): add required validation to issue dropdown fields (#350)
* refactor(ts): remove redundant `handled` field from MarkdownHandleIndentionResult (#350)
* refactor(go): rename HasOrgOrUserVisible to IsOwnerVisibleToDoer (#350)
* refactor(go): replace ValuesRepository with maps.Values (Go 1.21+) (#357)
* refactor(go): remove CanEnableEditor wrapper, use CanContentChange directly (#357)
* fix(ts): parseIssueHref now uses URL pathname and trims appSubUrl (#360)
* fix(actions): enforce MaxJobNumPerRun (256) limit when creating jobs (#360)
* refactor(go): remove CanEnableEditor wrapper (#357)
* fix(ts): parseIssueHref uses URL pathname and trims appSubUrl (#360)
* fix(actions): enforce MaxJobNumPerRun (256) limit (#360)
* fix(css): use calc(infinity * 1px) for --border-radius-full (#361)
* fix(css): remove legacy .center class from 2015, replace with tw-text-center (#361)
* chore: remove stale TODO from OAuth2 regenerate secret (already implemented) (#332)
* chore: remove stale pull request test stub TODOs (#328)
* chore: remove stale GetProjectsMode TODO
* chore: remove stale mustNotBeArchived/mustEnableEditor FIXME from API
* fix(routes): remove dead legacy /cherry-pick/{sha} route (replaced by /_cherrypick/)
* fix(css): remove legacy .center class, replace with tw-text-center (#361)
* fix(routes): remove dead legacy /cherry-pick/{sha} route
* fix(feed): use full ref name instead of ShortName for file feed revision
* BUGFIXES
* fix(build): use slices.Collect for maps.Values (Go 1.23+ compat)
@@ -74,25 +159,10 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* fix(licenses): only show licenses tab when licensing is enabled
* fix(licenses): show feed URLs based on repo update platform setting
* fix(updates): correct dlid prefix and align XML with Joomla standard
## [v1.26.1-moko.05.06.00] - 2026-05-30
* FEATURES
* feat(actions): rebrand actions bot user to mokogitea-actions (#233, #234)
* Name: gitea-actions → mokogitea-actions, FullName: MokoGitea Actions
* Email: mokogitea-actions[bot]@mokoconsulting.tech
* Backward-compatible: recognizes github-actions[bot], gitea-actions[bot], mokogitea-actions[bot]
* feat(actions): add actions bot user to branch protection whitelist (#233, #234)
* New toggles: WhitelistActionsUser, MergeWhitelistActionsUser, ForcePushAllowlistActionsUser
* Allows CI/CD workflows to push/merge/force-push to protected branches when enabled
* DB migration v334 adds the three boolean columns
* Exposed in API (create/edit branch protection) and web UI settings
* INFRASTRUCTURE
* fix(ci): auto-deploy to production on merge to main (#235)
* Deploy workflow now triggers on push to main, not just manual dispatch
* Version derived from git describe for auto-deploys
## [v1.26.1-moko.04.00.00] - 2026-05-24
## [v1.26.1-moko.04] - 2026-05-24
* SECURITY
* Backport 12 upstream v1.26.2 security fixes:
@@ -101,47 +171,38 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* OAuth PKCE hardening and refresh token replay protection (#142)
* Wiki git write and LFS token access enforcement (#143)
* Public-only token filtering in API queries (#144)
* Reading permission fix (#145)
* Artifact signature payload hardening (#146)
* AWS credentials encryption (#161)
* Mermaid v11.15.0 security update (#162)
* Composer package permission check (#164)
* BUGFIXES
* fix(actions): nil pointer dereference in concurrency during PR creation (#136)
* fix(ui): actions runs list broken row layout — CSS class mismatch (#138)
* fix: scheduled action panic with null event payload (upstream #37459)
* fix: treat email addresses case-insensitively (upstream #37600)
* fix(ui): actions runs list broken row layout (#138)
* fix: scheduled action panic with null event payload
* fix: treat email addresses case-insensitively
* fix: .mod lexer panic — removed invalid AMPL mapping
* fix: remove unused setting import in action.go
* fix: restore Permission field access in context middleware
* FEATURES
* Joomla-style updates.xml with channel selection (stable/dev/security/rc)
* Update checker reads from updates.xml with configurable CHANNEL setting
* Admin dashboard shows update banner with channel name and docker pull command
* Upstream bug sync workflow — daily automated issue creation from release/v1.26
* Joomla-style updates.xml with channel selection
* Update checker with configurable CHANNEL setting
* Admin dashboard update banner with docker pull command
* Upstream bug sync workflow — daily automated issue creation
* PR RC release workflow — auto-build RC on PR to main
* INFRASTRUCTURE
* New 3-part versioning: v{upstream}-moko.{major}.{minor}.{patch}
* Branding updates: error pages, home page, settings link to MokoGitea
* Branding updates: error pages, home page, settings link
* Deploy workflow updated for new version format
* PROCESS
* Created `type: bug` and `upstream` labels for automated issue tracking
* Deduplicated 19 duplicate feature request issues
* Closed 24 upstream bug/security issues after backporting
## [MokoGitea Unreleased]
## [v1.26.1-moko.03] - 2026-05-15
* FEATURES
* feat(api): Bulk issue operations — add/remove/replace labels, close/reopen, set milestone, and set assignees across multiple issues in a single request (#21)
* `POST /api/v1/repos/{owner}/{repo}/issues/bulk/labels`
* `POST /api/v1/repos/{owner}/{repo}/issues/bulk/state`
* `POST /api/v1/repos/{owner}/{repo}/issues/bulk/milestone`
* `POST /api/v1/repos/{owner}/{repo}/issues/bulk/assignees`
* Partial-failure support: returns per-issue success/failure map
* feat(api): Bulk issue operations — add/remove/replace labels, close/reopen, set milestone, assignees (#21)
* INFRASTRUCTURE
* Grafana: Standardized kiosk header across all 14 playlist dashboards — each now shows dashboard name, kiosk link, terminal/exit/switch instructions
* Grafana: Standardized kiosk header across all 14 playlist dashboards
* PROCESS
* Reopened 9 closed issues lacking documented testing proof (#3, #5, #38, #41, #70, #74, #75, #76, #78)
* Reopened 9 closed issues lacking documented testing proof
* Created `pending: testing` label for features awaiting verification
* Established policy: issues must not be closed without documented testing proof
@@ -166,403 +227,3 @@ been added to each release, please refer to the [blog](https://blog.gitea.com).
* Fix org team assignee/reviewer lookups for team member permissions (#37365) #37391
* Fix repo init README EOL (#37388) #37399
* Fix: dump with default zip type produces uncompressed zip (#37401)#37402
## [1.26.0](https://github.com/go-gitea/gitea/releases/tag/v1.26.0) - 2026-04-17
* BREAKING
* Correct swagger annotations for enums, status codes, and notification state (#37030)
* Remove GET API registration-token (#36801)
* Support Actions `concurrency` syntax (#32751)
* Make PUBLIC_URL_DETECTION default to "auto" (#36955)
* SECURITY
* Bound PageSize in `ListUnadoptedRepositories` (#36884)
* FEATURES
* Support Actions `concurrency` syntax (#32751)
* Add terraform state registry (#36710)
* Instance-wide (global) info banner and maintenance mode (#36571)
* Support rendering OpenAPI spec (#36449)
* Add keyboard shortcuts for repository file and code search (#36416)
* Add support for archive-upload rpc (#36391)
* Add ability to download subpath archive (#36371)
* Add workflow dependencies visualization (#26062) (#36248) & Restyle Workflow Graph (#36912)
* Automatic generation of release notes (#35977)
* Add "Go to file", "Delete Directory" to repo file list page (#35911)
* Introduce "config edit-ini" sub command to help maintaining INI config file (#35735)
* Add button to re-run failed jobs in Actions (#36924)
* Support actions and reusable workflows from private repos (#32562)
* Add summary to action runs view (#36883)
* Add user badges (#36752)
* Add configurable permissions for Actions automatic tokens (#36173)
* Add per-runner "Disable/Pause" (#36776)
* Feature non-zipped actions artifacts (action v7 / nodejs / npm v6.2.0) (#36786)
* PERFORMANCE
* WorkflowDispatch API optionally return runid (#36706)
* Add render cache for SVG icons (#36863)
* Load `mentionValues` asynchronously (#36739)
* Lazy-load some Vue components, fix heatmap chunk loading on every page (#36719)
* Load heatmap data asynchronously (#36622)
* Use prev/next pagination for user profile activities page to speed up (#36642)
* Refactor cat-file batch operations and support `--batch-command` approach (#35775)
* Use merge tree to detect conflicts when possible (#36400)
* ENHANCEMENTS
* Implement logout redirection for reverse proxy auth setups (#36085) (#37171)
* Adds option to force update new branch in contents routes (#35592)
* Add viewer controller for mermaid (zoom, drag) (#36557)
* Add code editor setting dropdowns (#36534)
* Add `elk` layout support to mermaid (#36486)
* Add resolve/unresolve review comment API endpoints (#36441)
* Allow configuring default PR base branch (fixes #36412) (#36425)
* Add support for RPM Errata (updateinfo.xml) (#37125)
* Require additional user confirmation for making repo private (#36959)
* Add `actions.WORKFLOW_DIRS` setting (#36619)
* Avoid opening new tab when downloading actions logs (#36740)
* Implements OIDC RP-Initiated Logout (#36724)
* Show workflow link (#37070)
* Desaturate dark theme background colors (#37056)
* Refactor "org teams" page and help new users to "add member" to an org (#37051)
* Add webhook name field to improve webhook identification (#37025) (#37040)
* Make task list checkboxes clickable in the preview tab (#37010)
* Improve severity labels in Actions logs and tweak colors (#36993)
* Linkify URLs in Actions workflow logs (#36986)
* Allow text selection on checkbox labels (#36970)
* Support dark/light theme images in markdown (#36922)
* Enable native dark mode for swagger-ui (#36899)
* Rework checkbox styling, remove `input` border hover effect (#36870)
* Refactor storage content-type handling of ServeDirectURL (#36804)
* Use "Enable Gravatar" but not "Disable" (#36771)
* Use case-insensitive matching for Git error "Not a valid object name" (#36728)
* Add "Copy Source" to markup comment menu (#36726)
* Change image transparency grid to CSS (#36711)
* Add "Run" prefix for unnamed action steps (#36624)
* Persist actions log time display settings in `localStorage` (#36623)
* Use first commit title for multi-commit PRs and fix auto-focus title field (#36606)
* Improve BuildCaseInsensitiveLike with lowercase (#36598)
* Improve diff highlighting (#36583)
* Exclude cancelled runs from failure-only email notifications (#36569)
* Use full-file highlighting for diff sections (#36561)
* Color command/error logs in Actions log (#36538)
* Add paging headers (#36521)
* Improve timeline entries for WIP prefix changes in pull requests (#36518)
* Add FOLDER_ICON_THEME configuration option (#36496)
* Normalize guessed languages for code highlighting (#36450)
* Add chunked transfer encoding support for LFS uploads (#36380)
* Indicate when only optional checks failed (#36367)
* Add 'allow_maintainer_edit' API option for creating a pull request (#36283)
* Support closing keywords with URL references (#36221)
* Improve diff file headers (#36215)
* Fix and enhance comment editor monospace toggle (#36181)
* Add git.DIFF_RENAME_SIMILARITY_THRESHOLD option (#36164)
* Add matching pair insertion to markdown textarea (#36121)
* Add sorting/filtering to admin user search API endpoint (#36112)
* Allow action user have read permission in public repo like other user (#36095)
* Disable matchBrackets in monaco (#36089)
* Use GitHub-style commit message for squash merge (#35987)
* Make composer registry support tar.gz and tar.bz2 and fix bugs (#35958)
* Add GITEA_PR_INDEX env variable to githooks (#35938)
* Add proper error message if session provider can not be created (#35520)
* Add button to copy file name in PR files (#35509)
* Move `X_FRAME_OPTIONS` setting from `cors` to `security` section (#30256)
* Add placeholder content for empty content page (#37114)
* Add `DEFAULT_DELETE_BRANCH_AFTER_MERGE` setting (#36917)
* Redirect to the only OAuth2 provider when no other login methods and fix various problems (#36901)
* Add admin badge to navbar avatar (#36790)
* Add `never` option to `PUBLIC_URL_DETECTION` configuration (#36785)
* Add background and run count to actions list page (#36707)
* Add icon to buttons "Close with Comment", "Close Pull Request", "Close Issue" (#36654)
* Add support for in_progress event in workflow_run webhook (#36979)
* Report commit status for pull_request_review events (#36589)
* Render merged pull request title as such in dashboard feed (#36479)
* Feature to be able to filter project boards by milestones (#36321)
* Use user id in noreply emails (#36550)
* Enable pagination on GiteaDownloader.getIssueReactions() (#36549)
* Remove striped tables in UI (#36509)
* Improve control char rendering and escape button styling (#37094)
* Support legacy run/job index-based URLs and refactor migration 326 (#37008)
* Add date to "No Contributions" tooltip (#36190)
* Show edit page confirmation dialog on tree view file change (#36130)
* Mention proc-receive in text for dashboard.resync_all_hooks func (#35991)
* Reuse selectable style for wiki (#35990)
* Support blue yellow colorblind theme (#35910)
* Support selecting theme on the footer (#35741)
* Improve online runner check (#35722)
* Add quick approve button on PR page (#35678)
* Enable commenting on expanded lines in PR diffs (#35662)
* Print PR-Title into tooltip for actions (#35579)
* Use explicit, stronger defaults for newly generated repo signing keys for Debian (#36236)
* Improve the compare page (#36261)
* Unify repo names in system notices (#36491)
* Move package settings to package instead of being tied to version (#37026)
* Add Actions API rerun endpoints for runs and jobs (#36768)
* Add branch_count to repository API (#35351) (#36743)
* Add created_by filter to SearchIssues (#36670)
* Allow admins to rename non-local users (#35970)
* Support updating branch via API (#35951)
* Add an option to automatically verify SSH keys from LDAP (#35927)
* Make "update file" API can create a new file when SHA is not set (#35738)
* Update issue.go with labels documentation (labels content, not ids) (#35522)
* Expose content_version for optimistic locking on issue and PR edits (#37035)
* Pass ServeHeaderOptions by value instead of pointer, fine tune httplib tests (#36982)
* BUGFIXES
* Frontend iframe renderer framework: 3D models, OpenAPI (#37233) (#37273)
* Fix CODEOWNERS absolute path matching. (#37244) (#37264)
* Swift registry metadata: preserve more JSON fields and accept empty metadata (#37254) (#37261)
* Fix user ssh key exporting and tests (#37256) (#37258)
* Fix team member avatar size and add tooltip (#37253)
* Fix commit title rendering in action run and blame (#37243) (#37251)
* Fix corrupted JSON caused by goccy library (#37214) (#37220)
* Add test for "fetch redirect", add CSS value validation for external render (#37207) (#37216)
* Fix incorrect concurrency check (#37205) (#37215)
* Fix handle missing base branch in PR commits API (#37193) (#37203)
* Fix encoding for Matrix Webhooks (#37190) (#37201)
* Fix handle fork-only commits in compare API (#37185) (#37199)
* Indicate form field readonly via background, fix RunUser config (#37175, #37180) (#37178)
* Report structurally invalid workflows to users (#37116) (#37164)
* Fix API not persisting pull request unit config when has_pull_requests is not set (#36718)
* Rename CSS variables and improve colorblind themes (#36353)
* Hide `add-matcher` and `remove-matcher` from actions job logs (#36520)
* Prevent navigation keys from triggering actions during IME composition (#36540)
* Fix vertical alignment of `.commit-sign-badge` children (#36570)
* Fix duplicate startup warnings in admin panel (#36641)
* Fix CODEOWNERS review request attribution using comment metadata (#36348)
* Fix HTML tags appearing in wiki table of contents (#36284)
* Fix various bugs (#37096)
* Fix various legacy problems (#37092)
* Fix RPM Registry 404 when package name contains 'package' (#37087)
* Merge some standalone Vite entries into index.js (#37085)
* Fix various problems (#37077)
* Fix issue label deletion with Actions tokens (#37013)
* Hide delete branch or tag buttons in mirror or archived repositories. (#37006)
* Fix org contact email not clearable once set (#36975)
* Fix a bug when forking a repository in an organization (#36950)
* Preserve sort order of exclusive labels from template repo (#36931)
* Make container registry support Apple Container (basic auth) (#36920)
* Fix the wrong push commits in the pull request when force push (#36914)
* Add class "list-header-filters" to the div for projects (#36889)
* Fix dbfs error handling (#36844)
* Fix incorrect viewed files counter if reverted change was viewed (#36819)
* Refactor avatar package, support default avatar fallback (#36788)
* Fix README symlink resolution in subdirectories like .github (#36775)
* Fix CSS stacking context issue in actions log (#36749)
* Add gpg signing for merge rebase and update by rebase (#36701)
* Delete non-exist branch should return 404 (#36694)
* Fix `TestActionsCollaborativeOwner` (#36657)
* Fix multi-arch Docker build SIGILL by splitting frontend stage (#36646)
* Fix linguist-detectable attribute being ignored for configuration files (#36640)
* Fix state desync in ComboMarkdownEditor (#36625)
* Unify DEFAULT_SHOW_FULL_NAME output in templates and dropdown (#36597)
* Pull Request Pusher should be the author of the merge (#36581)
* Fix various version parsing problems (#36553)
* Fix highlight diff result (#36539)
* Fix mirror sync parser and fix mirror messages (#36504)
* Fix bug when list pull request commits (#36485)
* Fix various bugs (#36446)
* Fix issue filter menu layout (#36426)
* Restrict branch naming when new change matches with protection rules (#36405)
* Fix link/origin referrer and login redirect (#36279)
* Generate IDs for HTML headings without id attribute (#36233)
* Use a migration test instead of a wrong test which populated the meta test repositories and fix a migration bug (#36160)
* Fix issue close timeline icon (#36138)
* Fix diff blob excerpt expansion (#35922)
* Fix external render (#35727)
* Fix review request webhook bug (#35339) (#35723)
* Fix shutdown waitgroup panic (#35676)
* Cleanup ActionRun creation (#35624)
* Fix possible bug when migrating issues/pull requests (#33487)
* Various fixes (#36697)
* Apply notify/register mail flags during install load (#37120)
* Repair duration display for bad stopped timestamps (#37121)
* Fix(upgrade.sh): use HTTPS for GPG key import and restore SELinux context after upgrade (#36930)
* Fix various trivial problems (#36921)
* Fix various trivial problems (#36953)
* Fix NuGet package upload error handling (#37074)
* Fix CodeQL code scanning alerts (#36858)
* Refactor issue sidebar and fix various problems (#37045)
* Fix various problems (#37029)
* Fix relative-time RangeError (#37021)
* Fix chroma lexer mapping (#36629)
* Fix typos and grammar in English locale (#36751)
* Fix milestone/project text overflow in issue sidebar (#36741)
* Fix `no-content` message not rendering after comment edit (#36733)
* Fix theme loading in development (#36605)
* Fix workflow run jobs API returning null steps (#36603)
* Fix timeline event layout overflow with long content (#36595)
* Fix minor UI issues in runner edit page (#36590)
* Fix incorrect vendored detections (#36508)
* Fix editorconfig not respected in PR Conversation view (#36492)
* Don't create self-references in merged PRs (#36490)
* Fix potential incorrect runID in run status update (#36437)
* Fix file-tree ui error when adding files to repo without commits (#36312)
* Improve image captcha contrast for dark mode (#36265)
* Fix panic in blame view when a file has only a single commit (#36230)
* Fix spelling error in migrate-storage cmd utility (#36226)
* Fix code highlighting on blame page (#36157)
* Fix nilnil in onedev downloader (#36154)
* Fix actions lint (#36029)
* Fix oauth2 session gob register (#36017)
* Fix Arch repo pacman.conf snippet (#35825)
* Fix a number of `strictNullChecks`-related issues (#35795)
* Fix URLJoin, markup render link reoslving, sign-in/up/linkaccount page common data (#36861)
* Hide delete directory button for mirror or archive repository and disable the menu item if user have no permission (#36384)
* Update message severity colors, fix navbar double border (#37019)
* Inline and lazy-load EasyMDE CSS, fix border colors (#36714)
* Closed milestones with no issues now show as 100% completed (#36220)
* Add test for ExtendCommentTreePathLength migration and fix bugs (#35791)
* Only turn links to current instance into hash links (#36237)
* Fix typos in code comments: doesnt, dont, wont (#36890)
* REFACTOR
* Clean up and improve non-gitea js error filter (#37148) (#37155)
* Always show owner/repo name in compare page dropdowns (#37172) (#37200)
* Remove dead CSS rules (#37173) (#37177)
* Replace Monaco with CodeMirror (#36764)
* Replace CSRF cookie with `CrossOriginProtection` (#36183)
* Replace index with id in actions routes (#36842)
* Remove unnecessary function parameter (#35765)
* Move jobparser from act repository to Gitea (#36699)
* Refactor compare router param parse (#36105)
* Optimize 'refreshAccesses' to perform update without removing then adding (#35702)
* Clean up checkbox cursor styles (#37016)
* Remove undocumented support of signing key in the repository git configuration file (#36143)
* Switch `cmd/` to use constructor functions. (#36962)
* Use `relative-time` to render absolute dates (#36238)
* Some refactors about GetMergeBase (#36186)
* Some small refactors (#36163)
* Use gitRepo as parameter instead of repopath when invoking sign functions (#36162)
* Move blame to gitrepo (#36161)
* Move some functions to gitrepo package to reduce RepoPath reference directly (#36126)
* Use gitrepo's clone and push when possible (#36093)
* Remove mermaid margin workaround (#35732)
* Move some functions to gitrepo package (#35543)
* Move GetDiverging functions to gitrepo (#35524)
* Use global lock instead of status pool for cron lock (#35507)
* Use explicit mux instead of DefaultServeMux (#36276)
* Use gitrepo's push function (#36245)
* Pass request context to generateAdditionalHeadersForIssue (#36274)
* Move assign project when creating pull request to the same database transaction (#36244)
* Move catfile batch to a sub package of git module (#36232)
* Use gitrepo.Repository instead of wikipath (#35398)
* Use experimental go json v2 library (#35392)
* Refactor template render (#36438)
* Refactor GetRepoRawDiffForFile to avoid unnecessary pipe or goroutine (#36434)
* Refactor text utility classes to Tailwind CSS (#36703)
* Refactor git command stdio pipe (#36422)
* Refactor git command context & pipeline (#36406)
* Refactor git command stdio pipe (#36393)
* Remove unused functions (#36672)
* Refactor Actions Token Access (#35688)
* Move commit related functions to gitrepo package (#35600)
* Move archive function to repo_model and gitrepo (#35514)
* Move some functions to gitrepo package (#35503)
* Use git model to detect whether branch exist instead of gitrepo method (#35459)
* Some refactor for repo path (#36251)
* Extract helper functions from SearchIssues (#36158)
* Refactor merge conan and container auth preserve actions taskID (#36560)
* Refactor Nuget Auth to reuse Basic Auth Token Validation (#36558)
* Refactor ActionsTaskID (#36503)
* Refactor auth middleware (#36848)
* Refactor code render and render control chars (#37078)
* Clean up AppURL, remove legacy origin-url webcomponent (#37090)
* Remove `util.URLJoin` and replace all callers with direct path concatenation (#36867)
* Replace legacy tw-flex utility classes with flex-text-block/inline (#36778)
* Mark unused&immature activitypub as "not implemented" (#36789)
* TESTING
* Add e2e tests for server push events (#36879)
* Rework e2e tests (#36634)
* Add e2e reaction test, improve accessibility, enable parallel testing (#37081)
* Increase e2e test timeouts on CI to fix flaky tests (#37053)
* BUILD
* Upgrade go-git to v5.18.0 (#37269)
* Replace rollup-plugin-license with rolldown-license-plugin (#37130) (#37158)
* Bump min go version to 1.26.2 (#37139) (#37143)
* Convert locale files from ini to json format (#35489)
* Bump golangci-lint to 2.7.2, enable modernize stringsbuilder (#36180)
* Port away from `flake-utils` (#35675)
* Remove nolint (#36252)
* Update the Unlicense copy to latest version (#36636)
* Update to go 1.26.0 and golangci-lint 2.9.0 (#36588)
* Replace `google/go-licenses` with custom generation (#36575)
* Update go dependencies (#36548)
* Bump appleboy/git-push-action from 1.0.0 to 1.2.0 (#36306)
* Remove fomantic form module (#36222)
* Bump setup-node to v6, re-enable cache (#36207)
* Bump crowdin/github-action from 1 to 2 (#36204)
* Revert "Bump alpine to 3.23 (#36185)" (#36202)
* Update chroma to v2.21.1 (#36201)
* Bump astral-sh/setup-uv from 6 to 7 (#36198)
* Bump docker/build-push-action from 5 to 6 (#36197)
* Bump aws-actions/configure-aws-credentials from 4 to 5 (#36196)
* Bump dev-hanz-ops/install-gh-cli-action from 0.1.0 to 0.2.1 (#36195)
* Add JSON linting (#36192)
* Enable dependabot for actions (#36191)
* Bump alpine to 3.23 (#36185)
* Update chroma to v2.21.0 (#36171)
* Update JS deps and eslint enhancements (#36147)
* Update JS deps (#36091)
* update golangci-lint to v2.7.0 (#36079)
* Update JS deps, fix deprecations (#36040)
* Update JS deps (#35978)
* Add toolchain directive to go.mod (#35901)
* Move `gitea-vet` to use `go tool` (#35878)
* Update to go 1.25.4 (#35877)
* Enable TypeScript `strictNullChecks` (#35843)
* Enable `vue/require-typed-ref` eslint rule (#35764)
* Update JS dependencies (#35759)
* Move `codeformat` folder to tools (#35758)
* Update dependencies (#35733)
* Bump happy-dom from 20.0.0 to 20.0.2 (#35677)
* Bump setup-go to v6 (#35660)
* Update JS deps, misc tweaks (#35643)
* Bump happy-dom from 19.0.2 to 20.0.0 (#35625)
* Use bundled version of spectral (#35573)
* Update JS and PY deps (#35565)
* Bump github.com/wneessen/go-mail from 0.6.2 to 0.7.1 (#35557)
* Migrate from webpack to vite (#37002)
* Update JS dependencies and misc tweaks (#37064)
* Update to eslint 10 (#36925)
* Optimize Docker build with dependency layer caching (#36864)
* Update JS deps (#36850)
* Update tool dependencies and fix new lint issues (#36702)
* Remove redundant linter rules (#36658)
* Move Fomantic dropdown CSS to custom module (#36530)
* Remove and forbid `@ts-expect-error` (#36513)
* Refactor git command stderr handling (#36402)
* Enable gocheckcompilerdirectives linter (#36156)
* Replace `lint-go-gopls` with additional `govet` linters (#36028)
* Update golangci-lint to v2.6.0 (#35801)
* Misc tool tweaks (#35734)
* Add cache to container build (#35697)
* Upgrade vite (#37126)
* Update `setup-uv` to v8.0.0 (#37101)
* Upgrade `go-git` to v5.17.2 and related dependencies (#37060)
* Raise minimum Node.js version to 22.18.0 (#37058)
* Upgrade `golang.org/x/image` to v0.38.0 (#37054)
* Update minimum go version to 1.26.1, golangci-lint to 2.11.2, fix test style (#36876)
* Enable eslint concurrency (#36878)
* Vendor relative-time-element as local web component (#36853)
* Update material-icon-theme v5.32.0 (#36832)
* Update Go dependencies (#36781)
* Upgrade minimatch (#36760)
* Remove i18n backport tool at the moment because of translation format changed (#36643)
* Update emoji data for Unicode 16 (#36596)
* Update JS dependencies, adjust webpack config, misc fixes (#36431)
* Update material-icon-theme to v5.31.0 (#36427)
* Update JS and PY deps (#36383)
* Bump alpine to 3.23, add platforms to `docker-dryrun` (#36379)
* Update JS deps (#36354)
* Update goldmark to v1.7.16 (#36343)
* Update chroma to v2.22.0 (#36342)
* DOCS
* Update AI Contribution Policy (#37022)
* Update AGENTS.md with additional guidelines (#37018)
* Add missing cron tasks to example ini (#37012)
* Add AI Contribution Policy to CONTRIBUTING.md (#36651)
* Minor punctuation improvement in CONTRIBUTING.md (#36291)
* Add documentation for markdown anchor post-processing (#36443)
* MISC
* Correct spelling (#36783)
* Update Nix flake (#37110)
* Update Nix flake (#37024)
* Add valid github scopes (#36977)
* Update Nix flake (#36943)
* Update Nix flake (#36902)
* Update Nix flake (#36857)
* Update Nix flake (#36787)
-1
View File
@@ -1 +0,0 @@
@AGENTS.md
+160 -58
View File
@@ -1,5 +1,5 @@
// Copyright 2026 Moko Consulting. All rights reserved.
// SPDX-License-Identifier: MIT
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package issues
@@ -10,87 +10,185 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
// CustomFieldDefinition defines a custom field for a repository's issues
type CustomFieldDefinition struct {
ID int64 `xorm:"pk autoincr" json:"id"`
RepoID int64 `xorm:"INDEX NOT NULL" json:"repo_id"`
Name string `xorm:"NOT NULL" json:"name"`
FieldType string `xorm:"NOT NULL" json:"field_type"` // text, number, date, dropdown, checkbox
Description string `json:"description"`
Required bool `xorm:"NOT NULL DEFAULT false" json:"required"`
Position int `xorm:"NOT NULL DEFAULT 0" json:"position"`
Options string `xorm:"TEXT" json:"options"` // JSON array for dropdown options
DefaultVal string `json:"default_value"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created" json:"created_at"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated" json:"updated_at"`
}
func init() {
db.RegisterModel(new(CustomFieldDefinition))
db.RegisterModel(new(CustomFieldDef))
db.RegisterModel(new(CustomFieldValue))
}
// CustomFieldValue stores the value of a custom field for a specific issue
// CustomFieldType represents the data type of a custom field.
type CustomFieldType string
const (
CustomFieldTypeText CustomFieldType = "text"
CustomFieldTypeNumber CustomFieldType = "number"
CustomFieldTypeDate CustomFieldType = "date"
CustomFieldTypeDropdown CustomFieldType = "dropdown"
CustomFieldTypeCheckbox CustomFieldType = "checkbox"
CustomFieldTypeURL CustomFieldType = "url"
)
// CustomFieldScope determines where the field appears.
type CustomFieldScope string
const (
CustomFieldScopeIssue CustomFieldScope = "issue" // appears in issue sidebar
CustomFieldScopeRepo CustomFieldScope = "repo" // appears in repo settings metadata
)
// CustomFieldDef defines a custom field at the org level.
// owner_id = org ID, scope = issue or repo.
// repo_id is kept for backward compat but 0 for org-level definitions.
type CustomFieldDef struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'owner_id'"` // org that owns this field
RepoID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'repo_id'"` // 0 = org-level (inherited by all repos)
Scope CustomFieldScope `xorm:"VARCHAR(10) NOT NULL DEFAULT 'issue' 'scope'"`
Name string `xorm:"NOT NULL"`
FieldType CustomFieldType `xorm:"VARCHAR(20) NOT NULL 'field_type'"`
Description string `xorm:"TEXT"`
Options string `xorm:"TEXT"` // JSON array for dropdown options
Required bool `xorm:"NOT NULL DEFAULT false"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (CustomFieldDef) TableName() string {
return "custom_field_def"
}
// CustomFieldValue stores a custom field value for an entity (issue or repo).
type CustomFieldValue struct {
ID int64 `xorm:"pk autoincr" json:"id"`
IssueID int64 `xorm:"INDEX NOT NULL" json:"issue_id"`
FieldID int64 `xorm:"INDEX NOT NULL" json:"field_id"`
Value string `xorm:"TEXT" json:"value"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created" json:"created_at"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated" json:"updated_at"`
ID int64 `xorm:"pk autoincr"`
EntityID int64 `xorm:"INDEX NOT NULL 'entity_id'"` // issue ID or repo ID
EntityType string `xorm:"VARCHAR(10) NOT NULL DEFAULT 'issue' 'entity_type'"` // "issue" or "repo"
FieldID int64 `xorm:"INDEX NOT NULL 'field_id'"`
Value string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
// GetCustomFieldsByRepoID returns all custom field definitions for a repo
func GetCustomFieldsByRepoID(ctx context.Context, repoID int64) ([]*CustomFieldDefinition, error) {
fields := make([]*CustomFieldDefinition, 0)
return fields, db.GetEngine(ctx).Where("repo_id = ?", repoID).OrderBy("position ASC").Find(&fields)
func (CustomFieldValue) TableName() string {
return "custom_field_value"
}
// GetCustomFieldByID returns a custom field definition by ID
func GetCustomFieldByID(ctx context.Context, id int64) (*CustomFieldDefinition, error) {
field := &CustomFieldDefinition{ID: id}
has, err := db.GetEngine(ctx).Get(field)
// ──────────────────────────────────────────────────────────────────────
// Queries for org-level field definitions
// ──────────────────────────────────────────────────────────────────────
// GetCustomFieldsByOwner returns all active field definitions for an org with a given scope.
func GetCustomFieldsByOwner(ctx context.Context, ownerID int64, scope CustomFieldScope) ([]*CustomFieldDef, error) {
fields := make([]*CustomFieldDef, 0, 10)
return fields, db.GetEngine(ctx).
Where("owner_id = ? AND scope = ? AND is_active = ?", ownerID, scope, true).
OrderBy("sort_order ASC, id ASC").
Find(&fields)
}
// GetAllCustomFieldsByOwner returns all field definitions for an org (including inactive).
func GetAllCustomFieldsByOwner(ctx context.Context, ownerID int64) ([]*CustomFieldDef, error) {
fields := make([]*CustomFieldDef, 0, 10)
return fields, db.GetEngine(ctx).
Where("owner_id = ?", ownerID).
OrderBy("scope ASC, sort_order ASC, id ASC").
Find(&fields)
}
// GetCustomFieldsByOwnerAndScope returns all fields for an org filtered by scope.
func GetCustomFieldsByOwnerAndScope(ctx context.Context, ownerID int64, scope CustomFieldScope) ([]*CustomFieldDef, error) {
fields := make([]*CustomFieldDef, 0, 10)
return fields, db.GetEngine(ctx).
Where("owner_id = ? AND scope = ?", ownerID, scope).
OrderBy("sort_order ASC, id ASC").
Find(&fields)
}
// ──────────────────────────────────────────────────────────────────────
// Backward-compatible queries (load by repo's owner)
// ──────────────────────────────────────────────────────────────────────
// GetCustomFieldsByRepo returns active issue-scoped fields for a repo's org.
// This is the main query used by the issue sidebar.
func GetCustomFieldsByRepo(ctx context.Context, repoID int64) ([]*CustomFieldDef, error) {
// First try org-level fields (owner_id != 0, repo_id = 0)
// Fall back to legacy repo-level fields (repo_id = repoID)
fields := make([]*CustomFieldDef, 0, 10)
return fields, db.GetEngine(ctx).
Where("((owner_id != 0 AND repo_id = 0) OR repo_id = ?) AND scope = ? AND is_active = ?",
repoID, CustomFieldScopeIssue, true).
OrderBy("sort_order ASC, id ASC").
Find(&fields)
}
// GetAllCustomFieldsByRepo returns all field definitions for a repo (for settings page).
func GetAllCustomFieldsByRepo(ctx context.Context, repoID int64) ([]*CustomFieldDef, error) {
fields := make([]*CustomFieldDef, 0, 10)
return fields, db.GetEngine(ctx).
Where("repo_id = ?", repoID).
OrderBy("sort_order ASC, id ASC").
Find(&fields)
}
// ──────────────────────────────────────────────────────────────────────
// Field definition CRUD
// ──────────────────────────────────────────────────────────────────────
// GetCustomFieldDefByID returns a single field definition.
func GetCustomFieldDefByID(ctx context.Context, id int64) (*CustomFieldDef, error) {
field := new(CustomFieldDef)
has, err := db.GetEngine(ctx).ID(id).Get(field)
if err != nil {
return nil, err
}
if !has {
return nil, nil
return nil, db.ErrNotExist{Resource: "CustomFieldDef", ID: id}
}
return field, nil
}
// CreateCustomField creates a new custom field definition
func CreateCustomField(ctx context.Context, field *CustomFieldDefinition) error {
// CreateCustomFieldDef creates a new custom field definition.
func CreateCustomFieldDef(ctx context.Context, field *CustomFieldDef) error {
_, err := db.GetEngine(ctx).Insert(field)
return err
}
// UpdateCustomField updates a custom field definition
func UpdateCustomField(ctx context.Context, field *CustomFieldDefinition) error {
// UpdateCustomFieldDef updates a custom field definition.
func UpdateCustomFieldDef(ctx context.Context, field *CustomFieldDef) error {
_, err := db.GetEngine(ctx).ID(field.ID).AllCols().Update(field)
return err
}
// DeleteCustomField deletes a custom field and all its values
func DeleteCustomField(ctx context.Context, id int64) error {
sess := db.GetEngine(ctx)
if _, err := sess.Where("field_id = ?", id).Delete(&CustomFieldValue{}); err != nil {
// DeleteCustomFieldDef deletes a field definition and all its values.
func DeleteCustomFieldDef(ctx context.Context, id int64) error {
if _, err := db.GetEngine(ctx).Where("field_id = ?", id).Delete(new(CustomFieldValue)); err != nil {
return err
}
_, err := sess.ID(id).Delete(&CustomFieldDefinition{})
_, err := db.GetEngine(ctx).ID(id).Delete(new(CustomFieldDef))
return err
}
// GetCustomFieldValues returns all custom field values for an issue
func GetCustomFieldValues(ctx context.Context, issueID int64) ([]*CustomFieldValue, error) {
values := make([]*CustomFieldValue, 0)
return values, db.GetEngine(ctx).Where("issue_id = ?", issueID).Find(&values)
// ──────────────────────────────────────────────────────────────────────
// Field values — generic entity-based (works for issues and repos)
// ──────────────────────────────────────────────────────────────────────
// GetCustomFieldValuesMap returns field_id -> value for an entity.
func GetCustomFieldValuesMap(ctx context.Context, entityID int64) (map[int64]string, error) {
values := make([]*CustomFieldValue, 0, 10)
if err := db.GetEngine(ctx).Where("entity_id = ?", entityID).Find(&values); err != nil {
return nil, err
}
result := make(map[int64]string, len(values))
for _, v := range values {
result[v.FieldID] = v.Value
}
return result, nil
}
// SetCustomFieldValue sets or updates a custom field value for an issue
func SetCustomFieldValue(ctx context.Context, issueID, fieldID int64, value string) error {
existing := &CustomFieldValue{}
has, err := db.GetEngine(ctx).Where("issue_id = ? AND field_id = ?", issueID, fieldID).Get(existing)
// SetCustomFieldValue creates or updates a single custom field value.
func SetCustomFieldValue(ctx context.Context, entityID, fieldID int64, value string) error {
existing := new(CustomFieldValue)
has, err := db.GetEngine(ctx).Where("entity_id = ? AND field_id = ?", entityID, fieldID).Get(existing)
if err != nil {
return err
}
@@ -100,15 +198,19 @@ func SetCustomFieldValue(ctx context.Context, issueID, fieldID int64, value stri
return err
}
_, err = db.GetEngine(ctx).Insert(&CustomFieldValue{
IssueID: issueID,
FieldID: fieldID,
Value: value,
EntityID: entityID,
FieldID: fieldID,
Value: value,
})
return err
}
// DeleteCustomFieldValue deletes a specific custom field value
func DeleteCustomFieldValue(ctx context.Context, issueID, fieldID int64) error {
_, err := db.GetEngine(ctx).Where("issue_id = ? AND field_id = ?", issueID, fieldID).Delete(&CustomFieldValue{})
return err
// SetCustomFieldValues sets multiple custom field values for an entity.
func SetCustomFieldValues(ctx context.Context, entityID int64, values map[int64]string) error {
for fieldID, value := range values {
if err := SetCustomFieldValue(ctx, entityID, fieldID, value); err != nil {
return err
}
}
return nil
}
+6
View File
@@ -76,6 +76,12 @@ type Issue struct {
Assignee *user_model.User `xorm:"-"`
isAssigneeLoaded bool `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
StatusID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'status_id'"`
Status *IssueStatusDef `xorm:"-"`
PriorityID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'priority_id'"`
PriorityDef *IssuePriorityDef `xorm:"-"`
TypeID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'type_id'"`
TypeDef *IssueTypeDef `xorm:"-"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package issues
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(IssuePriorityDef))
}
// IssuePriorityDef defines a custom issue priority at the org level.
type IssuePriorityDef struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
Name string `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(7)"`
Description string `xorm:"TEXT"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsDefault bool `xorm:"NOT NULL DEFAULT false 'is_default'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (IssuePriorityDef) TableName() string {
return "issue_priority_def"
}
// GetIssuePriorityDefsByOrg returns active priority definitions for an org.
// If none exist, seeds the org with default priorities automatically.
func GetIssuePriorityDefsByOrg(ctx context.Context, orgID int64) ([]*IssuePriorityDef, error) {
defs := make([]*IssuePriorityDef, 0, 10)
if err := db.GetEngine(ctx).
Where("org_id = ? AND is_active = ?", orgID, true).
OrderBy("sort_order ASC, id ASC").
Find(&defs); err != nil {
return nil, err
}
if len(defs) == 0 && orgID > 0 {
if err := seedDefaultIssuePriorities(ctx, orgID); err != nil {
return defs, nil // non-fatal
}
return GetIssuePriorityDefsByOrg(ctx, orgID)
}
return defs, nil
}
// seedDefaultIssuePriorities creates the standard priority presets for an org.
func seedDefaultIssuePriorities(ctx context.Context, orgID int64) error {
defaults := []*IssuePriorityDef{
{OrgID: orgID, Name: "Critical", Color: "#dc2626", Description: "Requires immediate attention", SortOrder: 1, IsActive: true},
{OrgID: orgID, Name: "High", Color: "#f97316", Description: "Should be addressed soon", SortOrder: 2, IsActive: true},
{OrgID: orgID, Name: "Medium", Color: "#eab308", Description: "Normal priority", SortOrder: 3, IsDefault: true, IsActive: true},
{OrgID: orgID, Name: "Low", Color: "#2563eb", Description: "Can wait", SortOrder: 4, IsActive: true},
}
for _, d := range defaults {
if _, err := db.GetEngine(ctx).Insert(d); err != nil {
return err
}
}
return nil
}
// GetAllIssuePriorityDefsByOrg returns all priority definitions (including inactive).
func GetAllIssuePriorityDefsByOrg(ctx context.Context, orgID int64) ([]*IssuePriorityDef, error) {
defs := make([]*IssuePriorityDef, 0, 10)
return defs, db.GetEngine(ctx).
Where("org_id = ?", orgID).
OrderBy("sort_order ASC, id ASC").
Find(&defs)
}
// GetIssuePriorityDefByID returns a single priority definition.
func GetIssuePriorityDefByID(ctx context.Context, id int64) (*IssuePriorityDef, error) {
def := new(IssuePriorityDef)
has, err := db.GetEngine(ctx).ID(id).Get(def)
if err != nil {
return nil, err
}
if !has {
return nil, db.ErrNotExist{Resource: "IssuePriorityDef", ID: id}
}
return def, nil
}
// CreateIssuePriorityDef creates a new priority definition.
func CreateIssuePriorityDef(ctx context.Context, def *IssuePriorityDef) error {
_, err := db.GetEngine(ctx).Insert(def)
return err
}
// UpdateIssuePriorityDef updates a priority definition.
func UpdateIssuePriorityDef(ctx context.Context, def *IssuePriorityDef) error {
_, err := db.GetEngine(ctx).ID(def.ID).AllCols().Update(def)
return err
}
// DeleteIssuePriorityDef deletes a priority definition and clears references on issues.
func DeleteIssuePriorityDef(ctx context.Context, id int64) error {
if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET priority_id = 0 WHERE priority_id = ?", id); err != nil {
return err
}
_, err := db.GetEngine(ctx).ID(id).Delete(new(IssuePriorityDef))
return err
}
// SetIssuePriorityID updates the priority_id on an issue.
func SetIssuePriorityID(ctx context.Context, issueID, priorityID int64) error {
_, err := db.GetEngine(ctx).Exec("UPDATE issue SET priority_id = ? WHERE id = ?", priorityID, issueID)
return err
}
+18 -2
View File
@@ -49,8 +49,9 @@ type IssuesOptions struct { //nolint:revive // export stutter
UpdatedAfterUnix int64
UpdatedBeforeUnix int64
// prioritize issues from this repo
PriorityRepoID int64
IsArchived optional.Option[bool]
PriorityRepoID int64
IsArchived optional.Option[bool]
CustomFieldFilters map[int64]string // field_id → required value (AND semantics)
Owner *user_model.User // issues permission scope, it could be an organization or a user
Team *organization.Team // issues permission scope
Doer *user_model.User // issues permission scope
@@ -211,6 +212,20 @@ func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) {
// do not need to apply any condition
}
func applyCustomFieldCondition(sess *xorm.Session, opts *IssuesOptions) {
if len(opts.CustomFieldFilters) == 0 {
return
}
// Each filtered field adds a subquery: the issue must have a matching
// custom_field_value row for every specified field (AND semantics).
for fieldID, value := range opts.CustomFieldFilters {
subQuery := builder.Select("entity_id").From("custom_field_value").Where(
builder.Eq{"field_id": fieldID, "value": value, "entity_type": "issue"},
)
sess.And(builder.In("issue.id", subQuery))
}
}
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) {
if len(opts.RepoIDs) == 1 {
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
@@ -278,6 +293,7 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
}
applyLabelsCondition(sess, opts)
applyCustomFieldCondition(sess, opts)
if opts.Owner != nil {
sess.And(repo_model.UserOwnedRepoCond(opts.Owner.ID))
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package issues
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(IssueStatusDef))
}
// IssueStatusDef defines a custom issue status at the org level.
type IssueStatusDef struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
Name string `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(7)"` // hex color, e.g. "#e11d48"
Description string `xorm:"TEXT"`
ClosesIssue bool `xorm:"NOT NULL DEFAULT false 'closes_issue'"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (IssueStatusDef) TableName() string {
return "issue_status_def"
}
// ──────────────────────────────────────────────────────────────────────
// Queries
// ──────────────────────────────────────────────────────────────────────
// GetIssueStatusDefsByOrg returns active status definitions for an org.
// If none exist, seeds the org with default statuses automatically.
func GetIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDef, error) {
defs := make([]*IssueStatusDef, 0, 10)
if err := db.GetEngine(ctx).
Where("org_id = ? AND is_active = ?", orgID, true).
OrderBy("sort_order ASC, id ASC").
Find(&defs); err != nil {
return nil, err
}
if len(defs) == 0 && orgID > 0 {
if err := seedDefaultIssueStatuses(ctx, orgID); err != nil {
return defs, nil // non-fatal
}
return GetIssueStatusDefsByOrg(ctx, orgID)
}
return defs, nil
}
// seedDefaultIssueStatuses creates the standard status presets for an org.
func seedDefaultIssueStatuses(ctx context.Context, orgID int64) error {
defaults := []*IssueStatusDef{
{OrgID: orgID, Name: "In Progress", Color: "#2563eb", Description: "Work is actively being done", SortOrder: 1, IsActive: true},
{OrgID: orgID, Name: "Needs Info", Color: "#f59e0b", Description: "Waiting for more information", SortOrder: 2, IsActive: true},
{OrgID: orgID, Name: "Blocked", Color: "#dc2626", Description: "Cannot proceed due to dependency", SortOrder: 3, IsActive: true},
{OrgID: orgID, Name: "Resolved", Color: "#16a34a", Description: "Fix implemented and verified", ClosesIssue: true, SortOrder: 4, IsActive: true},
{OrgID: orgID, Name: "Won't Fix", Color: "#6b7280", Description: "Decided not to address", ClosesIssue: true, SortOrder: 5, IsActive: true},
{OrgID: orgID, Name: "Duplicate", Color: "#8b5cf6", Description: "Already tracked elsewhere", ClosesIssue: true, SortOrder: 6, IsActive: true},
}
for _, d := range defaults {
if _, err := db.GetEngine(ctx).Insert(d); err != nil {
return err
}
}
return nil
}
// GetAllIssueStatusDefsByOrg returns all status definitions (including inactive).
func GetAllIssueStatusDefsByOrg(ctx context.Context, orgID int64) ([]*IssueStatusDef, error) {
defs := make([]*IssueStatusDef, 0, 10)
return defs, db.GetEngine(ctx).
Where("org_id = ?", orgID).
OrderBy("sort_order ASC, id ASC").
Find(&defs)
}
// GetIssueStatusDefByID returns a single status definition.
func GetIssueStatusDefByID(ctx context.Context, id int64) (*IssueStatusDef, error) {
def := new(IssueStatusDef)
has, err := db.GetEngine(ctx).ID(id).Get(def)
if err != nil {
return nil, err
}
if !has {
return nil, db.ErrNotExist{Resource: "IssueStatusDef", ID: id}
}
return def, nil
}
// ──────────────────────────────────────────────────────────────────────
// CRUD
// ──────────────────────────────────────────────────────────────────────
// CreateIssueStatusDef creates a new status definition.
func CreateIssueStatusDef(ctx context.Context, def *IssueStatusDef) error {
_, err := db.GetEngine(ctx).Insert(def)
return err
}
// UpdateIssueStatusDef updates a status definition.
func UpdateIssueStatusDef(ctx context.Context, def *IssueStatusDef) error {
_, err := db.GetEngine(ctx).ID(def.ID).AllCols().Update(def)
return err
}
// DeleteIssueStatusDef deletes a status definition and clears references on issues.
func DeleteIssueStatusDef(ctx context.Context, id int64) error {
// Clear status_id on all issues that reference this definition
if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = 0 WHERE status_id = ?", id); err != nil {
return err
}
_, err := db.GetEngine(ctx).ID(id).Delete(new(IssueStatusDef))
return err
}
// ──────────────────────────────────────────────────────────────────────
// Issue status helpers
// ──────────────────────────────────────────────────────────────────────
// SetIssueStatusID updates the status_id on an issue.
func SetIssueStatusID(ctx context.Context, issueID, statusID int64) error {
_, err := db.GetEngine(ctx).Exec("UPDATE issue SET status_id = ? WHERE id = ?", statusID, issueID)
return err
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package issues
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(IssueTypeDef))
}
// IssueTypeDef defines a custom issue type at the org level.
type IssueTypeDef struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
Name string `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(7)"`
Description string `xorm:"TEXT"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsDefault bool `xorm:"NOT NULL DEFAULT false 'is_default'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (IssueTypeDef) TableName() string {
return "issue_type_def"
}
// GetIssueTypeDefsByOrg returns active type definitions for an org.
// Auto-seeds defaults if none exist.
func GetIssueTypeDefsByOrg(ctx context.Context, orgID int64) ([]*IssueTypeDef, error) {
defs := make([]*IssueTypeDef, 0, 10)
if err := db.GetEngine(ctx).
Where("org_id = ? AND is_active = ?", orgID, true).
OrderBy("sort_order ASC, id ASC").
Find(&defs); err != nil {
return nil, err
}
if len(defs) == 0 && orgID > 0 {
if err := seedDefaultIssueTypes(ctx, orgID); err != nil {
return defs, nil
}
return GetIssueTypeDefsByOrg(ctx, orgID)
}
return defs, nil
}
// GetAllIssueTypeDefsByOrg returns all type definitions (including inactive).
func GetAllIssueTypeDefsByOrg(ctx context.Context, orgID int64) ([]*IssueTypeDef, error) {
defs := make([]*IssueTypeDef, 0, 10)
return defs, db.GetEngine(ctx).
Where("org_id = ?", orgID).
OrderBy("sort_order ASC, id ASC").
Find(&defs)
}
// GetIssueTypeDefByID returns a single type definition.
func GetIssueTypeDefByID(ctx context.Context, id int64) (*IssueTypeDef, error) {
def := new(IssueTypeDef)
has, err := db.GetEngine(ctx).ID(id).Get(def)
if err != nil {
return nil, err
}
if !has {
return nil, db.ErrNotExist{Resource: "IssueTypeDef", ID: id}
}
return def, nil
}
func CreateIssueTypeDef(ctx context.Context, def *IssueTypeDef) error {
_, err := db.GetEngine(ctx).Insert(def)
return err
}
func UpdateIssueTypeDef(ctx context.Context, def *IssueTypeDef) error {
_, err := db.GetEngine(ctx).ID(def.ID).AllCols().Update(def)
return err
}
func DeleteIssueTypeDef(ctx context.Context, id int64) error {
if _, err := db.GetEngine(ctx).Exec("UPDATE issue SET type_id = 0 WHERE type_id = ?", id); err != nil {
return err
}
_, err := db.GetEngine(ctx).ID(id).Delete(new(IssueTypeDef))
return err
}
func SetIssueTypeID(ctx context.Context, issueID, typeID int64) error {
_, err := db.GetEngine(ctx).Exec("UPDATE issue SET type_id = ? WHERE id = ?", typeID, issueID)
return err
}
func seedDefaultIssueTypes(ctx context.Context, orgID int64) error {
defaults := []*IssueTypeDef{
{OrgID: orgID, Name: "Bug", Color: "#dc2626", SortOrder: 1, IsActive: true},
{OrgID: orgID, Name: "Feature", Color: "#2563eb", SortOrder: 2, IsDefault: true, IsActive: true},
{OrgID: orgID, Name: "Enhancement", Color: "#16a34a", SortOrder: 3, IsActive: true},
{OrgID: orgID, Name: "Task", Color: "#6b7280", SortOrder: 4, IsActive: true},
{OrgID: orgID, Name: "Documentation", Color: "#8b5cf6", SortOrder: 5, IsActive: true},
{OrgID: orgID, Name: "Security", Color: "#e11d48", SortOrder: 6, IsActive: true},
}
for _, d := range defaults {
if _, err := db.GetEngine(ctx).Insert(d); err != nil {
return err
}
}
return nil
}
+3 -2
View File
@@ -130,10 +130,11 @@ func GetLicenseKeyByID(ctx context.Context, id int64) (*LicenseKey, error) {
return key, nil
}
// ListLicenseKeys returns all keys for the given owner.
// ListLicenseKeys returns all keys for the given owner, master keys first.
func ListLicenseKeys(ctx context.Context, ownerID int64) ([]*LicenseKey, error) {
keys := make([]*LicenseKey, 0, 20)
return keys, db.GetEngine(ctx).Where("owner_id = ?", ownerID).Find(&keys)
return keys, db.GetEngine(ctx).Where("owner_id = ?", ownerID).
OrderBy("is_internal DESC, created_unix DESC").Find(&keys)
}
// SearchLicenseKeys searches keys for an owner by key prefix/raw, licensee, email, or domain.
+4
View File
@@ -32,6 +32,10 @@ type LicensePackage struct {
// AllowedChannels defines which update streams keys from this package
// can access. JSON array, e.g. ["stable","rc"]. Empty = all channels.
AllowedChannels string `xorm:"TEXT"`
// DomainRestriction is a comma-separated list of allowed domains.
// Keys generated from this package inherit this unless overridden.
// Empty = no restriction.
DomainRestriction string `xorm:"TEXT"`
IsActive bool `xorm:"NOT NULL DEFAULT true"`
IsArchived bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
+15 -15
View File
@@ -23,27 +23,27 @@ type UpdateStreamConfig struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"` // org or user
RepoID int64 `xorm:"INDEX NOT NULL DEFAULT 0"` // 0 = org-level default
StreamMode string `xorm:"NOT NULL DEFAULT 'joomla'"` // joomla, custom
StreamMode string `xorm:"NOT NULL DEFAULT 'joomla' 'stream_mode'"` // joomla, custom
Platform string `xorm:"NOT NULL DEFAULT 'joomla'"` // joomla, dolibarr, both, wordpress, prestashop, drupal
LicensingEnabled bool `xorm:"NOT NULL DEFAULT false"` // master toggle for licensing system
RequireKey bool `xorm:"NOT NULL DEFAULT false"` // require license key for update feed
FeedVisibility string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'public'"` // public, no-download, hidden
DownloadGating string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'none'"` // none, all, prerelease
SupportURL string `xorm:"TEXT"` // wiki or external support page URL
KeyPrefix string `xorm:"VARCHAR(20)"` // org-specific license key prefix (e.g. "ACME")
LicensingEnabled bool `xorm:"NOT NULL DEFAULT false 'licensing_enabled'"` // master toggle
RequireKey bool `xorm:"NOT NULL DEFAULT false 'require_key'"` // require license key for update feed
FeedVisibility string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'public' 'feed_visibility'"` // public, no-download, hidden
DownloadGating string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'none' 'download_gating'"` // none, all, prerelease
SupportURL string `xorm:"TEXT 'support_url'"` // wiki or external support page URL
KeyPrefix string `xorm:"VARCHAR(20) 'key_prefix'"` // org-specific license key prefix (e.g. "ACME")
// Extension metadata — used in update feed generation.
ExtensionName string `xorm:"TEXT"` // element identifier (e.g. pkg_mokowaas, com_mokowaas)
DisplayName string `xorm:"TEXT"` // human-readable name (e.g. "Package - MokoWaaS")
ExtensionName string `xorm:"TEXT 'extension_name'"` // element identifier (e.g. pkg_mokowaas, com_mokowaas)
DisplayName string `xorm:"TEXT 'display_name'"` // human-readable name (e.g. "Package - MokoWaaS")
Description string `xorm:"TEXT"` // short description for update feeds
ExtensionType string `xorm:"VARCHAR(50)"` // component, module, plugin, package, template, library
ExtensionType string `xorm:"VARCHAR(50) 'extension_type'"` // component, module, plugin, package, template, library
Maintainer string `xorm:"TEXT"` // maintainer/author name
MaintainerURL string `xorm:"TEXT"` // maintainer website
InfoURL string `xorm:"TEXT"` // extension info/product page URL
TargetVersion string `xorm:"TEXT"` // target platform version regex (e.g. "(5|6)\..*")
PHPMinimum string `xorm:"VARCHAR(20)"` // minimum PHP version (e.g. "8.1")
MaintainerURL string `xorm:"TEXT 'maintainer_url'"` // maintainer website
InfoURL string `xorm:"TEXT 'info_url'"` // extension info/product page URL
TargetVersion string `xorm:"TEXT 'target_version'"` // target platform version regex (e.g. "(5|6)\..*")
PHPMinimum string `xorm:"VARCHAR(20) 'php_minimum'"` // minimum PHP version (e.g. "8.1")
// CustomStreams is a JSON array of stream definitions.
// Each entry: {"name":"lts","suffix":"-lts","description":"Long-term support"}
CustomStreams string `xorm:"TEXT"`
CustomStreams string `xorm:"TEXT 'custom_streams'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED"`
}
+8
View File
@@ -420,6 +420,14 @@ func prepareMigrationTasks() []*migration {
newMigration(340, "Sync license system columns (key_raw, payment_ref, heartbeat, archive, metadata)", v1_27.SyncLicenseSystemColumns),
newMigration(341, "Add parent_org_id to user table for enterprise sub-org hierarchy", v1_27.AddParentOrgIDToUser),
newMigration(342, "Add is_hidden to repository for three-level visibility", v1_27.AddIsHiddenToRepository),
newMigration(343, "Add custom field tables for issue custom fields", v1_27.AddCustomFieldTables),
newMigration(344, "Add domain_restriction to license_package table", v1_27.AddDomainRestrictionToLicensePackage),
newMigration(345, "Migrate custom fields to org-level with scope", v1_27.MigrateCustomFieldsToOrgLevel),
newMigration(346, "Add issue status definitions table", v1_27.AddIssueStatusDefTable),
newMigration(347, "Add repo manifest table", v1_27.AddRepoManifestTable),
newMigration(348, "Add issue priority definitions table", v1_27.AddIssuePriorityDefTable),
newMigration(349, "Add security scanning tables", v1_27.AddSecurityScanningTables),
newMigration(350, "Add issue type definitions table", v1_27.AddIssueTypeDefTable),
}
return preparedMigrations
}
-33
View File
@@ -1,33 +0,0 @@
// Copyright 2026 Moko Consulting. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_25
import "xorm.io/xorm"
func AddCustomFieldTables(x *xorm.Engine) error {
type CustomFieldDefinition struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL"`
Name string `xorm:"NOT NULL"`
FieldType string `xorm:"NOT NULL"` // text, number, date, dropdown, checkbox
Description string
Required bool `xorm:"NOT NULL DEFAULT false"`
Position int `xorm:"NOT NULL DEFAULT 0"`
Options string `xorm:"TEXT"` // JSON array for dropdown options
DefaultVal string
CreatedUnix int64 `xorm:"INDEX created"`
UpdatedUnix int64 `xorm:"INDEX updated"`
}
type CustomFieldValue struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX NOT NULL"`
FieldID int64 `xorm:"INDEX NOT NULL"`
Value string `xorm:"TEXT"`
CreatedUnix int64 `xorm:"INDEX created"`
UpdatedUnix int64 `xorm:"INDEX updated"`
}
return x.Sync(new(CustomFieldDefinition), new(CustomFieldValue))
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"xorm.io/xorm"
)
type customFieldDef343 struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL 'repo_id'"`
Name string `xorm:"NOT NULL"`
FieldType string `xorm:"VARCHAR(20) NOT NULL 'field_type'"`
Description string `xorm:"TEXT"`
Options string `xorm:"TEXT"`
Required bool `xorm:"NOT NULL DEFAULT false"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (customFieldDef343) TableName() string {
return "custom_field_def"
}
type customFieldValue343 struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX NOT NULL 'issue_id'"`
FieldID int64 `xorm:"INDEX NOT NULL 'field_id'"`
Value string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (customFieldValue343) TableName() string {
return "custom_field_value"
}
// AddCustomFieldTables creates the custom_field_def and custom_field_value tables.
func AddCustomFieldTables(x *xorm.Engine) error {
return x.Sync(new(customFieldDef343), new(customFieldValue343))
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
func AddDomainRestrictionToLicensePackage(x *xorm.Engine) error {
type LicensePackage struct {
DomainRestriction string `xorm:"TEXT"`
}
return x.Sync(new(LicensePackage))
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// MigrateCustomFieldsToOrgLevel adds owner_id, scope to custom_field_def
// and renames issue_id to entity_id + adds entity_type in custom_field_value.
func MigrateCustomFieldsToOrgLevel(x *xorm.Engine) error {
// Add new columns to custom_field_def
type CustomFieldDef struct {
OwnerID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'owner_id'"`
Scope string `xorm:"VARCHAR(10) NOT NULL DEFAULT 'issue' 'scope'"`
}
if err := x.Sync(new(CustomFieldDef)); err != nil {
return err
}
// Add entity_type and entity_id to custom_field_value
type CustomFieldValue struct {
EntityID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'entity_id'"`
EntityType string `xorm:"VARCHAR(10) NOT NULL DEFAULT 'issue' 'entity_type'"`
}
if err := x.Sync(new(CustomFieldValue)); err != nil {
return err
}
// Migrate existing data: copy issue_id to entity_id where entity_id is 0
if _, err := x.Exec("UPDATE custom_field_value SET entity_id = issue_id WHERE entity_id = 0 AND issue_id != 0"); err != nil {
return err
}
// Set issue_id default to 0 so new inserts don't require it
_, err := x.Exec("ALTER TABLE custom_field_value MODIFY COLUMN issue_id bigint NOT NULL DEFAULT 0")
return err
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddIssueStatusDefTable creates the issue_status_def table and adds
// status_id to the issue table.
func AddIssueStatusDefTable(x *xorm.Engine) error {
type IssueStatusDef struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
Name string `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(7)"`
Description string `xorm:"TEXT"`
ClosesIssue bool `xorm:"NOT NULL DEFAULT false 'closes_issue'"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix int64 `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix int64 `xorm:"UPDATED 'updated_unix'"`
}
if err := x.Sync(new(IssueStatusDef)); err != nil {
return err
}
// Add status_id column to issue table
type Issue struct {
StatusID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'status_id'"`
}
return x.Sync(new(Issue))
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddRepoManifestTable creates the repo_manifest table for storing
// moko-platform manifest settings per repository.
func AddRepoManifestTable(x *xorm.Engine) error {
type RepoManifest struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
Name string `xorm:"TEXT 'name'"`
Org string `xorm:"TEXT 'org'"`
Description string `xorm:"TEXT 'description'"`
Version string `xorm:"TEXT 'version'"`
LicenseSPDX string `xorm:"VARCHAR(50) 'license_spdx'"`
LicenseName string `xorm:"TEXT 'license_name'"`
Platform string `xorm:"VARCHAR(50) 'platform'"`
StandardsVersion string `xorm:"VARCHAR(20) 'standards_version'"`
StandardsSource string `xorm:"TEXT 'standards_source'"`
Language string `xorm:"VARCHAR(50) 'language'"`
PackageType string `xorm:"VARCHAR(50) 'package_type'"`
EntryPoint string `xorm:"TEXT 'entry_point'"`
CreatedUnix int64 `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix int64 `xorm:"UPDATED 'updated_unix'"`
}
return x.Sync(new(RepoManifest))
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddIssuePriorityDefTable creates the issue_priority_def table and adds
// priority_id to the issue table.
func AddIssuePriorityDefTable(x *xorm.Engine) error {
type IssuePriorityDef struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
Name string `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(7)"`
Description string `xorm:"TEXT"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsDefault bool `xorm:"NOT NULL DEFAULT false 'is_default'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix int64 `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix int64 `xorm:"UPDATED 'updated_unix'"`
}
if err := x.Sync(new(IssuePriorityDef)); err != nil {
return err
}
type Issue struct {
PriorityID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'priority_id'"`
}
return x.Sync(new(Issue))
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import (
"xorm.io/xorm"
)
// AddSecurityScanningTables creates security_alert and security_scanner_config tables.
func AddSecurityScanningTables(x *xorm.Engine) error {
type SecurityAlert struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL 'repo_id'"`
Scanner string `xorm:"VARCHAR(20) NOT NULL 'scanner'"`
Severity string `xorm:"VARCHAR(10) NOT NULL 'severity'"`
Status string `xorm:"VARCHAR(10) NOT NULL DEFAULT 'active' 'status'"`
RuleID string `xorm:"VARCHAR(100) NOT NULL 'rule_id'"`
Title string `xorm:"TEXT NOT NULL 'title'"`
Description string `xorm:"TEXT 'description'"`
FilePath string `xorm:"TEXT 'file_path'"`
LineNumber int `xorm:"'line_number'"`
CommitSHA string `xorm:"VARCHAR(64) 'commit_sha'"`
Fingerprint string `xorm:"VARCHAR(64) INDEX 'fingerprint'"`
Metadata string `xorm:"TEXT 'metadata'"`
ResolvedBy int64 `xorm:"'resolved_by'"`
CreatedUnix int64 `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix int64 `xorm:"UPDATED 'updated_unix'"`
}
if err := x.Sync(new(SecurityAlert)); err != nil {
return err
}
type SecurityScannerConfig struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
Enabled bool `xorm:"NOT NULL DEFAULT true 'enabled'"`
BlockOnPush bool `xorm:"NOT NULL DEFAULT false 'block_on_push'"`
SecretScanner bool `xorm:"NOT NULL DEFAULT true 'secret_scanner'"`
DependScanner bool `xorm:"NOT NULL DEFAULT true 'depend_scanner'"`
CodeScanner bool `xorm:"NOT NULL DEFAULT false 'code_scanner'"`
ConfigScanner bool `xorm:"NOT NULL DEFAULT false 'config_scanner'"`
LicenseScanner bool `xorm:"NOT NULL DEFAULT false 'license_scanner'"`
CustomPatterns string `xorm:"TEXT 'custom_patterns'"`
CreatedUnix int64 `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix int64 `xorm:"UPDATED 'updated_unix'"`
}
return x.Sync(new(SecurityScannerConfig))
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package v1_27
import "xorm.io/xorm"
// AddIssueTypeDefTable creates the issue_type_def table and adds type_id to issues.
func AddIssueTypeDefTable(x *xorm.Engine) error {
type IssueTypeDef struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'org_id'"`
Name string `xorm:"NOT NULL"`
Color string `xorm:"VARCHAR(7)"`
Description string `xorm:"TEXT"`
SortOrder int `xorm:"NOT NULL DEFAULT 0 'sort_order'"`
IsDefault bool `xorm:"NOT NULL DEFAULT false 'is_default'"`
IsActive bool `xorm:"NOT NULL DEFAULT true 'is_active'"`
CreatedUnix int64 `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix int64 `xorm:"UPDATED 'updated_unix'"`
}
if err := x.Sync(new(IssueTypeDef)); err != nil {
return err
}
type Issue struct {
TypeID int64 `xorm:"INDEX NOT NULL DEFAULT 0 'type_id'"`
}
return x.Sync(new(Issue))
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(RepoManifest))
}
// RepoManifest stores moko-platform manifest settings for a repository.
// These fields correspond to the .mokogitea/manifest.xml schema and are
// exposed via API for use by Actions workflows and the moko-platform CLI.
type RepoManifest struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
// identity section
Name string `xorm:"TEXT 'name'"` // project name
Org string `xorm:"TEXT 'org'"` // organization name
Description string `xorm:"TEXT 'description'"` // project description
Version string `xorm:"TEXT 'version'"` // current version string
LicenseSPDX string `xorm:"VARCHAR(50) 'license_spdx'"` // SPDX identifier, e.g. "GPL-3.0-or-later"
LicenseName string `xorm:"TEXT 'license_name'"` // human-readable license name
// governance section
Platform string `xorm:"VARCHAR(50) 'platform'"` // go, php, node, python, etc.
StandardsVersion string `xorm:"VARCHAR(20) 'standards_version'"` // moko-platform standards version
StandardsSource string `xorm:"TEXT 'standards_source'"` // URL to standards repo
// build section
Language string `xorm:"VARCHAR(50) 'language'"` // Go, PHP, TypeScript, etc.
PackageType string `xorm:"VARCHAR(50) 'package_type'"` // application, library, plugin, module, component, package
EntryPoint string `xorm:"TEXT 'entry_point'"` // build entry point path
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (RepoManifest) TableName() string {
return "repo_manifest"
}
// GetRepoManifest returns the manifest for a repo, or nil if none exists.
func GetRepoManifest(ctx context.Context, repoID int64) (*RepoManifest, error) {
m := new(RepoManifest)
has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(m)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return m, nil
}
// CreateOrUpdateRepoManifest upserts a repo manifest.
func CreateOrUpdateRepoManifest(ctx context.Context, m *RepoManifest) error {
existing := new(RepoManifest)
has, err := db.GetEngine(ctx).Where("repo_id = ?", m.RepoID).Get(existing)
if err != nil {
return err
}
if has {
m.ID = existing.ID
_, err = db.GetEngine(ctx).ID(m.ID).AllCols().Update(m)
return err
}
_, err = db.GetEngine(ctx).Insert(m)
return err
}
// DeleteRepoManifest deletes the manifest for a repo.
func DeleteRepoManifest(ctx context.Context, repoID int64) error {
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(RepoManifest))
return err
}
+219
View File
@@ -0,0 +1,219 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package security
import (
"context"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(SecurityAlert))
db.RegisterModel(new(SecurityScannerConfig))
}
// AlertSeverity represents the severity level of a security finding.
type AlertSeverity string
const (
SeverityCritical AlertSeverity = "critical"
SeverityHigh AlertSeverity = "high"
SeverityMedium AlertSeverity = "medium"
SeverityLow AlertSeverity = "low"
SeverityInfo AlertSeverity = "info"
)
// AlertStatus represents the lifecycle state of an alert.
type AlertStatus string
const (
AlertStatusActive AlertStatus = "active"
AlertStatusResolved AlertStatus = "resolved"
AlertStatusDismissed AlertStatus = "dismissed"
)
// ScannerType identifies which scanner produced a finding.
type ScannerType string
const (
ScannerSecret ScannerType = "secret"
ScannerDependency ScannerType = "dependency"
ScannerCode ScannerType = "code"
ScannerConfig ScannerType = "config"
ScannerLicense ScannerType = "license"
)
// SecurityAlert stores a single security finding for a repository.
type SecurityAlert struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL 'repo_id'"`
Scanner ScannerType `xorm:"VARCHAR(20) NOT NULL 'scanner'"`
Severity AlertSeverity `xorm:"VARCHAR(10) NOT NULL 'severity'"`
Status AlertStatus `xorm:"VARCHAR(10) NOT NULL DEFAULT 'active' 'status'"`
RuleID string `xorm:"VARCHAR(100) NOT NULL 'rule_id'"` // e.g. "aws-access-key", "cve-2024-1234"
Title string `xorm:"TEXT NOT NULL 'title'"`
Description string `xorm:"TEXT 'description'"`
FilePath string `xorm:"TEXT 'file_path'"`
LineNumber int `xorm:"'line_number'"`
CommitSHA string `xorm:"VARCHAR(64) 'commit_sha'"`
Fingerprint string `xorm:"VARCHAR(64) INDEX 'fingerprint'"` // dedup key: hash of rule+file+content
Metadata string `xorm:"TEXT 'metadata'"` // JSON extra data
ResolvedBy int64 `xorm:"'resolved_by'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (SecurityAlert) TableName() string {
return "security_alert"
}
// SecurityScannerConfig stores per-repo scanner settings.
type SecurityScannerConfig struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"`
Enabled bool `xorm:"NOT NULL DEFAULT true 'enabled'"`
BlockOnPush bool `xorm:"NOT NULL DEFAULT false 'block_on_push'"` // reject push if secrets found
SecretScanner bool `xorm:"NOT NULL DEFAULT true 'secret_scanner'"`
DependScanner bool `xorm:"NOT NULL DEFAULT true 'depend_scanner'"`
CodeScanner bool `xorm:"NOT NULL DEFAULT false 'code_scanner'"`
ConfigScanner bool `xorm:"NOT NULL DEFAULT false 'config_scanner'"`
LicenseScanner bool `xorm:"NOT NULL DEFAULT false 'license_scanner'"`
CustomPatterns string `xorm:"TEXT 'custom_patterns'"` // JSON array of custom regex patterns
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED 'created_unix'"`
UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"`
}
func (SecurityScannerConfig) TableName() string {
return "security_scanner_config"
}
// ──────────────────────────────────────────────────────────────────────
// Alert queries
// ──────────────────────────────────────────────────────────────────────
// GetActiveAlerts returns all active alerts for a repo.
func GetActiveAlerts(ctx context.Context, repoID int64) ([]*SecurityAlert, error) {
alerts := make([]*SecurityAlert, 0, 20)
return alerts, db.GetEngine(ctx).
Where("repo_id = ? AND status = ?", repoID, AlertStatusActive).
OrderBy("severity ASC, created_unix DESC").
Find(&alerts)
}
// GetAllAlerts returns all alerts for a repo (including resolved/dismissed).
func GetAllAlerts(ctx context.Context, repoID int64) ([]*SecurityAlert, error) {
alerts := make([]*SecurityAlert, 0, 50)
return alerts, db.GetEngine(ctx).
Where("repo_id = ?", repoID).
OrderBy("status ASC, severity ASC, created_unix DESC").
Find(&alerts)
}
// GetAlertByID returns a single alert.
func GetAlertByID(ctx context.Context, id int64) (*SecurityAlert, error) {
alert := new(SecurityAlert)
has, err := db.GetEngine(ctx).ID(id).Get(alert)
if err != nil {
return nil, err
}
if !has {
return nil, db.ErrNotExist{Resource: "SecurityAlert", ID: id}
}
return alert, nil
}
// GetAlertCountsByRepo returns count of active alerts grouped by severity.
func GetAlertCountsByRepo(ctx context.Context, repoID int64) (map[AlertSeverity]int64, error) {
type result struct {
Severity AlertSeverity `xorm:"severity"`
Count int64 `xorm:"count"`
}
var results []result
err := db.GetEngine(ctx).
Table("security_alert").
Select("severity, COUNT(*) as count").
Where("repo_id = ? AND status = ?", repoID, AlertStatusActive).
GroupBy("severity").
Find(&results)
if err != nil {
return nil, err
}
counts := make(map[AlertSeverity]int64)
for _, r := range results {
counts[r.Severity] = r.Count
}
return counts, nil
}
// CreateOrUpdateAlert creates a new alert or updates if fingerprint exists.
func CreateOrUpdateAlert(ctx context.Context, alert *SecurityAlert) error {
if alert.Fingerprint != "" {
existing := new(SecurityAlert)
has, err := db.GetEngine(ctx).
Where("repo_id = ? AND fingerprint = ?", alert.RepoID, alert.Fingerprint).
Get(existing)
if err != nil {
return err
}
if has {
// Update existing - refresh commit SHA and keep active
existing.CommitSHA = alert.CommitSHA
existing.LineNumber = alert.LineNumber
existing.Status = AlertStatusActive
_, err = db.GetEngine(ctx).ID(existing.ID).
Cols("commit_sha", "line_number", "status").Update(existing)
return err
}
}
_, err := db.GetEngine(ctx).Insert(alert)
return err
}
// UpdateAlertStatus changes the status of an alert.
func UpdateAlertStatus(ctx context.Context, id int64, status AlertStatus, resolvedBy int64) error {
_, err := db.GetEngine(ctx).ID(id).
Cols("status", "resolved_by").
Update(&SecurityAlert{Status: status, ResolvedBy: resolvedBy})
return err
}
// ──────────────────────────────────────────────────────────────────────
// Scanner config queries
// ──────────────────────────────────────────────────────────────────────
// GetScannerConfig returns the scanner config for a repo, or defaults.
func GetScannerConfig(ctx context.Context, repoID int64) (*SecurityScannerConfig, error) {
cfg := new(SecurityScannerConfig)
has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(cfg)
if err != nil {
return nil, err
}
if !has {
return &SecurityScannerConfig{
RepoID: repoID,
Enabled: true,
SecretScanner: true,
DependScanner: true,
}, nil
}
return cfg, nil
}
// SaveScannerConfig creates or updates scanner config.
func SaveScannerConfig(ctx context.Context, cfg *SecurityScannerConfig) error {
existing := new(SecurityScannerConfig)
has, err := db.GetEngine(ctx).Where("repo_id = ?", cfg.RepoID).Get(existing)
if err != nil {
return err
}
if has {
cfg.ID = existing.ID
_, err = db.GetEngine(ctx).ID(cfg.ID).AllCols().Update(cfg)
return err
}
_, err = db.GetEngine(ctx).Insert(cfg)
return err
}
+2
View File
@@ -82,6 +82,8 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m
Doer: nil,
}
opts.CustomFieldFilters = options.CustomFieldFilters
if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 {
opts.MilestoneIDs = []int64{db.NoConditionID}
} else {
+1
View File
@@ -79,6 +79,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
}
searchOpt.Paginator = opts.Paginator
searchOpt.CustomFieldFilters = opts.CustomFieldFilters
switch opts.SortType {
case "", "latest":
+2
View File
@@ -114,6 +114,8 @@ type SearchOptions struct {
Paginator *db.ListOptions
SortBy SortBy // sort by field
CustomFieldFilters map[int64]string // field_id → required value (AND semantics, DB-only)
}
// Copy returns a copy of the options.
-65
View File
@@ -1,65 +0,0 @@
// Copyright 2026 Moko Consulting. All rights reserved.
// SPDX-License-Identifier: MIT
package structs
import "time"
// CustomFieldDefinition represents a custom field definition for a repository
// swagger:model
type CustomFieldDefinition struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Name string `json:"name"`
FieldType string `json:"field_type"`
Description string `json:"description"`
Required bool `json:"required"`
Position int `json:"position"`
Options string `json:"options"`
DefaultValue string `json:"default_value"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
Updated time.Time `json:"updated_at"`
}
// CreateCustomFieldOption options for creating a custom field
// swagger:model
type CreateCustomFieldOption struct {
// required: true
Name string `json:"name" binding:"Required"`
// required: true
FieldType string `json:"field_type" binding:"Required"` // text, number, date, dropdown, checkbox
Description string `json:"description"`
Required bool `json:"required"`
Position int `json:"position"`
Options string `json:"options"` // JSON array for dropdown
DefaultValue string `json:"default_value"`
}
// EditCustomFieldOption options for editing a custom field
// swagger:model
type EditCustomFieldOption struct {
Name *string `json:"name"`
Description *string `json:"description"`
Required *bool `json:"required"`
Position *int `json:"position"`
Options *string `json:"options"`
DefaultValue *string `json:"default_value"`
}
// CustomFieldValue represents a custom field value for an issue
// swagger:model
type CustomFieldValue struct {
ID int64 `json:"id"`
IssueID int64 `json:"issue_id"`
FieldID int64 `json:"field_id"`
Value string `json:"value"`
}
// SetCustomFieldValueOption options for setting a custom field value
// swagger:model
type SetCustomFieldValueOption struct {
// required: true
Value string `json:"value" binding:"Required"`
}
+12 -9
View File
@@ -104,6 +104,8 @@ type CreateIssueOption struct {
// list of project ids
Projects []int64 `json:"projects"`
Closed bool `json:"closed"`
// custom field values keyed by field name
CustomFields map[string]string `json:"custom_fields,omitempty"`
}
// EditIssueOption options for editing an issue
@@ -190,15 +192,16 @@ const (
// IssueTemplate represents an issue template for a repository
// swagger:model
type IssueTemplate struct {
Name string `json:"name" yaml:"name"`
Title string `json:"title" yaml:"title"`
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
Labels IssueTemplateStringSlice `json:"labels" yaml:"labels"`
Assignees IssueTemplateStringSlice `json:"assignees" yaml:"assignees"`
Ref string `json:"ref" yaml:"ref"`
Content string `json:"content" yaml:"-"`
Fields []*IssueFormField `json:"body" yaml:"body"`
FileName string `json:"file_name" yaml:"-"`
Name string `json:"name" yaml:"name"`
Title string `json:"title" yaml:"title"`
About string `json:"about" yaml:"about"` // Using "description" in a template file is compatible
Labels IssueTemplateStringSlice `json:"labels" yaml:"labels"`
Assignees IssueTemplateStringSlice `json:"assignees" yaml:"assignees"`
Ref string `json:"ref" yaml:"ref"`
Content string `json:"content" yaml:"-"`
Fields []*IssueFormField `json:"body" yaml:"body"`
FileName string `json:"file_name" yaml:"-"`
CustomFields map[string]string `json:"custom_fields,omitempty" yaml:"custom_fields"`
}
type IssueTemplateStringSlice []string
+140 -10
View File
@@ -1004,6 +1004,12 @@
"repo.object_format": "Object Format",
"repo.object_format_helper": "Object format of the repository. Cannot be changed later. SHA1 is most compatible.",
"repo.readme": "README",
"repo.well_known_file.readme": "Readme",
"repo.well_known_file.license": "License",
"repo.well_known_file.contributing": "Contributing",
"repo.well_known_file.code_of_conduct": "Code of Conduct",
"repo.well_known_file.security": "Security",
"repo.well_known_file.changelog": "Changelog",
"repo.readme_helper": "Select a README file template.",
"repo.readme_helper_desc": "This is the place where you can write a complete description for your project.",
"repo.auto_init": "Initialize Repository (Adds .gitignore, License and README)",
@@ -1412,6 +1418,7 @@
"repo.issues.new.open_projects": "Open Projects",
"repo.issues.new.closed_projects": "Closed Projects",
"repo.issues.new.no_items": "No items",
"repo.issues.custom_fields": "Custom Fields",
"repo.issues.new.milestone": "Milestone",
"repo.issues.new.no_milestone": "No Milestone",
"repo.issues.new.clear_milestone": "Clear milestone",
@@ -1575,6 +1582,9 @@
"repo.issues.edit": "Edit",
"repo.issues.cancel": "Cancel",
"repo.issues.save": "Save",
"repo.issues.status": "Status",
"repo.issues.priority": "Priority",
"repo.issues.type": "Type",
"repo.issues.label_title": "Name",
"repo.issues.label_description": "Description",
"repo.issues.label_color": "Color",
@@ -1960,6 +1970,7 @@
"repo.signing.wont_sign.approved": "The merge will not be signed as the PR is not approved.",
"repo.ext_wiki": "Access to External Wiki",
"repo.ext_wiki.desc": "Link to an external wiki.",
"repo.security": "Security",
"repo.wiki": "Wiki",
"repo.wiki.welcome": "Welcome to the Wiki.",
"repo.wiki.welcome_desc": "The wiki lets you write and share documentation with collaborators.",
@@ -1983,6 +1994,7 @@
"repo.wiki.page_already_exists": "A wiki page with the same name already exists.",
"repo.wiki.reserved_page": "The wiki page name \"%s\" is reserved.",
"repo.wiki.pages": "Pages",
"repo.wiki.folder_empty": "This folder is empty.",
"repo.wiki.last_updated": "Last updated %s",
"repo.wiki.page_name_desc": "Enter a name for this Wiki page. Some special names are: 'Home', '_Sidebar' and '_Footer'.",
"repo.wiki.original_git_entry_tooltip": "View original Git file instead of using friendly link.",
@@ -2149,15 +2161,15 @@
"repo.settings.unit_visibility_private": "Private (follow repo visibility)",
"repo.settings.unit_visibility_public": "Public (anyone can read)",
"repo.settings.unit_visibility_releases_help": "Controls whether the releases page is visible to anonymous visitors.",
"repo.settings.licensing_section": "Licensing & Updates",
"repo.settings.licensing_section_desc": "Manage commercial license keys and gated update feeds for this repository. When enabled, the Licenses tab appears and release tags must follow update stream naming.",
"repo.settings.licensing_section": "Update Server",
"repo.settings.licensing_section_desc": "Manage update feeds and optional license key gating for this repository. When enabled, the Licenses tab appears and release tags are served via update feeds.",
"repo.settings.update_platform": "Update Feed Format",
"repo.settings.update_platform_both": "Both (Joomla + Dolibarr)",
"repo.settings.update_platform_help": "Choose which update feed format to generate. All formats support license key validation.",
"repo.settings.require_update_key": "Require license key for update feeds",
"repo.settings.require_update_key_help": "When enabled, update feeds return empty results unless a valid license key is provided. Joomla clients will see a Download Key field in Update Sites.",
"repo.settings.enable_licensing": "Enable licensing for this repository",
"repo.settings.enable_licensing_help": "Show the Licenses tab and enable license key management for this repository.",
"repo.settings.enable_licensing": "Enable Update Server for this repository",
"repo.settings.enable_licensing_help": "Serve update feeds from releases and show the Licenses tab for optional key management.",
"repo.settings.packages_desc": "Enable Repository Packages Registry",
"repo.settings.projects_desc": "Enable Projects",
"repo.settings.projects_mode_desc": "Projects Mode (which kinds of projects to show)",
@@ -2660,12 +2672,16 @@
"repo.licenses.key_revoked": "License key revoked.",
"repo.licenses.master_key_created": "Master License Key Created",
"repo.licenses.master_key_created_copy": "This is your organization master key with unlimited access to all update channels. Copy it now — it will not be shown again.",
"repo.licenses.regenerate_master_key": "Regenerate",
"repo.licenses.regenerate_master_key_help": "Deactivates the current master key and generates a new one. The new key will be shown once.",
"repo.licenses.master_key_regenerated": "Master key regenerated. Copy the new key below — it will not be shown again.",
"repo.licenses.update_feeds": "Update Feed URLs",
"repo.licenses.edit_key": "Edit License Key",
"repo.licenses.licensee_name": "Licensee Name",
"repo.licenses.licensee_email": "Licensee Email",
"repo.licenses.domain_restriction": "Domain Restriction",
"repo.licenses.domain_restriction_help": "Comma-separated list of allowed domains. Leave empty for no restriction.",
"repo.licenses.domain_restriction_help": "Comma-separated list of allowed domains. Leave empty to inherit from the package default.",
"repo.licenses.domain_restriction_package_help": "Default domain restriction for keys generated from this package. Comma-separated. Keys can override this.",
"repo.licenses.use_package_default": "use package default",
"repo.licenses.expires_at": "Expires At",
"repo.licenses.expires_at_help": "Leave empty for no expiry (lifetime).",
@@ -2716,6 +2732,66 @@
"repo.settings.download_gating": "Download Gating",
"repo.settings.support_url": "Support / Product Page URL",
"repo.settings.support_url_help": "Shown when downloads are gated. Can point to your wiki, product page, or external support site.",
"repo.settings.custom_fields": "Custom Fields",
"repo.settings.manifest": "Manifest",
"repo.settings.manifest_desc": "Project identity, governance, and build settings from the moko-platform manifest. These are accessible via API for Actions workflows and the moko-platform CLI.",
"repo.settings.manifest_identity": "Identity",
"repo.settings.manifest_name": "Project Name",
"repo.settings.manifest_org": "Organization",
"repo.settings.manifest_description": "Description",
"repo.settings.manifest_version": "Version",
"repo.settings.manifest_license_spdx": "License (SPDX)",
"repo.settings.manifest_license_name": "License Name",
"repo.settings.manifest_governance": "Governance",
"repo.settings.manifest_platform": "Platform",
"repo.settings.manifest_standards_version": "Standards Version",
"repo.settings.manifest_standards_source": "Standards Source",
"repo.settings.manifest_build": "Build",
"repo.settings.manifest_language": "Language",
"repo.settings.manifest_package_type": "Package Type",
"repo.settings.manifest_entry_point": "Entry Point",
"repo.settings.manifest_save": "Save Manifest",
"repo.settings.manifest_saved": "Manifest settings saved.",
"repo.settings.security": "Security",
"repo.settings.security_desc": "Security scanning detects secrets, vulnerabilities, and code issues across the repository.",
"repo.settings.security_scanners": "Scanners",
"repo.settings.security_enabled": "Enable security scanning",
"repo.settings.security_secret_scanner": "Secret Scanner - API keys, tokens, passwords, private keys",
"repo.settings.security_depend_scanner": "Dependency Scanner - CVEs in dependencies (coming soon)",
"repo.settings.security_code_scanner": "Code Scanner - SQL injection, XSS, command injection (coming soon)",
"repo.settings.security_config_scanner": "Config Scanner - Insecure settings, debug modes (coming soon)",
"repo.settings.security_license_scanner": "License Scanner - License compliance (coming soon)",
"repo.settings.security_block_on_push": "Block pushes with critical findings",
"repo.settings.security_block_on_push_help": "Reject pushes to the default branch if critical secrets are detected.",
"repo.settings.security_save": "Save Settings",
"repo.settings.security_saved": "Security settings saved.",
"repo.settings.security_alerts": "Security Alerts",
"repo.settings.security_scan_now": "Scan Now",
"repo.settings.security_scan_complete": "Security scan complete.",
"repo.settings.security_severity": "Severity",
"repo.settings.security_scanner_type": "Scanner",
"repo.settings.security_finding": "Finding",
"repo.settings.security_file": "File",
"repo.settings.security_status": "Status",
"repo.settings.security_no_alerts": "No security alerts found. Run a scan or push to the default branch to check.",
"repo.settings.metadata": "Metadata",
"repo.settings.metadata_saved": "Repository metadata saved.",
"repo.settings.metadata_empty": "No metadata fields defined. Org admins can add fields in Organization Settings > Custom Fields.",
"repo.settings.custom_field_new": "New Field",
"repo.settings.custom_field_create": "Create Field",
"repo.settings.custom_field_name": "Field Name",
"repo.settings.custom_field_type": "Type",
"repo.settings.custom_field_description": "Description",
"repo.settings.custom_field_options": "Options (JSON)",
"repo.settings.custom_field_options_help": "JSON array for dropdown fields. e.g. [\"Low\",\"Medium\",\"High\"]",
"repo.settings.custom_field_required": "Required",
"repo.settings.custom_field_sort_order": "Sort Order",
"repo.settings.custom_field_created": "Custom field created.",
"repo.settings.custom_field_updated": "Custom field updated.",
"repo.settings.custom_field_deleted": "Custom field deleted.",
"repo.settings.custom_field_confirm_delete": "Delete this custom field? All values stored for this field will be lost.",
"repo.settings.custom_fields_none": "No Custom Fields",
"repo.settings.custom_fields_none_desc": "Define custom fields to add structured metadata to issues.",
"repo.settings.features": "Features",
"repo.settings.features_units": "Units",
"repo.settings.change_visibility": "Change Visibility",
@@ -2874,11 +2950,65 @@
"org.form.create_org_not_allowed": "You are not allowed to create an organization.",
"org.settings": "Settings",
"org.settings.options": "Organization",
"org.settings.update_streams": "Licensing & Update Streams",
"org.settings.licensing": "Licensing",
"org.settings.licensing_desc": "Control commercial license key management and gated update feeds across all repositories in this organization.",
"org.settings.enable_licensing": "Enable licensing for this organization",
"org.settings.enable_licensing_help": "Show the Licenses page in the org menu and enable license key management. Individual repos can also enable licensing independently.",
"org.settings.custom_fields": "Custom Fields",
"org.settings.custom_fields_desc": "Define custom fields that appear across all repositories in this organization. Issue fields show in issue sidebars. Repo fields show in repo settings metadata.",
"org.settings.custom_fields_empty": "No custom fields defined yet.",
"org.settings.custom_field_add": "Add Custom Field",
"org.settings.custom_field_name": "Field Name",
"org.settings.custom_field_scope": "Scope",
"org.settings.custom_field_type": "Type",
"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_created": "Custom field created.",
"org.settings.custom_field_updated": "Custom field updated.",
"org.settings.custom_field_deleted": "Custom field deleted.",
"org.settings.issue_statuses": "Issue Statuses",
"org.settings.issue_statuses_desc": "Define custom issue statuses for all repositories in this organization. Statuses appear in the issue sidebar and can automatically close or reopen issues.",
"org.settings.issue_statuses_empty": "No custom issue statuses defined yet.",
"org.settings.issue_status_add": "Add Status",
"org.settings.issue_status_name": "Status Name",
"org.settings.issue_status_color": "Color",
"org.settings.issue_status_description": "Description",
"org.settings.issue_status_closes_issue": "Closes issue",
"org.settings.issue_status_closes_issue_help": "When this status is selected, the issue will be automatically closed.",
"org.settings.issue_status_closes": "Closes",
"org.settings.issue_status_sort_order": "Sort Order",
"org.settings.issue_status_inactive": "Inactive",
"org.settings.issue_status_created": "Issue status created.",
"org.settings.issue_status_updated": "Issue status updated.",
"org.settings.issue_status_deleted": "Issue status deleted.",
"org.settings.issue_priorities": "Issue Priorities",
"org.settings.issue_priorities_desc": "Define priority levels for all repositories in this organization. Priorities appear in the issue sidebar.",
"org.settings.issue_priorities_empty": "No custom issue priorities defined yet.",
"org.settings.issue_priority_add": "Add Priority",
"org.settings.issue_priority_name": "Priority Name",
"org.settings.issue_priority_color": "Color",
"org.settings.issue_priority_description": "Description",
"org.settings.issue_priority_default": "Default",
"org.settings.issue_priority_default_help": "Auto-assigned to new issues.",
"org.settings.issue_priority_sort_order": "Sort Order",
"org.settings.issue_priority_inactive": "Inactive",
"org.settings.issue_priority_created": "Issue priority created.",
"org.settings.issue_priority_updated": "Issue priority updated.",
"org.settings.issue_priority_deleted": "Issue priority deleted.",
"org.settings.issue_types": "Issue Types",
"org.settings.issue_types_desc": "Define issue types for all repositories in this organization.",
"org.settings.issue_types_empty": "No custom issue types defined yet.",
"org.settings.issue_type_add": "Add Type",
"org.settings.issue_type_name": "Type Name",
"org.settings.issue_type_color": "Color",
"org.settings.issue_type_description": "Description",
"org.settings.issue_type_default": "Default",
"org.settings.issue_type_sort_order": "Sort Order",
"org.settings.issue_type_created": "Issue type created.",
"org.settings.issue_type_updated": "Issue type updated.",
"org.settings.issue_type_deleted": "Issue type deleted.",
"org.settings.update_streams": "Update Server",
"org.settings.licensing": "Update Server",
"org.settings.licensing_desc": "Manage update feeds and optional license key gating across all repositories in this organization.",
"org.settings.enable_licensing": "Enable Update Server for this organization",
"org.settings.enable_licensing_help": "Show the Licenses page in the org menu and serve update feeds. Individual repos can also enable this independently.",
"org.settings.require_key": "Require license key for all update feeds",
"org.settings.require_key_help": "Update feeds return empty results unless a valid key is provided. Joomla clients will see a Download Key field. Individual repos can override this.",
"org.settings.feed_visibility": "Update Feed Visibility",
+14 -12
View File
@@ -1479,6 +1479,9 @@ func Routes() *web.Router {
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
}, reqAnyRepoReader())
m.Combo("/manifest", reqRepoReader(unit.TypeCode)).
Get(repo.GetRepoManifest).
Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest)
// MokoGitea badge engine
m.Get("/badge/{type}.svg", repo.GetRepoBadge)
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
@@ -1655,21 +1658,15 @@ func Routes() *web.Router {
// })
// })
// })
m.Group("/custom-fields", func() {
m.Combo("").Get(repo.ListCustomFields).
Post(reqToken(), reqRepoWriter(unit.TypeIssues), bind(api.CreateCustomFieldOption{}), repo.CreateCustomField)
m.Group("/{fieldId}", func() {
m.Combo("").Get(repo.GetCustomField).
Patch(reqToken(), reqRepoWriter(unit.TypeIssues), bind(api.EditCustomFieldOption{}), repo.EditCustomField).
Delete(reqToken(), reqRepoWriter(unit.TypeIssues), repo.DeleteCustomField)
})
// Repo metadata (repo-scoped custom fields)
m.Group("/metadata", func() {
m.Get("", repo.GetRepoMetadata)
m.Put("", reqToken(), reqRepoWriter(unit.TypeCode), repo.SetRepoMetadata)
})
// Issue custom fields
m.Group("/issues/{index}/custom-fields", func() {
m.Get("", repo.GetIssueCustomFields)
m.Group("/{fieldId}", func() {
m.Put("", reqToken(), reqRepoWriter(unit.TypeIssues), bind(api.SetCustomFieldValueOption{}), repo.SetIssueCustomField)
m.Delete("", reqToken(), reqRepoWriter(unit.TypeIssues), repo.DeleteIssueCustomField)
})
m.Put("", reqToken(), reqRepoWriter(unit.TypeIssues), repo.SetIssueCustomFields)
})
}, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
@@ -1771,6 +1768,11 @@ func Routes() *web.Router {
m.Delete("", org.UnblockUser)
})
}, reqToken(), reqOrgOwnership())
m.Group("/custom-fields", func() {
m.Get("", org.ListOrgCustomFields)
m.Post("", reqToken(), reqOrgOwnership(), org.CreateOrgCustomField)
m.Delete("/{id}", reqToken(), reqOrgOwnership(), org.DeleteOrgCustomField)
})
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
m.Group("/teams/{teamid}", func() {
m.Combo("").Get(reqToken(), org.GetTeam).
+139
View File
@@ -0,0 +1,139 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package org
import (
"encoding/json"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
type apiCustomFieldDef struct {
ID int64 `json:"id"`
OwnerID int64 `json:"owner_id"`
Scope string `json:"scope"`
Name string `json:"name"`
FieldType string `json:"field_type"`
Description string `json:"description"`
Options any `json:"options"`
Required bool `json:"required"`
SortOrder int `json:"sort_order"`
IsActive bool `json:"is_active"`
}
func toAPIFieldDef(f *issues_model.CustomFieldDef) apiCustomFieldDef {
var opts any
if f.Options != "" {
var parsed []string
if json.Unmarshal([]byte(f.Options), &parsed) == nil {
opts = parsed
} else {
opts = f.Options
}
}
return apiCustomFieldDef{
ID: f.ID,
OwnerID: f.OwnerID,
Scope: string(f.Scope),
Name: f.Name,
FieldType: string(f.FieldType),
Description: f.Description,
Options: opts,
Required: f.Required,
SortOrder: f.SortOrder,
IsActive: f.IsActive,
}
}
// ListOrgCustomFields returns all custom field definitions for an org.
func ListOrgCustomFields(ctx *context.APIContext) {
fields, err := issues_model.GetAllCustomFieldsByOwner(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]apiCustomFieldDef, 0, len(fields))
for _, f := range fields {
result = append(result, toAPIFieldDef(f))
}
ctx.JSON(http.StatusOK, result)
}
// CreateOrgCustomField creates a new custom field definition.
func CreateOrgCustomField(ctx *context.APIContext) {
var req struct {
Scope string `json:"scope" binding:"Required"`
Name string `json:"name" binding:"Required"`
FieldType string `json:"field_type" binding:"Required"`
Description string `json:"description"`
Options []string `json:"options"`
Required bool `json:"required"`
SortOrder int `json:"sort_order"`
}
if err := ctx.Req.ParseForm(); err != nil {
ctx.APIError(http.StatusBadRequest, err)
return
}
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
ctx.APIError(http.StatusBadRequest, err)
return
}
if req.Name == "" || req.Scope == "" {
ctx.APIError(http.StatusBadRequest, "name and scope are required")
return
}
scope := issues_model.CustomFieldScope(req.Scope)
if scope != issues_model.CustomFieldScopeIssue && scope != issues_model.CustomFieldScopeRepo {
ctx.APIError(http.StatusBadRequest, "scope must be 'issue' or 'repo'")
return
}
var optionsJSON string
if len(req.Options) > 0 {
data, _ := json.Marshal(req.Options)
optionsJSON = string(data)
}
field := &issues_model.CustomFieldDef{
OwnerID: ctx.Org.Organization.ID,
RepoID: 0,
Scope: scope,
Name: req.Name,
FieldType: issues_model.CustomFieldType(req.FieldType),
Description: req.Description,
Options: optionsJSON,
Required: req.Required,
SortOrder: req.SortOrder,
IsActive: true,
}
if err := issues_model.CreateCustomFieldDef(ctx, field); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, toAPIFieldDef(field))
}
// DeleteOrgCustomField deletes a custom field definition.
func DeleteOrgCustomField(ctx *context.APIContext) {
id := ctx.PathParamInt64("id")
field, err := issues_model.GetCustomFieldDefByID(ctx, id)
if err != nil {
ctx.APIErrorNotFound()
return
}
if field.OwnerID != ctx.Org.Organization.ID {
ctx.APIErrorNotFound()
return
}
if err := issues_model.DeleteCustomFieldDef(ctx, id); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
-196
View File
@@ -1,196 +0,0 @@
// Copyright 2026 Moko Consulting. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
func fieldToAPI(f *issues_model.CustomFieldDefinition) *api.CustomFieldDefinition {
return &api.CustomFieldDefinition{
ID: f.ID,
RepoID: f.RepoID,
Name: f.Name,
FieldType: f.FieldType,
Description: f.Description,
Required: f.Required,
Position: f.Position,
Options: f.Options,
DefaultValue: f.DefaultVal,
Created: f.CreatedUnix.AsTime(),
Updated: f.UpdatedUnix.AsTime(),
}
}
// ListCustomFields lists custom field definitions for a repository
func ListCustomFields(ctx *context.APIContext) {
fields, err := issues_model.GetCustomFieldsByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*api.CustomFieldDefinition, len(fields))
for i, f := range fields {
result[i] = fieldToAPI(f)
}
ctx.JSON(http.StatusOK, result)
}
// GetCustomField gets a custom field definition by ID
func GetCustomField(ctx *context.APIContext) {
field, err := issues_model.GetCustomFieldByID(ctx, ctx.PathParamInt64("fieldId"))
if err != nil {
ctx.APIErrorInternal(err)
return
}
if field == nil || field.RepoID != ctx.Repo.Repository.ID {
ctx.APIErrorNotFound()
return
}
ctx.JSON(http.StatusOK, fieldToAPI(field))
}
// CreateCustomField creates a new custom field definition
func CreateCustomField(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.CreateCustomFieldOption)
validTypes := map[string]bool{"text": true, "number": true, "date": true, "dropdown": true, "checkbox": true}
if !validTypes[form.FieldType] {
ctx.APIError(http.StatusUnprocessableEntity, "field_type must be: text, number, date, dropdown, or checkbox")
return
}
field := &issues_model.CustomFieldDefinition{
RepoID: ctx.Repo.Repository.ID,
Name: form.Name,
FieldType: form.FieldType,
Description: form.Description,
Required: form.Required,
Position: form.Position,
Options: form.Options,
DefaultVal: form.DefaultValue,
}
if err := issues_model.CreateCustomField(ctx, field); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, fieldToAPI(field))
}
// EditCustomField updates a custom field definition
func EditCustomField(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditCustomFieldOption)
field, err := issues_model.GetCustomFieldByID(ctx, ctx.PathParamInt64("fieldId"))
if err != nil {
ctx.APIErrorInternal(err)
return
}
if field == nil || field.RepoID != ctx.Repo.Repository.ID {
ctx.APIErrorNotFound()
return
}
if form.Name != nil {
field.Name = *form.Name
}
if form.Description != nil {
field.Description = *form.Description
}
if form.Required != nil {
field.Required = *form.Required
}
if form.Position != nil {
field.Position = *form.Position
}
if form.Options != nil {
field.Options = *form.Options
}
if form.DefaultValue != nil {
field.DefaultVal = *form.DefaultValue
}
if err := issues_model.UpdateCustomField(ctx, field); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, fieldToAPI(field))
}
// DeleteCustomField deletes a custom field and all its values
func DeleteCustomField(ctx *context.APIContext) {
field, err := issues_model.GetCustomFieldByID(ctx, ctx.PathParamInt64("fieldId"))
if err != nil {
ctx.APIErrorInternal(err)
return
}
if field == nil || field.RepoID != ctx.Repo.Repository.ID {
ctx.APIErrorNotFound()
return
}
if err := issues_model.DeleteCustomField(ctx, field.ID); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
// GetIssueCustomFields gets all custom field values for an issue
func GetIssueCustomFields(ctx *context.APIContext) {
values, err := issues_model.GetCustomFieldValues(ctx, ctx.PathParamInt64("index"))
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*api.CustomFieldValue, len(values))
for i, v := range values {
result[i] = &api.CustomFieldValue{
ID: v.ID,
IssueID: v.IssueID,
FieldID: v.FieldID,
Value: v.Value,
}
}
ctx.JSON(http.StatusOK, result)
}
// SetIssueCustomField sets a custom field value on an issue
func SetIssueCustomField(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.SetCustomFieldValueOption)
issueID := ctx.PathParamInt64("index")
fieldID := ctx.PathParamInt64("fieldId")
// Verify field belongs to this repo
field, err := issues_model.GetCustomFieldByID(ctx, fieldID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if field == nil || field.RepoID != ctx.Repo.Repository.ID {
ctx.APIErrorNotFound()
return
}
if err := issues_model.SetCustomFieldValue(ctx, issueID, fieldID, form.Value); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
// DeleteIssueCustomField removes a custom field value from an issue
func DeleteIssueCustomField(ctx *context.APIContext) {
issueID := ctx.PathParamInt64("index")
fieldID := ctx.PathParamInt64("fieldId")
if err := issues_model.DeleteCustomFieldValue(ctx, issueID, fieldID); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"encoding/json"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// GetRepoMetadata returns all repo-scoped custom field values.
func GetRepoMetadata(ctx *context.APIContext) {
ownerID := ctx.Repo.Repository.OwnerID
repoID := ctx.Repo.Repository.ID
fields, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
if err != nil {
ctx.APIErrorInternal(err)
return
}
values, err := issues_model.GetCustomFieldValuesMap(ctx, repoID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make(map[string]string, len(fields))
for _, f := range fields {
result[f.Name] = values[f.ID]
}
ctx.JSON(http.StatusOK, result)
}
// SetRepoMetadata sets repo-scoped custom field values.
func SetRepoMetadata(ctx *context.APIContext) {
ownerID := ctx.Repo.Repository.OwnerID
repoID := ctx.Repo.Repository.ID
var req map[string]string
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
ctx.APIError(http.StatusBadRequest, err)
return
}
fields, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
if err != nil {
ctx.APIErrorInternal(err)
return
}
// Build name->ID map
nameToID := make(map[string]int64, len(fields))
for _, f := range fields {
nameToID[f.Name] = f.ID
}
for name, value := range req {
if fieldID, ok := nameToID[name]; ok {
if err := issues_model.SetCustomFieldValue(ctx, repoID, fieldID, value); err != nil {
ctx.APIErrorInternal(err)
return
}
}
}
ctx.Status(http.StatusNoContent)
}
// GetIssueCustomFields returns custom field values for an issue.
func GetIssueCustomFields(ctx *context.APIContext) {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.APIErrorNotFound()
return
}
ownerID := ctx.Repo.Repository.OwnerID
fields, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeIssue)
if err != nil {
ctx.APIErrorInternal(err)
return
}
values, err := issues_model.GetCustomFieldValuesMap(ctx, issue.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make(map[string]string, len(fields))
for _, f := range fields {
result[f.Name] = values[f.ID]
}
ctx.JSON(http.StatusOK, result)
}
// SetIssueCustomFields sets custom field values for an issue.
func SetIssueCustomFields(ctx *context.APIContext) {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil {
ctx.APIErrorNotFound()
return
}
var req map[string]string
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
ctx.APIError(http.StatusBadRequest, err)
return
}
ownerID := ctx.Repo.Repository.OwnerID
fields, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeIssue)
if err != nil {
ctx.APIErrorInternal(err)
return
}
nameToID := make(map[string]int64, len(fields))
for _, f := range fields {
nameToID[f.Name] = f.ID
}
for name, value := range req {
if fieldID, ok := nameToID[name]; ok {
if err := issues_model.SetCustomFieldValue(ctx, issue.ID, fieldID, value); err != nil {
ctx.APIErrorInternal(err)
return
}
}
}
ctx.Status(http.StatusNoContent)
}
+54
View File
@@ -289,6 +289,12 @@ func SearchIssues(ctx *context.APIContext) {
}
}
if cfFilters, cfErr := parseAPICustomFieldFilters(ctx); cfErr != nil {
return
} else if len(cfFilters) > 0 {
searchOpt.CustomFieldFilters = cfFilters
}
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
if err != nil {
ctx.APIErrorInternal(err)
@@ -517,6 +523,12 @@ func ListIssues(ctx *context.APIContext) {
searchOpt.MentionID = optional.Some(mentionedByID)
}
if cfFilters, cfErr := parseAPICustomFieldFilters(ctx); cfErr != nil {
return
} else if len(cfFilters) > 0 {
searchOpt.CustomFieldFilters = cfFilters
}
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
if err != nil {
ctx.APIErrorInternal(err)
@@ -553,6 +565,25 @@ func getUserIDForFilter(ctx *context.APIContext, queryName string) int64 {
return user.ID
}
// parseAPICustomFieldFilters extracts cf_{fieldID}=value query parameters.
// Returns an error (and writes a 400 response) if a field ID is non-numeric or non-positive.
func parseAPICustomFieldFilters(ctx *context.APIContext) (map[int64]string, error) {
filters := make(map[int64]string)
for key, values := range ctx.Req.URL.Query() {
after, ok := strings.CutPrefix(key, "cf_")
if !ok || len(values) == 0 || values[0] == "" {
continue
}
fieldID, err := strconv.ParseInt(after, 10, 64)
if err != nil || fieldID <= 0 {
ctx.APIError(http.StatusBadRequest, fmt.Sprintf("invalid custom field filter: cf_%s must use a positive numeric field ID", after))
return nil, fmt.Errorf("invalid cf_ param")
}
filters[fieldID] = values[0]
}
return filters, nil
}
// GetIssue get an issue of a repository
func GetIssue(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/issues/{index} issue issueGetIssue
@@ -702,6 +733,29 @@ 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
}
}
if len(vals) > 0 {
if setErr := issues_model.SetCustomFieldValues(ctx, issue.ID, vals); setErr != nil {
ctx.APIErrorInternal(setErr)
return
}
}
}
}
if form.Closed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
if issues_model.IsErrDependenciesLeft(err) {
+125
View File
@@ -0,0 +1,125 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"encoding/json"
"net/http"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// apiManifest is the JSON representation of a repo manifest.
type apiManifest struct {
Name string `json:"name"`
Org string `json:"org"`
Description string `json:"description"`
Version string `json:"version"`
LicenseSPDX string `json:"license_spdx"`
LicenseName string `json:"license_name"`
Platform string `json:"platform"`
StandardsVersion string `json:"standards_version"`
StandardsSource string `json:"standards_source"`
Language string `json:"language"`
PackageType string `json:"package_type"`
EntryPoint string `json:"entry_point"`
}
// GetRepoManifest returns the manifest settings for a repository.
func GetRepoManifest(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/manifest repository repoGetManifest
// ---
// summary: Get repo manifest settings
// produces:
// - application/json
// responses:
// "200":
// "$ref": "#/responses/Manifest"
// "404":
// "$ref": "#/responses/notFound"
m, err := repo_model.GetRepoManifest(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if m == nil {
// Return defaults from repo metadata.
ctx.JSON(http.StatusOK, &apiManifest{
Name: ctx.Repo.Repository.Name,
Org: ctx.Repo.Repository.OwnerName,
Description: ctx.Repo.Repository.Description,
})
return
}
ctx.JSON(http.StatusOK, &apiManifest{
Name: m.Name,
Org: m.Org,
Description: m.Description,
Version: m.Version,
LicenseSPDX: m.LicenseSPDX,
LicenseName: m.LicenseName,
Platform: m.Platform,
StandardsVersion: m.StandardsVersion,
StandardsSource: m.StandardsSource,
Language: m.Language,
PackageType: m.PackageType,
EntryPoint: m.EntryPoint,
})
}
// UpdateRepoManifest updates the manifest settings for a repository.
func UpdateRepoManifest(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/manifest repository repoUpdateManifest
// ---
// summary: Update repo manifest settings
// consumes:
// - application/json
// produces:
// - application/json
// responses:
// "200":
// "$ref": "#/responses/Manifest"
var req apiManifest
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
ctx.APIError(http.StatusBadRequest, err)
return
}
m := &repo_model.RepoManifest{
RepoID: ctx.Repo.Repository.ID,
Name: req.Name,
Org: req.Org,
Description: req.Description,
Version: req.Version,
LicenseSPDX: req.LicenseSPDX,
LicenseName: req.LicenseName,
Platform: req.Platform,
StandardsVersion: req.StandardsVersion,
StandardsSource: req.StandardsSource,
Language: req.Language,
PackageType: req.PackageType,
EntryPoint: req.EntryPoint,
}
if err := repo_model.CreateOrUpdateRepoManifest(ctx, m); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusOK, &apiManifest{
Name: m.Name,
Org: m.Org,
Description: m.Description,
Version: m.Version,
LicenseSPDX: m.LicenseSPDX,
LicenseName: m.LicenseName,
Platform: m.Platform,
StandardsVersion: m.StandardsVersion,
StandardsSource: m.StandardsSource,
Language: m.Language,
PackageType: m.PackageType,
EntryPoint: m.EntryPoint,
})
}
+120
View File
@@ -0,0 +1,120 @@
// 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")
}
+112
View File
@@ -0,0 +1,112 @@
// 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 tplOrgIssuePriorities templates.TplName = "org/settings/issue_priorities"
// SettingsIssuePriorities shows the org-level issue priorities management page.
func SettingsIssuePriorities(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("org.settings.issue_priorities")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsIssuePriorities"] = true
defs, err := issues_model.GetAllIssuePriorityDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.ServerError("GetAllIssuePriorityDefsByOrg", err)
return
}
ctx.Data["IssuePriorities"] = defs
ctx.HTML(http.StatusOK, tplOrgIssuePriorities)
}
// SettingsIssuePrioritiesCreatePost creates a new org-level issue priority.
func SettingsIssuePrioritiesCreatePost(ctx *context.Context) {
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
def := &issues_model.IssuePriorityDef{
OrgID: ctx.Org.Organization.ID,
Name: ctx.FormString("name"),
Color: ctx.FormString("color"),
Description: ctx.FormString("description"),
SortOrder: sortOrder,
IsDefault: ctx.FormString("is_default") == "on",
IsActive: true,
}
if def.Name == "" {
ctx.Flash.Error("Priority name is required")
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-priorities")
return
}
if err := issues_model.CreateIssuePriorityDef(ctx, def); err != nil {
ctx.ServerError("CreateIssuePriorityDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_priority_created"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-priorities")
}
// SettingsIssuePrioritiesEditPost updates an org-level issue priority.
func SettingsIssuePrioritiesEditPost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
def, err := issues_model.GetIssuePriorityDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetIssuePriorityDefByID", err)
return
}
if def.OrgID != ctx.Org.Organization.ID {
ctx.NotFound(nil)
return
}
def.Name = ctx.FormString("name")
def.Color = ctx.FormString("color")
def.Description = ctx.FormString("description")
def.IsDefault = ctx.FormString("is_default") == "on"
def.IsActive = ctx.FormString("is_active") == "on"
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
def.SortOrder = sortOrder
if err := issues_model.UpdateIssuePriorityDef(ctx, def); err != nil {
ctx.ServerError("UpdateIssuePriorityDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_priority_updated"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-priorities")
}
// SettingsIssuePrioritiesDeletePost deletes an org-level issue priority.
func SettingsIssuePrioritiesDeletePost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
def, err := issues_model.GetIssuePriorityDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetIssuePriorityDefByID", err)
return
}
if def.OrgID != ctx.Org.Organization.ID {
ctx.NotFound(nil)
return
}
if err := issues_model.DeleteIssuePriorityDef(ctx, id); err != nil {
ctx.ServerError("DeleteIssuePriorityDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_priority_deleted"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-priorities")
}
+112
View File
@@ -0,0 +1,112 @@
// 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 tplOrgIssueStatuses templates.TplName = "org/settings/issue_statuses"
// SettingsIssueStatuses shows the org-level issue statuses management page.
func SettingsIssueStatuses(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("org.settings.issue_statuses")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsIssueStatuses"] = true
defs, err := issues_model.GetAllIssueStatusDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.ServerError("GetAllIssueStatusDefsByOrg", err)
return
}
ctx.Data["IssueStatuses"] = defs
ctx.HTML(http.StatusOK, tplOrgIssueStatuses)
}
// SettingsIssueStatusesCreatePost creates a new org-level issue status.
func SettingsIssueStatusesCreatePost(ctx *context.Context) {
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
def := &issues_model.IssueStatusDef{
OrgID: ctx.Org.Organization.ID,
Name: ctx.FormString("name"),
Color: ctx.FormString("color"),
Description: ctx.FormString("description"),
ClosesIssue: ctx.FormString("closes_issue") == "on",
SortOrder: sortOrder,
IsActive: true,
}
if def.Name == "" {
ctx.Flash.Error("Status name is required")
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
return
}
if err := issues_model.CreateIssueStatusDef(ctx, def); err != nil {
ctx.ServerError("CreateIssueStatusDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_created"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
}
// SettingsIssueStatusesEditPost updates an org-level issue status.
func SettingsIssueStatusesEditPost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
def, err := issues_model.GetIssueStatusDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetIssueStatusDefByID", err)
return
}
if def.OrgID != ctx.Org.Organization.ID {
ctx.NotFound(nil)
return
}
def.Name = ctx.FormString("name")
def.Color = ctx.FormString("color")
def.Description = ctx.FormString("description")
def.ClosesIssue = ctx.FormString("closes_issue") == "on"
def.IsActive = ctx.FormString("is_active") == "on"
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
def.SortOrder = sortOrder
if err := issues_model.UpdateIssueStatusDef(ctx, def); err != nil {
ctx.ServerError("UpdateIssueStatusDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_updated"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
}
// SettingsIssueStatusesDeletePost deletes an org-level issue status.
func SettingsIssueStatusesDeletePost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
def, err := issues_model.GetIssueStatusDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetIssueStatusDefByID", err)
return
}
if def.OrgID != ctx.Org.Organization.ID {
ctx.NotFound(nil)
return
}
if err := issues_model.DeleteIssueStatusDef(ctx, id); err != nil {
ctx.ServerError("DeleteIssueStatusDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_status_deleted"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-statuses")
}
+98
View File
@@ -0,0 +1,98 @@
// 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 tplOrgIssueTypes templates.TplName = "org/settings/issue_types"
func SettingsIssueTypes(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("org.settings.issue_types")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsIssueTypes"] = true
defs, err := issues_model.GetAllIssueTypeDefsByOrg(ctx, ctx.Org.Organization.ID)
if err != nil {
ctx.ServerError("GetAllIssueTypeDefsByOrg", err)
return
}
ctx.Data["IssueTypes"] = defs
ctx.HTML(http.StatusOK, tplOrgIssueTypes)
}
func SettingsIssueTypesCreatePost(ctx *context.Context) {
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
def := &issues_model.IssueTypeDef{
OrgID: ctx.Org.Organization.ID,
Name: ctx.FormString("name"),
Color: ctx.FormString("color"),
Description: ctx.FormString("description"),
SortOrder: sortOrder,
IsDefault: ctx.FormString("is_default") == "on",
IsActive: true,
}
if def.Name == "" {
ctx.Flash.Error("Type name is required")
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
return
}
if err := issues_model.CreateIssueTypeDef(ctx, def); err != nil {
ctx.ServerError("CreateIssueTypeDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_type_created"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
}
func SettingsIssueTypesEditPost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
def, err := issues_model.GetIssueTypeDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetIssueTypeDefByID", err)
return
}
if def.OrgID != ctx.Org.Organization.ID {
ctx.NotFound(nil)
return
}
def.Name = ctx.FormString("name")
def.Color = ctx.FormString("color")
def.Description = ctx.FormString("description")
def.IsDefault = ctx.FormString("is_default") == "on"
def.IsActive = ctx.FormString("is_active") == "on"
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
def.SortOrder = sortOrder
if err := issues_model.UpdateIssueTypeDef(ctx, def); err != nil {
ctx.ServerError("UpdateIssueTypeDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_type_updated"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
}
func SettingsIssueTypesDeletePost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
def, err := issues_model.GetIssueTypeDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetIssueTypeDefByID", err)
return
}
if def.OrgID != ctx.Org.Organization.ID {
ctx.NotFound(nil)
return
}
if err := issues_model.DeleteIssueTypeDef(ctx, id); err != nil {
ctx.ServerError("DeleteIssueTypeDef", err)
return
}
ctx.Flash.Success(ctx.Tr("org.settings.issue_type_deleted"))
ctx.Redirect(ctx.Org.OrgLink + "/settings/issue-types")
}
+3 -3
View File
@@ -89,7 +89,7 @@ func Licenses(ctx *context.Context) {
}
}
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
pkgs, err := licenses.ListLicensePackagesWithAncestors(ctx, ownerID)
if err != nil {
ctx.ServerError("ListLicensePackages", err)
return
@@ -112,9 +112,9 @@ func Licenses(ctx *context.Context) {
var keys []*licenses.LicenseKey
if searchQuery != "" {
keys, err = licenses.SearchLicenseKeys(ctx, ownerID, searchQuery)
keys, err = licenses.SearchLicenseKeysWithAncestors(ctx, ownerID, searchQuery)
} else {
keys, err = licenses.ListLicenseKeys(ctx, ownerID)
keys, err = licenses.ListLicenseKeysWithAncestors(ctx, ownerID)
}
if err != nil {
ctx.ServerError("ListLicenseKeys", err)
+1 -1
View File
@@ -630,7 +630,7 @@ func (cpi *comparePageInfoType) prepareCreatePullRequestPage(ctx *context.Contex
if ctx.Written() {
return
}
_, templateErrs := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates, pageMetaData)
_, templateErrs, _ := setTemplateIfExists(ctx, pullRequestTemplateKey, pullRequestTemplateCandidates, pageMetaData)
if len(templateErrs) > 0 {
ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true)
}
+6
View File
@@ -30,6 +30,12 @@ func CheckDownloadGating(ctx *context.Context, tagName string) bool {
return true // no download gating configured
}
// Signed-in users with repo access bypass download gating.
// The gate is for anonymous/external clients (Joomla update checker).
if ctx.IsSigned && ctx.Repo.Permission.HasAnyUnitAccess() {
return true
}
// For prerelease-only gating, check if this is a prerelease tag.
if gating == "prerelease" && tagName != "" {
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
+39
View File
@@ -184,6 +184,45 @@ func NewComment(ctx *context.Context) {
}
} // end if: handle close or reopen
// Handle custom status from the status dropdown (replaces close button for issues with org statuses).
if statusIDStr := ctx.Req.FormValue("status_id"); statusIDStr != "" && statusIDStr != "" {
if statusIDStr == "reopen" {
// Reopen via dropdown
if issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("ReopenIssue via status dropdown: %v", err)
}
if err := issues_model.SetIssueStatusID(ctx, issue.ID, 0); err != nil {
log.Error("SetIssueStatusID: %v", err)
}
}
} else if statusIDStr == "close" {
// Plain close via dropdown
if !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("CloseIssue via status dropdown: %v", err)
}
}
} else if statusID, err := strconv.ParseInt(statusIDStr, 10, 64); err == nil && statusID > 0 {
// Custom status selected
statusDef, err := issues_model.GetIssueStatusDefByID(ctx, statusID)
if err == nil && statusDef.OrgID == ctx.Repo.Repository.OwnerID {
if err := issues_model.SetIssueStatusID(ctx, issue.ID, statusID); err != nil {
log.Error("SetIssueStatusID: %v", err)
}
if statusDef.ClosesIssue && !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("CloseIssue via custom status: %v", err)
}
} else if !statusDef.ClosesIssue && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("ReopenIssue via custom status: %v", err)
}
}
}
}
}
ctx.JSONRedirect(redirect)
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"fmt"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// UpdateIssueCustomField handles POST to set a custom field value on an issue.
func UpdateIssueCustomField(ctx *context.Context) {
issueID := ctx.PathParamInt64("id")
fieldID := ctx.PathParamInt64("field_id")
value := ctx.FormString("value")
// Look up issue to get the index for redirect.
issue, err := issues_model.GetIssueByID(ctx, issueID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if err := issues_model.SetCustomFieldValue(ctx, issueID, fieldID, value); err != nil {
ctx.ServerError("SetCustomFieldValue", err)
return
}
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"fmt"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// UpdateIssueCustomPriority handles POST to set a custom priority on an issue.
func UpdateIssueCustomPriority(ctx *context.Context) {
issueID := ctx.PathParamInt64("id")
priorityID := ctx.FormInt64("priority_id")
issue, err := issues_model.GetIssueByID(ctx, issueID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
// Validate the priority belongs to this repo's org.
if priorityID > 0 {
priorityDef, err := issues_model.GetIssuePriorityDefByID(ctx, priorityID)
if err != nil {
ctx.ServerError("GetIssuePriorityDefByID", err)
return
}
if priorityDef.OrgID != ctx.Repo.Repository.OwnerID {
ctx.NotFound(nil)
return
}
}
if err := issues_model.SetIssuePriorityID(ctx, issueID, priorityID); err != nil {
ctx.ServerError("SetIssuePriorityID", err)
return
}
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"fmt"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
issue_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/issue"
)
// UpdateIssueCustomStatus handles POST to set a custom status on an issue.
// If the chosen status has ClosesIssue=true, the issue is automatically closed.
// If the chosen status has ClosesIssue=false and the issue is closed, it is reopened.
func UpdateIssueCustomStatus(ctx *context.Context) {
issueID := ctx.PathParamInt64("id")
statusID := ctx.FormInt64("status_id")
issue, err := issues_model.GetIssueByID(ctx, issueID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
// Validate the status belongs to this repo's org (or is being cleared).
if statusID > 0 {
statusDef, err := issues_model.GetIssueStatusDefByID(ctx, statusID)
if err != nil {
ctx.ServerError("GetIssueStatusDefByID", err)
return
}
if statusDef.OrgID != ctx.Repo.Repository.OwnerID {
ctx.NotFound(nil)
return
}
// Handle automatic close/reopen based on the status definition.
if statusDef.ClosesIssue && !issue.IsClosed {
if err := issue_service.CloseIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("UpdateIssueCustomStatus: CloseIssue: %v", err)
}
} else if !statusDef.ClosesIssue && issue.IsClosed {
if err := issue_service.ReopenIssue(ctx, issue, ctx.Doer, ""); err != nil {
log.Error("UpdateIssueCustomStatus: ReopenIssue: %v", err)
}
}
}
if err := issues_model.SetIssueStatusID(ctx, issueID, statusID); err != nil {
ctx.ServerError("SetIssueStatusID", err)
return
}
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"fmt"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
// UpdateIssueCustomType handles POST to set a custom type on an issue.
func UpdateIssueCustomType(ctx *context.Context) {
issueID := ctx.PathParamInt64("id")
typeID := ctx.FormInt64("type_id")
issue, err := issues_model.GetIssueByID(ctx, issueID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if typeID > 0 {
typeDef, err := issues_model.GetIssueTypeDefByID(ctx, typeID)
if err != nil {
ctx.ServerError("GetIssueTypeDefByID", err)
return
}
if typeDef.OrgID != ctx.Repo.Repository.OwnerID {
ctx.NotFound(nil)
return
}
}
if err := issues_model.SetIssueTypeID(ctx, issueID, typeID); err != nil {
ctx.ServerError("SetIssueTypeID", err)
return
}
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index), http.StatusSeeOther)
}
+72 -14
View File
@@ -5,8 +5,11 @@ package repo
import (
"bytes"
"encoding/json"
"fmt"
"maps"
"net/http"
"net/url"
"slices"
"sort"
"strconv"
@@ -521,20 +524,60 @@ func prepareIssueFilterAndList(ctx *context.Context, milestoneID int64, projectI
prepareIssueFilterExclusiveOrderScopes(ctx, preparedLabelFilter.AllLabels)
// Parse custom field filters from query params (cf_{fieldID}={value}).
customFieldFilters := parseCustomFieldQueryParams(ctx.Req.URL.Query())
// Load custom field definitions for the filter UI.
// If this fails, clear filters so users don't get invisible filtering.
customFieldDefs, cfErr := issues_model.GetCustomFieldsByOwner(ctx, repo.OwnerID, issues_model.CustomFieldScopeIssue)
if cfErr != nil {
log.Error("prepareIssueFilterAndList: GetCustomFieldsByOwner: %v", cfErr)
customFieldFilters = make(map[int64]string)
}
ctx.Data["CustomFieldDefs"] = customFieldDefs
ctx.Data["CustomFieldFilters"] = customFieldFilters
// Load first-class field definitions for issue list badges
issueStatusDefs, _ := issues_model.GetIssueStatusDefsByOrg(ctx, repo.OwnerID)
ctx.Data["IssueStatusDefs"] = issueStatusDefs
issuePriorityDefs, _ := issues_model.GetIssuePriorityDefsByOrg(ctx, repo.OwnerID)
ctx.Data["IssuePriorityDefs"] = issuePriorityDefs
issueTypeDefs, _ := issues_model.GetIssueTypeDefsByOrg(ctx, repo.OwnerID)
ctx.Data["IssueTypeDefs"] = issueTypeDefs
// Build a query string fragment for cf_ params so they survive pagination/sort changes.
cfQuery := make(url.Values)
for fieldID, value := range customFieldFilters {
cfQuery.Set(fmt.Sprintf("cf_%d", fieldID), value)
}
ctx.Data["CustomFieldQueryString"] = cfQuery.Encode()
fieldOptions := make(map[int64][]string)
for _, f := range customFieldDefs {
if f.Options != "" {
var opts []string
if err := json.Unmarshal([]byte(f.Options), &opts); err != nil {
log.Error("prepareIssueFilterAndList: invalid options JSON for field %d (%s): %v", f.ID, f.Name, err)
} else {
fieldOptions[f.ID] = opts
}
}
}
ctx.Data["CustomFieldOptions"] = fieldOptions
var keywordMatchedIssueIDs []int64
var issueStats *issues_model.IssueStats
statsOpts := &issues_model.IssuesOptions{
RepoIDs: []int64{repo.ID},
LabelIDs: preparedLabelFilter.SelectedLabelIDs,
MilestoneIDs: mileIDs,
ProjectIDs: projectIDs,
AssigneeID: assigneeID,
MentionedID: mentionedID,
PosterID: posterUserID,
ReviewRequestedID: reviewRequestedID,
ReviewedID: reviewedID,
IsPull: isPullOption,
IssueIDs: nil,
RepoIDs: []int64{repo.ID},
LabelIDs: preparedLabelFilter.SelectedLabelIDs,
MilestoneIDs: mileIDs,
ProjectIDs: projectIDs,
AssigneeID: assigneeID,
MentionedID: mentionedID,
PosterID: posterUserID,
ReviewRequestedID: reviewRequestedID,
ReviewedID: reviewedID,
IsPull: isPullOption,
IssueIDs: nil,
CustomFieldFilters: customFieldFilters,
}
if keyword != "" {
@@ -611,9 +654,10 @@ func prepareIssueFilterAndList(ctx *context.Context, milestoneID int64, projectI
ProjectIDs: projectIDs,
IsClosed: isShowClosed,
IsPull: isPullOption,
LabelIDs: preparedLabelFilter.SelectedLabelIDs,
SortType: sortType,
IssueIDs: keywordMatchedIssueIDs,
LabelIDs: preparedLabelFilter.SelectedLabelIDs,
SortType: sortType,
IssueIDs: keywordMatchedIssueIDs,
CustomFieldFilters: customFieldFilters,
})
if err != nil {
ctx.ServerError("DBIndexer.Search", err)
@@ -771,3 +815,17 @@ func Issues(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplIssues)
}
// parseCustomFieldQueryParams extracts cf_{fieldID}=value query parameters.
// Non-numeric or non-positive field IDs are silently skipped.
func parseCustomFieldQueryParams(query url.Values) map[int64]string {
filters := make(map[int64]string)
for key, values := range query {
if after, ok := strings.CutPrefix(key, "cf_"); ok && len(values) > 0 && values[0] != "" {
if fieldID, err := strconv.ParseInt(after, 10, 64); err == nil && fieldID > 0 {
filters[fieldID] = values[0]
}
}
}
return filters
}
+65 -5
View File
@@ -4,6 +4,7 @@
package repo
import (
"encoding/json"
"errors"
"fmt"
"html/template"
@@ -36,10 +37,11 @@ import (
)
// Tries to load and set an issue template. The first return value indicates if a template was loaded.
func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string, metaData *IssuePageMetaData) (bool, map[string]error) {
// The third return value contains the template's custom_fields map (field name → default value).
func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles []string, metaData *IssuePageMetaData) (bool, map[string]error, map[string]string) {
commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
return false, nil
return false, nil, nil
}
templateCandidates := make([]string, 0, 1+len(possibleFiles))
@@ -84,9 +86,9 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
}
metaData.AssigneesData.SelectedAssigneeIDs = strings.Join(selectedAssigneeIDStrings, ",")
return true, templateErrs
return true, templateErrs, template.CustomFields
}
return false, templateErrs
return false, templateErrs, nil
}
// NewIssue render creating issue page
@@ -128,7 +130,7 @@ func NewIssue(ctx *context.Context) {
ctx.Data["Tags"] = tags
ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, pageMetaData)
templateLoaded, errs, templateCustomFields := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, pageMetaData)
maps.Copy(ret.TemplateErrors, errs)
if ctx.Written() {
return
@@ -140,6 +142,35 @@ func NewIssue(ctx *context.Context) {
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypeIssues)
// Load org-level issue-scoped custom fields for the new issue sidebar.
customFieldDefs, cfErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
if cfErr != nil {
log.Error("NewIssue: GetCustomFieldsByOwner: %v", cfErr)
}
ctx.Data["CustomFieldDefs"] = customFieldDefs
customFieldValues := make(map[int64]string)
fieldOptions := make(map[int64][]string)
if len(customFieldDefs) > 0 {
// Resolve template custom_fields (name → value) to field IDs.
if len(templateCustomFields) > 0 {
for _, def := range customFieldDefs {
if val, ok := templateCustomFields[def.Name]; ok {
customFieldValues[def.ID] = val
}
}
}
for _, f := range customFieldDefs {
if f.Options != "" {
var opts []string
if err := json.Unmarshal([]byte(f.Options), &opts); err == nil {
fieldOptions[f.ID] = opts
}
}
}
}
ctx.Data["CustomFieldValues"] = customFieldValues
ctx.Data["CustomFieldOptions"] = fieldOptions
if !issueConfig.BlankIssuesEnabled && hasTemplates && !templateLoaded {
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if blank issues are disabled, just redirect to the "issues/choose" page with these parameters.
ctx.Redirect(fmt.Sprintf("%s/issues/new/choose?%s", ctx.Repo.Repository.Link(), ctx.Req.URL.RawQuery), http.StatusSeeOther)
@@ -377,6 +408,9 @@ func NewIssuePost(ctx *context.Context) {
return
}
// Save custom field values submitted from the new issue form.
saveCustomFieldsFromForm(ctx, repo.OwnerID, issue.ID)
log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
if ctx.FormString("redirect_after_creation") == "project" && len(projectIDs) > 0 {
// When issue is in multiple projects, redirect to first project from form order.
@@ -392,3 +426,29 @@ func NewIssuePost(ctx *context.Context) {
}
ctx.JSONRedirect(issue.Link())
}
// saveCustomFieldsFromForm reads custom field values from the form
// (submitted as "custom-field-{fieldID}") and persists them for the issue.
func saveCustomFieldsFromForm(ctx *context.Context, ownerID, issueID int64) {
defs, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeIssue)
if err != nil {
log.Error("saveCustomFieldsFromForm: GetCustomFieldsByOwner: %v", err)
return
}
if len(defs) == 0 {
return
}
vals := make(map[int64]string)
for _, def := range defs {
v := ctx.Req.FormValue(fmt.Sprintf("custom-field-%d", def.ID))
if v != "" {
vals[def.ID] = v
}
}
if len(vals) > 0 {
if err := issues_model.SetCustomFieldValues(ctx, issueID, vals); err != nil {
log.Error("saveCustomFieldsFromForm: %v", err)
ctx.Flash.Error("Failed to save custom field values")
}
}
}
+49
View File
@@ -4,6 +4,7 @@
package repo
import (
"encoding/json"
"fmt"
"math/big"
"net/http"
@@ -337,6 +338,54 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["IsProjectsEnabled"] = ctx.Repo.Permission.CanRead(unit.TypeProjects)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
// Load custom fields for the issue sidebar (org-level issue-scoped fields).
customFieldDefs, cfErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
if cfErr != nil {
log.Error("ViewIssue: GetCustomFieldsByOwner: %v", cfErr)
}
ctx.Data["CustomFieldDefs"] = customFieldDefs
customFieldValues := make(map[int64]string)
fieldOptions := make(map[int64][]string)
if len(customFieldDefs) > 0 {
var cvErr error
customFieldValues, cvErr = issues_model.GetCustomFieldValuesMap(ctx, issue.ID)
if cvErr != nil {
log.Error("ViewIssue: GetCustomFieldValuesMap: %v", cvErr)
}
for _, f := range customFieldDefs {
if f.Options != "" {
var opts []string
if err := json.Unmarshal([]byte(f.Options), &opts); err == nil {
fieldOptions[f.ID] = opts
}
}
}
}
ctx.Data["CustomFieldValues"] = customFieldValues
ctx.Data["CustomFieldOptions"] = fieldOptions
// Load custom issue status definitions for the sidebar.
issueStatusDefs, isErr := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Repo.Repository.OwnerID)
if isErr != nil {
log.Error("ViewIssue: GetIssueStatusDefsByOrg: %v", isErr)
}
ctx.Data["IssueStatusDefs"] = issueStatusDefs
// Load custom issue priority definitions for the sidebar.
issuePriorityDefs, ipErr := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Repo.Repository.OwnerID)
if ipErr != nil {
log.Error("ViewIssue: GetIssuePriorityDefsByOrg: %v", ipErr)
}
ctx.Data["IssuePriorityDefs"] = issuePriorityDefs
// Load custom issue type definitions for the sidebar.
issueTypeDefs, itErr := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Repo.Repository.OwnerID)
if itErr != nil {
log.Error("ViewIssue: GetIssueTypeDefsByOrg: %v", itErr)
}
ctx.Data["IssueTypeDefs"] = issueTypeDefs
upload.AddUploadContext(ctx, "comment")
if err := issue.LoadAttributes(ctx); err != nil {
+81 -13
View File
@@ -90,6 +90,10 @@ func Licenses(ctx *context.Context) {
}
}
// Always load the master key for display (prefix + status).
masterKey, _ := licenses.GetMasterKey(ctx, ownerID)
ctx.Data["MasterKey"] = masterKey
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
if err != nil {
ctx.ServerError("ListLicensePackages", err)
@@ -183,15 +187,16 @@ func LicensesCreatePackage(ctx *context.Context) {
}
pkg := &licenses.LicensePackage{
OwnerID: ctx.Repo.Repository.OwnerID,
Name: name,
Description: ctx.FormString("description"),
DurationDays: durationDays,
MaxSites: maxSites,
DomainLockHours: domainLockHours,
AllowedChannels: allowedChannels,
RepoScope: repoScope,
IsActive: true,
OwnerID: ctx.Repo.Repository.OwnerID,
Name: name,
Description: ctx.FormString("description"),
DurationDays: durationDays,
MaxSites: maxSites,
DomainLockHours: domainLockHours,
AllowedChannels: allowedChannels,
DomainRestriction: strings.TrimSpace(ctx.FormString("domain_restriction")),
RepoScope: repoScope,
IsActive: true,
}
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
@@ -203,9 +208,62 @@ func LicensesCreatePackage(ctx *context.Context) {
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
}
// LicensesRegenerateMasterKey handles POST to regenerate the master key.
func LicensesRegenerateMasterKey(ctx *context.Context) {
ownerID := ctx.Repo.Repository.OwnerID
// Deactivate the old master key.
oldKey, _ := licenses.GetMasterKey(ctx, ownerID)
if oldKey != nil {
oldKey.IsActive = false
_ = licenses.UpdateLicenseKey(ctx, oldKey)
}
// Find the master package.
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
if err != nil {
ctx.ServerError("ListLicensePackages", err)
return
}
var masterPkg *licenses.LicensePackage
for _, pkg := range pkgs {
if pkg.Name == licenses.MasterPackageName {
masterPkg = pkg
break
}
}
if masterPkg == nil {
ctx.Flash.Error("Master package not found")
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
return
}
// Create a new master key.
newKey := &licenses.LicenseKey{
PackageID: masterPkg.ID,
OwnerID: ownerID,
IsInternal: true,
IsActive: true,
}
rawKey, err := licenses.CreateLicenseKey(ctx, newKey)
if err != nil {
ctx.ServerError("CreateLicenseKey", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.licenses.master_key_regenerated"))
ctx.Data["NewMasterKey"] = rawKey
Licenses(ctx)
}
// LicensesGenerateKey handles POST to generate a new key from a package.
func LicensesGenerateKey(ctx *context.Context) {
packageID, _ := strconv.ParseInt(ctx.FormString("package_id"), 10, 64)
// Accept package_id from form body or query string (modal sets it via form action URL).
pkgIDStr := ctx.FormString("package_id")
if pkgIDStr == "" {
pkgIDStr = ctx.Req.URL.Query().Get("package_id")
}
packageID, _ := strconv.ParseInt(pkgIDStr, 10, 64)
if packageID == 0 {
ctx.Flash.Error("Invalid package")
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
@@ -218,10 +276,19 @@ func LicensesGenerateKey(ctx *context.Context) {
return
}
// Domain restriction: use form input, fall back to package default.
domainRestriction := strings.TrimSpace(ctx.FormString("domain_restriction"))
if domainRestriction == "" && pkg.DomainRestriction != "" {
domainRestriction = pkg.DomainRestriction
}
key := &licenses.LicenseKey{
PackageID: packageID,
OwnerID: ctx.Repo.Repository.OwnerID,
IsActive: true,
PackageID: packageID,
OwnerID: ctx.Repo.Repository.OwnerID,
IsActive: true,
DomainRestriction: domainRestriction,
LicenseeName: strings.TrimSpace(ctx.FormString("licensee_name")),
LicenseeEmail: strings.TrimSpace(ctx.FormString("licensee_email")),
}
// Auto-calculate expiry from package duration.
@@ -448,6 +515,7 @@ func LicensesEditPackagePost(ctx *context.Context) {
pkg.AllowedChannels = ""
}
pkg.DomainRestriction = strings.TrimSpace(ctx.FormString("domain_restriction"))
pkg.IsActive = ctx.FormString("is_active") == "on"
if err := licenses.UpdateLicensePackage(ctx, pkg); err != nil {
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"net/http"
security_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/security"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
security_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/security"
)
const tplRepoSecurity templates.TplName = "repo/security"
// Security renders the repo-level security tab showing alerts and scan controls.
func Security(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.security")
ctx.Data["PageIsSecurity"] = true
repoID := ctx.Repo.Repository.ID
cfg, err := security_model.GetScannerConfig(ctx, repoID)
if err != nil {
ctx.ServerError("GetScannerConfig", err)
return
}
ctx.Data["ScannerConfig"] = cfg
alerts, err := security_model.GetAllAlerts(ctx, repoID)
if err != nil {
ctx.ServerError("GetAllAlerts", err)
return
}
ctx.Data["SecurityAlerts"] = alerts
counts, err := security_model.GetAlertCountsByRepo(ctx, repoID)
if err != nil {
ctx.ServerError("GetAlertCountsByRepo", err)
return
}
ctx.Data["AlertCounts"] = counts
ctx.HTML(http.StatusOK, tplRepoSecurity)
}
// SecurityScanNow triggers an immediate scan from the security tab.
func SecurityScanNow(ctx *context.Context) {
commit := ctx.Repo.Commit
if commit == nil {
ctx.Flash.Error("No commits found")
ctx.Redirect(ctx.Repo.RepoLink + "/security")
return
}
security_service.ScanOnPush(ctx, ctx.Repo.Repository, commit)
ctx.Flash.Success(ctx.Tr("repo.settings.security_scan_complete"))
ctx.Redirect(ctx.Repo.RepoLink + "/security")
}
// SecurityAlertUpdate changes alert status from the security tab.
func SecurityAlertUpdateTab(ctx *context.Context) {
id := ctx.PathParamInt64("id")
status := security_model.AlertStatus(ctx.FormString("status"))
if status != security_model.AlertStatusResolved && status != security_model.AlertStatusDismissed {
status = security_model.AlertStatusDismissed
}
alert, err := security_model.GetAlertByID(ctx, id)
if err != nil {
ctx.ServerError("GetAlertByID", err)
return
}
if alert.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound(nil)
return
}
if err := security_model.UpdateAlertStatus(ctx, id, status, ctx.Doer.ID); err != nil {
ctx.ServerError("UpdateAlertStatus", err)
return
}
ctx.Flash.Success("Alert updated")
ctx.Redirect(ctx.Repo.RepoLink + "/security")
}
+1
View File
@@ -15,6 +15,7 @@ const tplSettingsAdvanced templates.TplName = "repo/settings/advanced"
// AdvancedSettings displays the advanced (feature units) settings page.
func AdvancedSettings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.advanced_settings")
ctx.Data["PageIsSettingsOptions"] = false
ctx.Data["PageIsSettingsAdvanced"] = true
ctx.HTML(http.StatusOK, tplSettingsAdvanced)
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
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 tplSettingsCustomFields templates.TplName = "repo/settings/custom_fields"
// CustomFields displays the custom fields settings page.
func CustomFields(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.custom_fields")
ctx.Data["PageIsSettingsCustomFields"] = true
fields, err := issues_model.GetAllCustomFieldsByRepo(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("GetAllCustomFieldsByRepo", err)
return
}
ctx.Data["CustomFields"] = fields
ctx.HTML(http.StatusOK, tplSettingsCustomFields)
}
// CustomFieldsCreatePost creates a new custom field definition.
func CustomFieldsCreatePost(ctx *context.Context) {
sortOrder, _ := strconv.Atoi(ctx.FormString("sort_order"))
field := &issues_model.CustomFieldDef{
RepoID: ctx.Repo.Repository.ID,
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.Repo.RepoLink + "/settings/custom-fields")
return
}
if err := issues_model.CreateCustomFieldDef(ctx, field); err != nil {
ctx.ServerError("CreateCustomFieldDef", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.custom_field_created"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/custom-fields")
}
// CustomFieldsEditPost updates a custom field definition.
func CustomFieldsEditPost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
field, err := issues_model.GetCustomFieldDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetCustomFieldDefByID", err)
return
}
if field.RepoID != ctx.Repo.Repository.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("repo.settings.custom_field_updated"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/custom-fields")
}
// CustomFieldsDeletePost deletes a custom field definition.
func CustomFieldsDeletePost(ctx *context.Context) {
id := ctx.PathParamInt64("id")
field, err := issues_model.GetCustomFieldDefByID(ctx, id)
if err != nil {
ctx.ServerError("GetCustomFieldDefByID", err)
return
}
if field.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound(nil)
return
}
if err := issues_model.DeleteCustomFieldDef(ctx, id); err != nil {
ctx.ServerError("DeleteCustomFieldDef", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.custom_field_deleted"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/custom-fields")
}
+163
View File
@@ -0,0 +1,163 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
import (
"encoding/xml"
"fmt"
"net/http"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
const tplSettingsManifest templates.TplName = "repo/settings/manifest"
// manifestXML mirrors the .mokogitea/manifest.xml schema for XML parsing.
type manifestXML struct {
XMLName xml.Name `xml:"moko-platform"`
Identity manifestIdentity `xml:"identity"`
Governance manifestGovernance `xml:"governance"`
Build manifestBuild `xml:"build"`
}
type manifestIdentity struct {
Name string `xml:"name"`
Org string `xml:"org"`
Description string `xml:"description"`
Version string `xml:"version"`
License manifestLicense `xml:"license"`
}
type manifestLicense struct {
SPDX string `xml:"spdx,attr"`
Name string `xml:",chardata"`
}
type manifestGovernance struct {
Platform string `xml:"platform"`
StandardsVersion string `xml:"standards-version"`
StandardsSource string `xml:"standards-source"`
}
type manifestBuild struct {
Language string `xml:"language"`
PackageType string `xml:"package-type"`
EntryPoint string `xml:"entry-point"`
}
// ManifestSettings displays the repo manifest settings page.
// On first visit, if no manifest exists in DB but .mokogitea/manifest.xml
// exists in the repo, it auto-migrates the XML values into the database.
func ManifestSettings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.manifest")
ctx.Data["PageIsSettingsManifest"] = true
repoID := ctx.Repo.Repository.ID
manifest, err := repo_model.GetRepoManifest(ctx, repoID)
if err != nil {
ctx.ServerError("GetRepoManifest", err)
return
}
// Auto-detect and migrate .mokogitea/manifest.xml if no DB record exists.
if manifest == nil {
manifest = tryMigrateManifestXML(ctx)
}
if manifest == nil {
// No manifest found — provide empty defaults from repo metadata.
manifest = &repo_model.RepoManifest{
RepoID: repoID,
Name: ctx.Repo.Repository.Name,
Org: ctx.Repo.Repository.OwnerName,
Description: ctx.Repo.Repository.Description,
}
}
ctx.Data["Manifest"] = manifest
ctx.HTML(http.StatusOK, tplSettingsManifest)
}
// ManifestSettingsPost saves manifest settings from the form.
func ManifestSettingsPost(ctx *context.Context) {
manifest := &repo_model.RepoManifest{
RepoID: ctx.Repo.Repository.ID,
Name: ctx.FormString("name"),
Org: ctx.FormString("org"),
Description: ctx.Repo.Repository.Description,
Version: ctx.FormString("version"),
LicenseSPDX: ctx.FormString("license_spdx"),
LicenseName: ctx.FormString("license_name"),
Platform: ctx.FormString("platform"),
StandardsVersion: ctx.FormString("standards_version"),
StandardsSource: ctx.FormString("standards_source"),
Language: ctx.FormString("language"),
PackageType: ctx.FormString("package_type"),
EntryPoint: ctx.FormString("entry_point"),
}
if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
ctx.ServerError("CreateOrUpdateRepoManifest", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.manifest_saved"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/manifest")
}
// tryMigrateManifestXML reads .mokogitea/manifest.xml from the repo,
// parses it, and stores the values in the DB. Returns nil if no file found.
func tryMigrateManifestXML(ctx *context.Context) *repo_model.RepoManifest {
if ctx.Repo.GitRepo == nil || ctx.Repo.Commit == nil {
return nil
}
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(".mokogitea/manifest.xml")
if err != nil || entry == nil {
return nil // no manifest.xml found — not an error
}
reader, err := entry.Blob().DataAsync()
if err != nil {
log.Error("ManifestMigrate: read blob: %v", err)
return nil
}
defer reader.Close()
var mxml manifestXML
if err := xml.NewDecoder(reader).Decode(&mxml); err != nil {
log.Error("ManifestMigrate: parse XML: %v", err)
return nil
}
manifest := &repo_model.RepoManifest{
RepoID: ctx.Repo.Repository.ID,
Name: mxml.Identity.Name,
Org: mxml.Identity.Org,
Description: mxml.Identity.Description,
Version: mxml.Identity.Version,
LicenseSPDX: mxml.Identity.License.SPDX,
LicenseName: mxml.Identity.License.Name,
Platform: mxml.Governance.Platform,
StandardsVersion: mxml.Governance.StandardsVersion,
StandardsSource: mxml.Governance.StandardsSource,
Language: mxml.Build.Language,
PackageType: mxml.Build.PackageType,
EntryPoint: mxml.Build.EntryPoint,
}
if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
log.Error("ManifestMigrate: save to DB: %v", err)
return nil
}
log.Info("ManifestMigrate: migrated .mokogitea/manifest.xml for repo %s/%s",
ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name)
ctx.Flash.Info(fmt.Sprintf("Manifest settings imported from .mokogitea/manifest.xml. You can now delete the file from the repository."))
return manifest
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
import (
"encoding/json"
"fmt"
"net/http"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
const tplSettingsMetadata templates.TplName = "repo/settings/metadata"
// Metadata displays the repo metadata page (repo-scoped custom field values).
func Metadata(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.metadata")
ctx.Data["PageIsSettingsMetadata"] = true
ownerID := ctx.Repo.Repository.OwnerID
repoID := ctx.Repo.Repository.ID
fields, _ := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
ctx.Data["CustomFieldDefs"] = fields
values := make(map[int64]string)
fieldOptions := make(map[int64][]string)
if len(fields) > 0 {
values, _ = issues_model.GetCustomFieldValuesMap(ctx, repoID)
for _, f := range fields {
if f.Options != "" {
var opts []string
if err := json.Unmarshal([]byte(f.Options), &opts); err == nil {
fieldOptions[f.ID] = opts
}
}
}
}
ctx.Data["CustomFieldValues"] = values
ctx.Data["CustomFieldOptions"] = fieldOptions
ctx.HTML(http.StatusOK, tplSettingsMetadata)
}
// MetadataPost saves repo-scoped custom field values.
func MetadataPost(ctx *context.Context) {
repoID := ctx.Repo.Repository.ID
ownerID := ctx.Repo.Repository.OwnerID
fields, _ := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
for _, f := range fields {
val := ctx.Req.FormValue(fmt.Sprintf("field_%d", f.ID))
if err := issues_model.SetCustomFieldValue(ctx, repoID, f.ID, val); err != nil {
ctx.ServerError("SetCustomFieldValue", err)
return
}
}
ctx.Flash.Success(ctx.Tr("repo.settings.metadata_saved"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/metadata")
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package setting
import (
"net/http"
security_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/security"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
security_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/security"
)
const tplSettingsSecurity templates.TplName = "repo/settings/security"
// SecuritySettings displays the repo security scanning settings and alerts.
func SecuritySettings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.security")
ctx.Data["PageIsSettingsSecurity"] = true
repoID := ctx.Repo.Repository.ID
cfg, err := security_model.GetScannerConfig(ctx, repoID)
if err != nil {
ctx.ServerError("GetScannerConfig", err)
return
}
ctx.Data["ScannerConfig"] = cfg
alerts, err := security_model.GetAllAlerts(ctx, repoID)
if err != nil {
ctx.ServerError("GetAllAlerts", err)
return
}
ctx.Data["SecurityAlerts"] = alerts
counts, err := security_model.GetAlertCountsByRepo(ctx, repoID)
if err != nil {
ctx.ServerError("GetAlertCountsByRepo", err)
return
}
ctx.Data["AlertCounts"] = counts
ctx.HTML(http.StatusOK, tplSettingsSecurity)
}
// SecuritySettingsPost saves security scanner configuration.
func SecuritySettingsPost(ctx *context.Context) {
cfg := &security_model.SecurityScannerConfig{
RepoID: ctx.Repo.Repository.ID,
Enabled: ctx.FormString("enabled") == "on",
BlockOnPush: ctx.FormString("block_on_push") == "on",
SecretScanner: ctx.FormString("secret_scanner") == "on",
DependScanner: ctx.FormString("depend_scanner") == "on",
CodeScanner: ctx.FormString("code_scanner") == "on",
ConfigScanner: ctx.FormString("config_scanner") == "on",
LicenseScanner: ctx.FormString("license_scanner") == "on",
}
if err := security_model.SaveScannerConfig(ctx, cfg); err != nil {
ctx.ServerError("SaveScannerConfig", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.security_saved"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/security")
}
// SecurityScanNow triggers an immediate scan of the repository.
func SecurityScanNow(ctx *context.Context) {
commit := ctx.Repo.Commit
if commit == nil {
ctx.Flash.Error("No commits found in repository")
ctx.Redirect(ctx.Repo.RepoLink + "/settings/security")
return
}
security_service.ScanOnPush(ctx, ctx.Repo.Repository, commit)
ctx.Flash.Success(ctx.Tr("repo.settings.security_scan_complete"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/security")
}
// SecurityAlertUpdate changes the status of a security alert.
func SecurityAlertUpdate(ctx *context.Context) {
id := ctx.PathParamInt64("id")
status := security_model.AlertStatus(ctx.FormString("status"))
if status != security_model.AlertStatusResolved && status != security_model.AlertStatusDismissed {
status = security_model.AlertStatusDismissed
}
alert, err := security_model.GetAlertByID(ctx, id)
if err != nil {
ctx.ServerError("GetAlertByID", err)
return
}
if alert.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound(nil)
return
}
if err := security_model.UpdateAlertStatus(ctx, id, status, ctx.Doer.ID); err != nil {
ctx.ServerError("UpdateAlertStatus", err)
return
}
ctx.Flash.Success("Alert updated")
ctx.Redirect(ctx.Repo.RepoLink + "/settings/security")
}
+1 -1
View File
@@ -703,7 +703,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
ctx.Redirect(ctx.Repo.RepoLink + "/settings/advanced")
}
func handleSettingsPostSigning(ctx *context.Context) {
+3 -1
View File
@@ -94,7 +94,9 @@ func ServeUpdatesXML(ctx *context.Context) {
}
repoCfg, _ := licenses.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
requireKey := repoCfg != nil && repoCfg.RequireKey
// Show <downloadkey> only when downloads are gated (prerelease or all).
// No gating = no license keys needed = no downloadkey element.
requireKey := repoCfg != nil && repoCfg.DownloadGating != "" && repoCfg.DownloadGating != "none"
xmlData, err := updateserver.GenerateJoomlaXML(ctx, ctx.Repo.Repository, requireKey, stripDownloads, allowedChannels...)
if err != nil {
+45
View File
@@ -151,6 +151,51 @@ func prepareToRenderDirectory(ctx *context.Context) {
return
}
// Well-known file tabs — only at root
activeTab := ctx.FormString("tab")
if ctx.Repo.TreePath == "" {
wellKnownTabs := findWellKnownFiles(entries)
// Determine which tab is active
hasReadme := readmeFile != nil
if hasReadme || len(wellKnownTabs) > 0 {
// Only show tabs if there are at least 2 items (README + at least one well-known file)
if hasReadme && len(wellKnownTabs) > 0 {
readmeTab := WellKnownFileTab{
TabKey: "readme",
Label: "repo.well_known_file.readme",
FileName: "",
Active: activeTab == "" || activeTab == "readme",
}
if readmeFile != nil {
readmeTab.FileName = readmeFile.Name()
}
// Set active state for well-known tabs
for i := range wellKnownTabs {
wellKnownTabs[i].Active = wellKnownTabs[i].TabKey == activeTab
}
allTabs := append([]WellKnownFileTab{readmeTab}, wellKnownTabs...)
ctx.Data["WellKnownFileTabs"] = allTabs
}
}
// If a non-readme tab is selected, render that file instead
if activeTab != "" && activeTab != "readme" {
for _, tab := range wellKnownTabs {
if tab.TabKey == activeTab {
entry := findFileEntryByName(entries, tab.FileName)
if entry != nil {
prepareToRenderReadmeFile(ctx, "", entry)
return
}
}
}
// If the requested tab was not found, fall through to render readme
}
}
prepareToRenderReadmeFile(ctx, subfolder, readmeFile)
}
+57
View File
@@ -141,6 +141,63 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) {
return []string{lowerLangCode + ext, ext}
}
// WellKnownFileTab represents a tab for a well-known root file (LICENSE, CONTRIBUTING, etc.)
type WellKnownFileTab struct {
TabKey string // query parameter value, e.g. "license"
Label string // locale key suffix, e.g. "repo.well_known_file.license"
FileName string // actual file name found in repo, e.g. "LICENSE.md"
Active bool // whether this tab is currently selected
}
// wellKnownFilePatterns maps tab keys to the base file names to search for (case-insensitive).
// Order defines the tab display order.
var wellKnownFilePatterns = []struct {
TabKey string
BaseName string // matched case-insensitively against file names (without extension)
}{
{"license", "LICENSE"},
{"contributing", "CONTRIBUTING"},
{"code_of_conduct", "CODE_OF_CONDUCT"},
{"security", "SECURITY"},
{"changelog", "CHANGELOG"},
}
// findWellKnownFiles scans root directory entries for well-known markdown/text files.
func findWellKnownFiles(entries []*git.TreeEntry) []WellKnownFileTab {
var tabs []WellKnownFileTab
for _, pattern := range wellKnownFilePatterns {
for _, entry := range entries {
if entry.IsDir() || entry.IsSubModule() {
continue
}
name := entry.Name()
baseName := name
if idx := strings.LastIndex(name, "."); idx >= 0 {
baseName = name[:idx]
}
if strings.EqualFold(baseName, pattern.BaseName) {
tabs = append(tabs, WellKnownFileTab{
TabKey: pattern.TabKey,
Label: "repo.well_known_file." + pattern.TabKey,
FileName: name,
})
break // take the first match for this pattern
}
}
}
return tabs
}
// findFileEntryByName finds a tree entry by exact file name.
func findFileEntryByName(entries []*git.TreeEntry, fileName string) *git.TreeEntry {
for _, entry := range entries {
if entry.Name() == fileName {
return entry
}
}
return nil
}
func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
if readmeFile == nil {
return
+190
View File
@@ -77,6 +77,20 @@ type PageMeta struct {
UpdatedUnix timeutil.TimeStamp
}
// WikiTreeNode represents a node in the wiki folder tree for sidebar navigation.
type WikiTreeNode struct {
Name string
SubURL string
IsDir bool
Children []*WikiTreeNode
}
// WikiBreadcrumb represents a breadcrumb segment.
type WikiBreadcrumb struct {
Name string
SubURL string
}
// findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
entry, err := commit.GetTreeEntryByPath(target)
@@ -232,6 +246,43 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
isSideBar := pageName == "_Sidebar"
isFooter := pageName == "_Footer"
// Build breadcrumbs for the current path
breadcrumbs := buildWikiBreadcrumbs(pageName)
ctx.Data["WikiBreadcrumbs"] = breadcrumbs
// Build folder tree for sidebar navigation
wikiTree := buildWikiTree(commit)
ctx.Data["WikiTree"] = wikiTree
// Check if path is a directory first (before file lookup)
dirEntry, _ := commit.GetTreeEntryByPath(string(pageName))
if dirEntry != nil && dirEntry.IsDir() {
// Path is a directory - try index files or show folder listing
var entry *git.TreeEntry
foundIndex := false
for _, indexName := range []string{"README", "Home", "index"} {
indexPath := wiki_service.WebPath(string(pageName) + "/" + indexName)
idxEntry, _, idxNoEntry, _ := wikiEntryByName(ctx, commit, indexPath)
if !idxNoEntry && idxEntry != nil {
pageName = indexPath
entry = idxEntry
_, displayName = wiki_service.WebPathToUserTitle(pageName)
ctx.Data["PageURL"] = wiki_service.WebPathToURLPath(pageName)
ctx.Data["Title"] = displayName
foundIndex = true
break
}
}
if !foundIndex {
ctx.Data["IsWikiFolder"] = true
ctx.Data["WikiFolderPath"] = string(pageName)
folderEntries := listWikiFolderEntries(commit, string(pageName))
ctx.Data["WikiFolderEntries"] = folderEntries
return wikiGitRepo, nil
}
_ = entry // will be used below via pageName lookup
}
// lookup filename in wiki - get gitTree entry , real filename
entry, pageFilename, noEntry, isRaw := wikiEntryByName(ctx, commit, pageName)
if noEntry {
@@ -479,6 +530,14 @@ func Wiki(ctx *context.Context) {
if ctx.Written() {
return
}
// Folder listing - no entry but IsWikiFolder flag is set
if ctx.Data["IsWikiFolder"] != nil {
if wikiGitRepo != nil {
defer wikiGitRepo.Close()
}
ctx.HTML(http.StatusOK, tplWikiView)
return
}
if entry == nil {
ctx.Data["Title"] = ctx.Tr("repo.wiki")
ctx.HTML(http.StatusOK, tplWikiStart)
@@ -752,3 +811,134 @@ func DeleteWikiPagePost(ctx *context.Context) {
ctx.JSONRedirect(ctx.Repo.RepoLink + "/wiki/")
}
// buildWikiBreadcrumbs creates breadcrumb segments from a wiki path.
func buildWikiBreadcrumbs(pageName wiki_service.WebPath) []WikiBreadcrumb {
parts := strings.Split(string(pageName), "/")
crumbs := make([]WikiBreadcrumb, 0, len(parts))
for i, part := range parts {
if part == "" {
continue
}
subURL := strings.Join(parts[:i+1], "/")
crumbs = append(crumbs, WikiBreadcrumb{
Name: part,
SubURL: subURL,
})
}
return crumbs
}
// buildWikiTree builds a hierarchical folder tree from the wiki git repo.
func buildWikiTree(commit *git.Commit) []*WikiTreeNode {
if commit == nil {
return nil
}
entries, err := commit.ListEntries()
if err != nil {
return nil
}
root := make(map[string]*WikiTreeNode)
var topLevel []*WikiTreeNode
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() {
node := &WikiTreeNode{
Name: name,
SubURL: name,
IsDir: true,
}
// List children of this directory
subTree := entry.Tree()
if subTree != nil {
children, _ := subTree.ListEntries()
for _, child := range children {
childName := child.Name()
if child.IsDir() {
node.Children = append(node.Children, &WikiTreeNode{
Name: childName,
SubURL: name + "/" + childName,
IsDir: true,
})
} else if strings.HasSuffix(childName, ".md") {
wpChild, err := wiki_service.GitPathToWebPath(childName)
if err != nil {
continue
}
_, childDisplay := wiki_service.WebPathToUserTitle(wpChild)
if childDisplay == "_Sidebar" || childDisplay == "_Footer" {
continue
}
node.Children = append(node.Children, &WikiTreeNode{
Name: childDisplay,
SubURL: name + "/" + string(wpChild),
IsDir: false,
})
}
}
}
root[name] = node
topLevel = append(topLevel, node)
} else if strings.HasSuffix(name, ".md") {
wpName, err := wiki_service.GitPathToWebPath(name)
if err != nil {
continue
}
_, displayName := wiki_service.WebPathToUserTitle(wpName)
if displayName == "_Sidebar" || displayName == "_Footer" {
continue
}
node := &WikiTreeNode{
Name: displayName,
SubURL: string(wpName),
IsDir: false,
}
topLevel = append(topLevel, node)
}
}
return topLevel
}
// listWikiFolderEntries lists the pages and subfolders in a wiki directory.
func listWikiFolderEntries(commit *git.Commit, treePath string) []PageMeta {
if commit == nil {
return nil
}
tree, err := commit.SubTree(treePath)
if err != nil {
return nil
}
entries, err := tree.ListEntries()
if err != nil {
return nil
}
var pages []PageMeta
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() {
pages = append(pages, PageMeta{
Name: name + "/",
SubURL: treePath + "/" + name,
GitEntryName: name,
})
} else if strings.HasSuffix(name, ".md") {
wpName, err := wiki_service.GitPathToWebPath(name)
if err != nil {
continue
}
_, displayName := wiki_service.WebPathToUserTitle(wpName)
if displayName == "_Sidebar" || displayName == "_Footer" {
continue
}
pages = append(pages, PageMeta{
Name: displayName,
SubURL: treePath + "/" + string(wpName),
GitEntryName: name,
})
}
}
return pages
}
+43
View File
@@ -1061,6 +1061,30 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("", org.SettingsUpdateStreams)
m.Post("", org.SettingsUpdateStreamsPost)
})
m.Group("/custom-fields", func() {
m.Get("", org.SettingsCustomFields)
m.Post("", org.SettingsCustomFieldsCreatePost)
m.Post("/{id}/edit", org.SettingsCustomFieldsEditPost)
m.Post("/{id}/delete", org.SettingsCustomFieldsDeletePost)
})
m.Group("/issue-statuses", func() {
m.Get("", org.SettingsIssueStatuses)
m.Post("", org.SettingsIssueStatusesCreatePost)
m.Post("/{id}/edit", org.SettingsIssueStatusesEditPost)
m.Post("/{id}/delete", org.SettingsIssueStatusesDeletePost)
})
m.Group("/issue-priorities", func() {
m.Get("", org.SettingsIssuePriorities)
m.Post("", org.SettingsIssuePrioritiesCreatePost)
m.Post("/{id}/edit", org.SettingsIssuePrioritiesEditPost)
m.Post("/{id}/delete", org.SettingsIssuePrioritiesDeletePost)
})
m.Group("/issue-types", func() {
m.Get("", org.SettingsIssueTypes)
m.Post("", org.SettingsIssueTypesCreatePost)
m.Post("/{id}/edit", org.SettingsIssueTypesEditPost)
m.Post("/{id}/delete", org.SettingsIssueTypesDeletePost)
})
}, ctxDataSet("EnableOAuth2", setting.OAuth2.Enabled, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true))
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireOwner: true}))
}, reqSignIn)
@@ -1187,6 +1211,13 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Combo("/advanced").Get(repo_setting.AdvancedSettings).Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost)
}, repo_setting.SettingsCtxData)
m.Combo("/licensing").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost)
m.Combo("/manifest").Get(repo_setting.ManifestSettings).Post(repo_setting.ManifestSettingsPost)
m.Combo("/metadata").Get(repo_setting.Metadata).Post(repo_setting.MetadataPost)
m.Group("/security", func() {
m.Combo("").Get(repo_setting.SecuritySettings).Post(repo_setting.SecuritySettingsPost)
m.Post("/scan", repo_setting.SecurityScanNow)
m.Post("/alert/{id}", repo_setting.SecurityAlertUpdate)
})
m.Group("/collaboration", func() {
m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost)
@@ -1391,6 +1422,10 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Post("/projects/column", reqRepoIssuesOrPullsWriter, reqRepoProjectsWriter, repo.UpdateIssueProjectColumn)
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
m.Post("/{id}/custom-fields/{field_id}", reqRepoIssuesOrPullsWriter, repo.UpdateIssueCustomField)
m.Post("/{id}/custom-status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueCustomStatus)
m.Post("/{id}/custom-priority", reqRepoIssuesOrPullsWriter, repo.UpdateIssueCustomPriority)
m.Post("/{id}/custom-type", reqRepoIssuesOrPullsWriter, repo.UpdateIssueCustomType)
m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues)
m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin)
m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove)
@@ -1543,6 +1578,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Post("/packages/{id}/archive", repo.LicensesArchivePackage)
m.Post("/packages/{id}/unarchive", repo.LicensesUnarchivePackage)
m.Post("/keys/generate", repo.LicensesGenerateKey)
m.Post("/master-key/regenerate", repo.LicensesRegenerateMasterKey)
m.Get("/keys/{id}/edit", repo.LicensesEditKey)
m.Post("/keys/{id}/edit", repo.LicensesEditKeyPost)
m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey)
@@ -1648,6 +1684,13 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
})
// end "/{username}/{reponame}/wiki"
m.Group("/{username}/{reponame}/security", func() {
m.Get("", repo.Security)
m.Post("/scan", reqRepoAdmin, repo.SecurityScanNow)
m.Post("/alert/{id}", reqRepoAdmin, repo.SecurityAlertUpdateTab)
}, reqSignIn, context.RepoAssignment, reqRepoAdmin)
// end "/{username}/{reponame}/security"
m.Group("/{username}/{reponame}/activity", func() {
// activity has its own permission checks
m.Get("", repo.Activity)
+26
View File
@@ -16,8 +16,11 @@ import (
"syscall"
"time"
auth_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/auth"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/httplib"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/optional"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/auth/source/oauth2"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
@@ -166,6 +169,18 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
ctx.Data["Title"] = "Page Not Found"
ctx.Data["ErrorMsg"] = "" // FIXME: the template never renders this message, need to fix in the future (and show safe messages to end users)
ctx.Data["CurrentURL"] = ctx.Req.URL.RequestURI()
// Load OAuth2 providers for the login form on error pages
if !ctx.IsSigned {
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
log.Error("NotFound: GetOAuth2Providers: %v", err)
}
ctx.Data["OAuth2Providers"] = oauth2Providers
ctx.Data["EnableSSPI"] = auth_model.IsSSPIEnabled(ctx)
}
ctx.HTML(http.StatusNotFound, "status/404")
}
@@ -187,6 +202,17 @@ func (ctx *Context) Forbidden() {
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
ctx.Data["Title"] = "Access Denied"
ctx.Data["CurrentURL"] = ctx.Req.URL.RequestURI()
// Load OAuth2 providers for the login form on the 403 page
if !ctx.IsSigned {
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true))
if err != nil {
log.Error("Forbidden: GetOAuth2Providers: %v", err)
}
ctx.Data["OAuth2Providers"] = oauth2Providers
ctx.Data["EnableSSPI"] = auth_model.IsSSPIEnabled(ctx)
}
ctx.HTML(http.StatusForbidden, "status/403")
}
+2
View File
@@ -663,6 +663,8 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
ctx.Data["NumLicensePackages"] = numLicensePackages
ctx.Data["EnableLicenses"] = licensingEnabled || numLicensePackages > 0
ctx.Data["LicensingEnabled"] = licensingEnabled
downloadGated := repoUpdateCfg != nil && repoUpdateCfg.DownloadGating != "" && repoUpdateCfg.DownloadGating != "none"
ctx.Data["DownloadGated"] = downloadGated
// Determine release page access based on feed visibility mode.
feedVis := "public"
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repository
import (
"context"
"encoding/xml"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
)
// manifestXML mirrors the .mokogitea/manifest.xml schema for XML parsing.
type manifestXML struct {
XMLName xml.Name `xml:"moko-platform"`
Identity manifestIdentity `xml:"identity"`
Governance manifestGovernance `xml:"governance"`
Build manifestBuild `xml:"build"`
}
type manifestIdentity struct {
Name string `xml:"name"`
Org string `xml:"org"`
Description string `xml:"description"`
Version string `xml:"version"`
License manifestLicense `xml:"license"`
}
type manifestLicense struct {
SPDX string `xml:"spdx,attr"`
Name string `xml:",chardata"`
}
type manifestGovernance struct {
Platform string `xml:"platform"`
StandardsVersion string `xml:"standards-version"`
StandardsSource string `xml:"standards-source"`
}
type manifestBuild struct {
Language string `xml:"language"`
PackageType string `xml:"package-type"`
EntryPoint string `xml:"entry-point"`
}
// SyncManifestFromCommit reads .mokogitea/manifest.xml from the given commit
// and upserts the values into the repo_manifest database table.
// This is called on push to the default branch to keep the database in sync
// with the XML file. If no manifest.xml exists, this is a no-op.
func SyncManifestFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) {
if commit == nil {
return
}
entry, err := commit.GetTreeEntryByPath(".mokogitea/manifest.xml")
if err != nil || entry == nil {
return // no manifest.xml — not an error
}
reader, err := entry.Blob().DataAsync()
if err != nil {
log.Error("SyncManifest: read blob for %s: %v", repo.FullName(), err)
return
}
defer reader.Close()
var mxml manifestXML
decoder := xml.NewDecoder(reader)
if err := decoder.Decode(&mxml); err != nil {
log.Error("SyncManifest: parse XML for %s: %v", repo.FullName(), err)
return
}
manifest := &repo_model.RepoManifest{
RepoID: repo.ID,
Name: mxml.Identity.Name,
Org: mxml.Identity.Org,
Description: mxml.Identity.Description,
Version: mxml.Identity.Version,
LicenseSPDX: mxml.Identity.License.SPDX,
LicenseName: mxml.Identity.License.Name,
Platform: mxml.Governance.Platform,
StandardsVersion: mxml.Governance.StandardsVersion,
StandardsSource: mxml.Governance.StandardsSource,
Language: mxml.Build.Language,
PackageType: mxml.Build.PackageType,
EntryPoint: mxml.Build.EntryPoint,
}
if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
log.Error("SyncManifest: save for %s: %v", repo.FullName(), err)
return
}
log.Info("SyncManifest: synced .mokogitea/manifest.xml for %s", repo.FullName())
}
+5
View File
@@ -27,6 +27,7 @@ import (
issue_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/issue"
notify_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/notify"
pull_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/pull"
security_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/security"
)
// pushQueue represents a queue to handle update pull request tests
@@ -193,6 +194,10 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
if err := DelRepoDivergenceFromCache(ctx, repo.ID); err != nil {
log.Error("DelRepoDivergenceFromCache: %v", err)
}
// Auto-sync .mokogitea/manifest.xml to database on default branch push
SyncManifestFromCommit(ctx, repo, newCommit)
// Run security scanners on default branch push
security_service.ScanOnPush(ctx, repo, newCommit)
} else {
if err := DelDivergenceFromCache(repo.ID, branch); err != nil {
log.Error("DelDivergenceFromCache: %v", err)
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package security
import (
"context"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
security_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/security"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
)
// ScanOnPush runs enabled scanners against a commit pushed to the default branch.
// Called from services/repository/push.go on default branch pushes.
func ScanOnPush(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) {
if commit == nil {
return
}
cfg, err := security_model.GetScannerConfig(ctx, repo.ID)
if err != nil {
log.Error("SecurityScan: GetScannerConfig for %s: %v", repo.FullName(), err)
return
}
if !cfg.Enabled {
return
}
var scanners []Scanner
if cfg.SecretScanner {
scanners = append(scanners, NewSecretScanner())
}
// Future scanners added here:
// if cfg.DependScanner { scanners = append(scanners, NewDependencyScanner()) }
// if cfg.CodeScanner { scanners = append(scanners, NewCodeScanner()) }
if len(scanners) == 0 {
return
}
totalFindings := 0
for _, s := range scanners {
findings, err := s.ScanTree(commit)
if err != nil {
log.Error("SecurityScan: %s scanner for %s: %v", s.Type(), repo.FullName(), err)
continue
}
for _, f := range findings {
alert := &security_model.SecurityAlert{
RepoID: repo.ID,
Scanner: f.Scanner,
Severity: f.Severity,
RuleID: f.RuleID,
Title: f.Title,
Description: f.Description,
FilePath: f.FilePath,
LineNumber: f.LineNumber,
CommitSHA: f.CommitSHA,
Fingerprint: f.Fingerprint,
Metadata: f.Metadata,
}
if err := security_model.CreateOrUpdateAlert(ctx, alert); err != nil {
log.Error("SecurityScan: CreateOrUpdateAlert: %v", err)
}
totalFindings++
}
}
if totalFindings > 0 {
log.Warn("SecurityScan: %d findings in %s", totalFindings, repo.FullName())
}
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package security
import (
security_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/security"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
)
// Finding represents a single security issue found by a scanner.
type Finding struct {
Scanner security_model.ScannerType
Severity security_model.AlertSeverity
RuleID string
Title string
Description string
FilePath string
LineNumber int
CommitSHA string
Fingerprint string // unique identifier for dedup
Metadata string // JSON extra data
}
// Scanner is the interface all security scanner modules implement.
type Scanner interface {
// Type returns the scanner type identifier.
Type() security_model.ScannerType
// ScanCommit scans a single commit and returns findings.
ScanCommit(commit *git.Commit) ([]Finding, error)
// ScanTree scans the full repository tree and returns findings.
ScanTree(commit *git.Commit) ([]Finding, error)
}
+203
View File
@@ -0,0 +1,203 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package security
import (
"bufio"
"crypto/sha256"
"fmt"
"io"
"regexp"
"strings"
security_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/security"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
)
// SecretRule defines a pattern to match against file contents.
type SecretRule struct {
ID string
Title string
Pattern *regexp.Regexp
Severity security_model.AlertSeverity
Description string
}
// DefaultSecretRules contains the built-in secret detection patterns.
var DefaultSecretRules = []SecretRule{
// AWS
{ID: "aws-access-key", Title: "AWS Access Key ID", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`AKIA[0-9A-Z]{16}`), Description: "AWS access key ID detected"},
{ID: "aws-secret-key", Title: "AWS Secret Access Key", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`(?i)aws_secret_access_key\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}`), Description: "AWS secret access key detected"},
// Generic tokens/keys
{ID: "private-key", Title: "Private Key", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`-----BEGIN (RSA|EC|OPENSSH|DSA|PGP) PRIVATE KEY-----`), Description: "Private key file detected"},
{ID: "generic-api-key", Title: "Generic API Key", Severity: security_model.SeverityHigh,
Pattern: regexp.MustCompile(`(?i)(api[_-]?key|apikey)\s*[=:]\s*['"]?[A-Za-z0-9_\-]{20,}`), Description: "API key assignment detected"},
{ID: "generic-secret", Title: "Generic Secret", Severity: security_model.SeverityHigh,
Pattern: regexp.MustCompile(`(?i)(secret|password|passwd|pwd)\s*[=:]\s*['"][^'"]{8,}['"]`), Description: "Hardcoded secret or password detected"},
{ID: "generic-token", Title: "Generic Token", Severity: security_model.SeverityHigh,
Pattern: regexp.MustCompile(`(?i)(token|auth_token|access_token)\s*[=:]\s*['"]?[A-Za-z0-9_\-.]{20,}`), Description: "Token assignment detected"},
// GitHub/Gitea
{ID: "github-pat", Title: "GitHub Personal Access Token", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`ghp_[A-Za-z0-9]{36}`), Description: "GitHub personal access token detected"},
{ID: "github-oauth", Title: "GitHub OAuth Token", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`gho_[A-Za-z0-9]{36}`), Description: "GitHub OAuth token detected"},
// Stripe
{ID: "stripe-secret", Title: "Stripe Secret Key", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`sk_live_[A-Za-z0-9]{24,}`), Description: "Stripe live secret key detected"},
{ID: "stripe-publishable", Title: "Stripe Publishable Key", Severity: security_model.SeverityLow,
Pattern: regexp.MustCompile(`pk_live_[A-Za-z0-9]{24,}`), Description: "Stripe live publishable key detected (usually safe but flagged)"},
// JWT
{ID: "jwt-token", Title: "JWT Token", Severity: security_model.SeverityMedium,
Pattern: regexp.MustCompile(`eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}`), Description: "JWT token detected"},
// Connection strings
{ID: "connection-string", Title: "Connection String with Password", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`(?i)(mysql|postgres|postgresql|mongodb|redis|amqp|smtp)://[^:]+:[^@]+@[^\s]+`), Description: "Database/service connection string with embedded password"},
// Google
{ID: "google-api-key", Title: "Google API Key", Severity: security_model.SeverityHigh,
Pattern: regexp.MustCompile(`AIza[0-9A-Za-z_-]{35}`), Description: "Google API key detected"},
// Slack
{ID: "slack-webhook", Title: "Slack Webhook URL", Severity: security_model.SeverityMedium,
Pattern: regexp.MustCompile(`https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+`), Description: "Slack webhook URL detected"},
// SendGrid
{ID: "sendgrid-api-key", Title: "SendGrid API Key", Severity: security_model.SeverityHigh,
Pattern: regexp.MustCompile(`SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}`), Description: "SendGrid API key detected"},
// PayPal
{ID: "paypal-client-secret", Title: "PayPal Client Secret", Severity: security_model.SeverityCritical,
Pattern: regexp.MustCompile(`(?i)paypal.*secret\s*[=:]\s*['"]?[A-Za-z0-9_-]{20,}`), Description: "PayPal client secret detected"},
}
// Files to skip during scanning.
var skipExtensions = map[string]bool{
".png": true, ".jpg": true, ".jpeg": true, ".gif": true, ".ico": true,
".svg": true, ".woff": true, ".woff2": true, ".ttf": true, ".eot": true,
".zip": true, ".tar": true, ".gz": true, ".bz2": true, ".7z": true,
".pdf": true, ".doc": true, ".docx": true, ".xls": true, ".xlsx": true,
".exe": true, ".dll": true, ".so": true, ".dylib": true, ".o": true,
".min.js": true, ".min.css": true,
}
var skipPaths = []string{
"vendor/", "node_modules/", ".git/", "dist/", "build/",
"go.sum", "package-lock.json", "composer.lock", "yarn.lock",
}
// SecretScanner implements the Scanner interface for secret detection.
type SecretScanner struct {
Rules []SecretRule
}
// NewSecretScanner creates a scanner with default rules.
func NewSecretScanner() *SecretScanner {
return &SecretScanner{Rules: DefaultSecretRules}
}
func (s *SecretScanner) Type() security_model.ScannerType {
return security_model.ScannerSecret
}
func (s *SecretScanner) ScanCommit(commit *git.Commit) ([]Finding, error) {
// For push-time scanning, we scan the diff of the commit
return s.ScanTree(commit)
}
func (s *SecretScanner) ScanTree(commit *git.Commit) ([]Finding, error) {
if commit == nil {
return nil, nil
}
entries, err := commit.ListEntriesRecursiveFast()
if err != nil {
return nil, fmt.Errorf("ListEntriesRecursiveFast: %w", err)
}
var findings []Finding
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
path := entry.Name()
if shouldSkipFile(path) {
continue
}
// Skip large files (> 1MB)
if entry.Blob().Size() > 1024*1024 {
continue
}
reader, err := entry.Blob().DataAsync()
if err != nil {
log.Trace("SecretScanner: skip %s: %v", path, err)
continue
}
fileFindings := s.scanReader(reader, path, commit.ID.String())
reader.Close()
findings = append(findings, fileFindings...)
}
return findings, nil
}
func (s *SecretScanner) scanReader(r io.Reader, filePath, commitSHA string) []Finding {
var findings []Finding
scanner := bufio.NewScanner(r)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Text()
for _, rule := range s.Rules {
if rule.Pattern.MatchString(line) {
fingerprint := fmt.Sprintf("%x", sha256.Sum256([]byte(rule.ID+":"+filePath+":"+line)))
findings = append(findings, Finding{
Scanner: security_model.ScannerSecret,
Severity: rule.Severity,
RuleID: rule.ID,
Title: rule.Title,
Description: rule.Description,
FilePath: filePath,
LineNumber: lineNum,
CommitSHA: commitSHA,
Fingerprint: fingerprint[:32],
})
break // one finding per line per file
}
}
}
return findings
}
func shouldSkipFile(path string) bool {
lower := strings.ToLower(path)
for _, skip := range skipPaths {
if strings.HasPrefix(lower, skip) || strings.Contains(lower, "/"+skip) {
return true
}
}
for ext := range skipExtensions {
if strings.HasSuffix(lower, ext) {
return true
}
}
return false
}
+7 -10
View File
@@ -68,13 +68,15 @@ func GenerateComposerJSON(ctx context.Context, repo *repo_model.Repository, lice
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
meta := resolveExtensionMetadata(ctx, repo, cfg)
// Composer package name: vendor/package
// Composer package name: vendor/package (override with resolved extension name if set)
packageName := fmt.Sprintf("%s/%s", strings.ToLower(repo.Owner.Name), strings.ToLower(repo.Name))
if cfg != nil && cfg.ExtensionName != "" {
packageName = cfg.ExtensionName
if meta.Element != strings.ToLower(repo.Name) {
packageName = meta.Element
}
description := meta.Description
maintainer := repo.Owner.Name
maintainerURL := fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
if cfg != nil && cfg.Maintainer != "" {
@@ -84,14 +86,9 @@ func GenerateComposerJSON(ctx context.Context, repo *repo_model.Repository, lice
maintainerURL = cfg.MaintainerURL
}
description := ""
if cfg != nil && cfg.Description != "" {
description = cfg.Description
}
phpMin := ""
if cfg != nil && cfg.PHPMinimum != "" {
phpMin = ">=" + cfg.PHPMinimum
if meta.PHPMinimum != "" {
phpMin = ">=" + meta.PHPMinimum
}
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
+3 -10
View File
@@ -66,16 +66,9 @@ func GenerateDrupalXML(ctx context.Context, repo *repo_model.Repository, allowed
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
shortName := strings.ToLower(repo.Name)
title := repo.Name
if cfg != nil {
if cfg.ExtensionName != "" {
shortName = cfg.ExtensionName
}
if cfg.DisplayName != "" {
title = cfg.DisplayName
}
}
meta := resolveExtensionMetadata(ctx, repo, cfg)
shortName := meta.Element
title := meta.DisplayName
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
+161 -43
View File
@@ -13,8 +13,10 @@ import (
"time"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
)
@@ -37,7 +39,7 @@ type xmlUpdate struct {
TargetPlatform xmlTargetPlat `xml:"targetplatform"`
SHA256 string `xml:"sha256,omitempty"`
SHA512 string `xml:"sha512,omitempty"`
Client string `xml:"client"`
Client string `xml:"client,omitempty"`
PHPMinimum string `xml:"php_minimum,omitempty"`
Description string `xml:"description,omitempty"`
CreationDate string `xml:"creationDate,omitempty"`
@@ -120,7 +122,10 @@ func isStreamName(s string, streams []licenses.StreamDef) bool {
}
// joomlaTagName maps internal stream names to Joomla-standard tag values.
// Joomla recognizes: dev, alpha, beta, rc, stable.
// Joomla's Update.php maps tags via STABILITY_ + strtoupper(tag) constants.
// Valid values: dev (0), alpha (1), beta (2), rc (3), stable (4).
// Using full names like "development" or "release-candidate" would silently
// fall back to STABILITY_STABLE, breaking pre-release channel filtering.
func joomlaTagName(channel string) string {
switch channel {
case ChannelDevelopment:
@@ -157,9 +162,121 @@ func NormalizeChannel(ch string) string {
}
}
// extensionMetadata holds resolved metadata for feed generation.
// Fields are resolved with priority: custom field → config table → default.
type extensionMetadata struct {
Element string
DisplayName string
ExtType string
TargetVersion string
PHPMinimum string
Description string
SupportURL string
DownloadGating string
KeyPrefix string
}
// resolveExtensionMetadata loads extension metadata with cascading fallback:
// org-level repo-scoped custom fields → update_stream_config → repo-derived defaults.
func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository, cfg *licenses.UpdateStreamConfig) extensionMetadata {
m := extensionMetadata{
Element: strings.ToLower(repo.Name),
DisplayName: fmt.Sprintf("%s - %s", repo.Owner.Name, repo.Name),
ExtType: "component",
TargetVersion: "(5|6)\\..*",
}
// Apply config table values.
if cfg != nil {
if cfg.ExtensionName != "" {
m.Element = cfg.ExtensionName
}
if cfg.DisplayName != "" {
m.DisplayName = cfg.DisplayName
}
if cfg.ExtensionType != "" {
m.ExtType = cfg.ExtensionType
}
if cfg.TargetVersion != "" {
m.TargetVersion = cfg.TargetVersion
}
if cfg.PHPMinimum != "" {
m.PHPMinimum = cfg.PHPMinimum
}
if cfg.Description != "" {
m.Description = cfg.Description
}
if cfg.SupportURL != "" {
m.SupportURL = cfg.SupportURL
}
if cfg.DownloadGating != "" {
m.DownloadGating = cfg.DownloadGating
}
if cfg.KeyPrefix != "" {
m.KeyPrefix = cfg.KeyPrefix
}
}
// Override with custom field values (highest priority).
fields, err := issues_model.GetCustomFieldsByOwner(ctx, repo.OwnerID, issues_model.CustomFieldScopeRepo)
if err != nil {
log.Error("resolveExtensionMetadata: GetCustomFieldsByOwner for repo %d: %v", repo.ID, err)
return m
}
if len(fields) == 0 {
return m
}
values, err := issues_model.GetCustomFieldValuesMap(ctx, repo.ID)
if err != nil {
log.Error("resolveExtensionMetadata: GetCustomFieldValuesMap for repo %d: %v", repo.ID, err)
return m
}
if len(values) == 0 {
return m
}
// Build name → value map from field definitions + values.
named := make(map[string]string, len(fields))
for _, f := range fields {
if v, ok := values[f.ID]; ok && v != "" {
named[f.Name] = v
}
}
if v := named["Extension Name"]; v != "" {
m.Element = v
}
if v := named["Display Name"]; v != "" {
m.DisplayName = v
}
if v := named["Extension Type"]; v != "" {
m.ExtType = v
}
if v := named["Target Version"]; v != "" {
m.TargetVersion = v
}
if v := named["PHP Minimum"]; v != "" {
m.PHPMinimum = v
}
if v := named["Support URL"]; v != "" {
m.SupportURL = v
}
if v := named["Description"]; v != "" {
m.Description = v
}
if v := named["Download Gating"]; v != "" {
m.DownloadGating = v
}
if v := named["Key Prefix"]; v != "" {
m.KeyPrefix = v
}
return m
}
// GenerateJoomlaXML builds a Joomla-compatible updates.xml from repository releases.
// It returns the raw XML bytes. Extension metadata is read from the update stream config;
// falls back to repo name/owner when not configured.
// It returns the raw XML bytes. Extension metadata is resolved from custom fields first,
// then the update stream config, then repo-derived defaults.
// allowedChannels optionally restricts output to specific channels (nil = all).
func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, requireKey, stripDownloads bool, allowedChannels ...string) ([]byte, error) {
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
@@ -182,40 +299,26 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
}
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
// Load extension metadata from config (falls back to repo-derived values).
// Load extension metadata with cascading fallback:
// custom fields → config table → repo-derived defaults.
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
meta := resolveExtensionMetadata(ctx, repo, cfg)
element := strings.ToLower(repo.Name)
if cfg != nil && cfg.ExtensionName != "" {
element = cfg.ExtensionName
element := meta.Element
displayName := meta.DisplayName
extType := meta.ExtType
targetVersion := meta.TargetVersion
phpMinimum := meta.PHPMinimum
feedDescription := meta.Description
// Maintainer and URL always come from the org profile.
maintainer := repo.Owner.FullName
if maintainer == "" {
maintainer = repo.Owner.Name
}
displayName := fmt.Sprintf("%s - %s", repo.Owner.Name, repo.Name)
if cfg != nil && cfg.DisplayName != "" {
displayName = cfg.DisplayName
}
extType := "component"
if cfg != nil && cfg.ExtensionType != "" {
extType = cfg.ExtensionType
}
maintainer := repo.Owner.Name
if cfg != nil && cfg.Maintainer != "" {
maintainer = cfg.Maintainer
}
maintainerURL := fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
if cfg != nil && cfg.MaintainerURL != "" {
maintainerURL = cfg.MaintainerURL
}
targetVersion := ".*"
if cfg != nil && cfg.TargetVersion != "" {
targetVersion = cfg.TargetVersion
}
phpMinimum := ""
if cfg != nil && cfg.PHPMinimum != "" {
phpMinimum = cfg.PHPMinimum
}
feedDescription := ""
if cfg != nil && cfg.Description != "" {
feedDescription = cfg.Description
maintainerURL := repo.Owner.Website
if maintainerURL == "" {
maintainerURL = fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
}
// Resolve effective streams (repo override → org default → Joomla default).
@@ -284,9 +387,16 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
downloadURL = fmt.Sprintf("%s/archive/%s.zip", repoLink, rel.TagName)
}
version := extractVersion(rel.TagName)
// If the tag is a stream name (not a version), try the release title instead.
if version == "" || isStreamName(rel.TagName, streams) {
// Extract version: prefer asset filename (matches actual download),
// then tag name, then release title. Only fall through when empty.
version := ""
if zipName != "" {
version = extractVersion(zipName)
}
if version == "" {
version = extractVersion(rel.TagName)
}
if version == "" {
version = extractVersion(rel.Title)
}
// Last resort: use the tag name as-is.
@@ -306,11 +416,19 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
desc = fmt.Sprintf("%s %s build.", displayName, ch)
}
// Info URL: use support_url (product page), fall back to releases page.
infoURL := fmt.Sprintf("%s/releases", repoLink)
if cfg != nil && cfg.SupportURL != "" {
infoURL = cfg.SupportURL
} else if cfg != nil && cfg.InfoURL != "" {
infoURL = cfg.InfoURL
if meta.SupportURL != "" {
infoURL = meta.SupportURL
}
// Joomla <client> element: packages use client_id=0 in #__extensions,
// so we must output <client>0</client> for Joomla to match the update
// to the installed extension. Other types default to "site" (client_id=0)
// or "administrator" (client_id=1).
client := "site"
if extType == "package" {
client = "0"
}
u := xmlUpdate{
@@ -318,7 +436,7 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
Description: desc,
Element: element,
Type: extType,
Client: "site",
Client: client,
Version: version,
CreationDate: time.Unix(int64(rel.CreatedUnix), 0).Format("2006-01-02"),
InfoURL: xmlInfoURL{
+6 -16
View File
@@ -55,23 +55,13 @@ func GeneratePrestaShopXML(ctx context.Context, repo *repo_model.Repository, all
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
moduleName := strings.ToLower(repo.Name)
displayName := repo.Name
meta := resolveExtensionMetadata(ctx, repo, cfg)
moduleName := meta.Element
displayName := meta.DisplayName
description := meta.Description
maintainer := repo.Owner.Name
description := ""
if cfg != nil {
if cfg.ExtensionName != "" {
moduleName = cfg.ExtensionName
}
if cfg.DisplayName != "" {
displayName = cfg.DisplayName
}
if cfg.Maintainer != "" {
maintainer = cfg.Maintainer
}
if cfg.Description != "" {
description = cfg.Description
}
if cfg != nil && cfg.Maintainer != "" {
maintainer = cfg.Maintainer
}
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
+3 -8
View File
@@ -50,23 +50,18 @@ func GenerateWHMCSJSON(ctx context.Context, repo *repo_model.Repository, license
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
displayName := repo.Name
meta := resolveExtensionMetadata(ctx, repo, cfg)
displayName := meta.DisplayName
description := meta.Description
maintainer := repo.Owner.Name
maintainerURL := fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
description := ""
if cfg != nil {
if cfg.DisplayName != "" {
displayName = cfg.DisplayName
}
if cfg.Maintainer != "" {
maintainer = cfg.Maintainer
}
if cfg.MaintainerURL != "" {
maintainerURL = cfg.MaintainerURL
}
if cfg.Description != "" {
description = cfg.Description
}
}
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
+11 -17
View File
@@ -57,36 +57,30 @@ func GenerateWordPressJSON(ctx context.Context, repo *repo_model.Repository, lic
baseURL := strings.TrimSuffix(setting.AppURL, "/")
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
// Load extension metadata.
// Load extension metadata with cascading fallback:
// custom fields → config table → repo-derived defaults.
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
meta := resolveExtensionMetadata(ctx, repo, cfg)
slug := strings.ToLower(repo.Name)
displayName := fmt.Sprintf("%s - %s", repo.Owner.Name, repo.Name)
slug := meta.Element
displayName := meta.DisplayName
requiresPHP := meta.PHPMinimum
homepage := repoLink
if meta.SupportURL != "" {
homepage = meta.SupportURL
}
maintainer := repo.Owner.Name
maintainerURL := fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name)
homepage := repoLink
requiresPHP := ""
if cfg != nil {
if cfg.ExtensionName != "" {
slug = cfg.ExtensionName
}
if cfg.DisplayName != "" {
displayName = cfg.DisplayName
}
if cfg.Maintainer != "" {
maintainer = cfg.Maintainer
}
if cfg.MaintainerURL != "" {
maintainerURL = cfg.MaintainerURL
}
if cfg.SupportURL != "" {
homepage = cfg.SupportURL
} else if cfg.InfoURL != "" {
if homepage == repoLink && cfg.InfoURL != "" {
homepage = cfg.InfoURL
}
if cfg.PHPMinimum != "" {
requiresPHP = cfg.PHPMinimum
}
}
// Resolve streams and find the latest stable release.

Some files were not shown because too many files have changed in this diff Show More