feat: Issue Types settings + MCP SSE + npm auto-publish #557

Merged
jmiller merged 1 commits from feat/type-settings-mcp-sse-npm into dev 2026-06-07 00:53:51 +00:00
6 changed files with 251 additions and 0 deletions
+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 }}
+12
View File
@@ -2992,6 +2992,18 @@
"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.",
+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")
}
+6
View File
@@ -1079,6 +1079,12 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
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)
+81
View File
@@ -0,0 +1,81 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings issue-types")}}
<h4 class="ui top attached header">
{{ctx.Locale.Tr "org.settings.issue_types"}}
</h4>
<div class="ui attached segment">
<p class="text grey">{{ctx.Locale.Tr "org.settings.issue_types_desc"}}</p>
{{if .IssueTypes}}
<table class="ui compact table">
<thead>
<tr>
<th>{{ctx.Locale.Tr "org.settings.issue_type_color"}}</th>
<th>{{ctx.Locale.Tr "org.settings.issue_type_name"}}</th>
<th>{{ctx.Locale.Tr "org.settings.issue_type_default"}}</th>
<th>{{ctx.Locale.Tr "org.settings.issue_type_sort_order"}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{range .IssueTypes}}
<tr {{if not .IsActive}}class="tw-opacity-50"{{end}}>
<td>
{{if .Color}}<span class="tw-inline-block tw-w-4 tw-h-4 tw-rounded" style="background-color: {{.Color}}"></span>{{else}}<span class="text grey">-</span>{{end}}
</td>
<td>
<strong>{{.Name}}</strong>
{{if not .IsActive}}<span class="ui mini grey label">Inactive</span>{{end}}
{{if .Description}}<br><small class="text grey">{{.Description}}</small>{{end}}
</td>
<td>
{{if .IsDefault}}<span class="ui mini blue label">{{ctx.Locale.Tr "org.settings.issue_type_default"}}</span>{{else}}<span class="text grey">-</span>{{end}}
</td>
<td>{{.SortOrder}}</td>
<td class="tw-text-right">
<form method="post" action="{{$.OrgLink}}/settings/issue-types/{{.ID}}/delete" class="tw-inline">
{{$.CsrfTokenHtml}}
<button class="ui tiny red icon button" type="submit" title="{{ctx.Locale.Tr "remove"}}">{{svg "octicon-trash" 14}}</button>
</form>
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<div class="empty-placeholder"><p>{{ctx.Locale.Tr "org.settings.issue_types_empty"}}</p></div>
{{end}}
<div class="divider"></div>
<h5>{{ctx.Locale.Tr "org.settings.issue_type_add"}}</h5>
<form class="ui form" method="post" action="{{.OrgLink}}/settings/issue-types">
{{.CsrfTokenHtml}}
<div class="three fields">
<div class="required field">
<label>{{ctx.Locale.Tr "org.settings.issue_type_name"}}</label>
<input name="name" required placeholder="e.g. Bug, Feature, Task">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "org.settings.issue_type_color"}}</label>
<input name="color" type="color" value="#2563eb">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "org.settings.issue_type_sort_order"}}</label>
<input name="sort_order" type="number" value="0" min="0">
</div>
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "org.settings.issue_type_description"}}</label>
<input name="description" placeholder="Help text">
</div>
<div class="field">
<div class="ui checkbox tw-mt-4">
<input name="is_default" type="checkbox">
<label>{{ctx.Locale.Tr "org.settings.issue_type_default"}}</label>
</div>
</div>
</div>
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "org.settings.issue_type_add"}}</button>
</form>
</div>
{{template "org/settings/layout_footer" .}}
+3
View File
@@ -37,6 +37,9 @@
<a class="{{if .PageIsSettingsIssuePriorities}}active {{end}}item" href="{{.OrgLink}}/settings/issue-priorities">
{{svg "octicon-flame"}} {{ctx.Locale.Tr "org.settings.issue_priorities"}}
</a>
<a class="{{if .PageIsSettingsIssueTypes}}active {{end}}item" href="{{.OrgLink}}/settings/issue-types">
{{svg "octicon-tag"}} {{ctx.Locale.Tr "org.settings.issue_types"}}
</a>
{{if .EnableActions}}
<details class="item toggleable-item" {{if or .PageIsOrgSettingsActionsGeneral .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables}}open{{end}}>
<summary>{{svg "octicon-play"}} {{ctx.Locale.Tr "actions.actions"}}</summary>