diff --git a/models/repo/repo_manifest.go b/models/repo/repo_manifest.go index f3163d2440..cddc21e673 100644 --- a/models/repo/repo_manifest.go +++ b/models/repo/repo_manifest.go @@ -12,13 +12,13 @@ import ( ) func init() { - db.RegisterModel(new(RepoManifest)) + db.RegisterModel(new(RepoMetadata)) } -// RepoManifest stores mokoplatform manifest settings for a repository. +// RepoMetadata stores mokoplatform metadata 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 mokoplatform CLI. -type RepoManifest struct { +type RepoMetadata struct { ID int64 `xorm:"pk autoincr"` RepoID int64 `xorm:"UNIQUE INDEX NOT NULL 'repo_id'"` @@ -55,13 +55,13 @@ type RepoManifest struct { UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED 'updated_unix'"` } -func (RepoManifest) TableName() string { +func (RepoMetadata) TableName() string { return "repo_manifest" } // joomlaTypePrefix maps Joomla extension types to their element name prefixes. -// Plugins have no prefix in Joomla's #__extensions table — the element is the -// lowercased, hyphen-free name, and the folder column determines the plugin group. +// Derived from Joomla 6 source: libraries/src/Installer/Adapter/*Adapter.php +// Plugins use bare names (no prefix) — the folder column determines the plugin group. var joomlaTypePrefix = map[string]string{ "component": "com_", "module": "mod_", @@ -71,22 +71,35 @@ var joomlaTypePrefix = map[string]string{ "file": "file_", } -// AutoElementName returns the auto-constructed Joomla element name (e.g. pkg_mokowaas). -// The name is lowercased and hyphens are removed to produce clean element names -// for the #__extensions.element column (e.g. "MokoSuiteBackup" → "pkg_mokosuitebackup"). -func (m *RepoManifest) AutoElementName() string { +// cleanJoomlaElement replicates Joomla's InputFilter::clean($value, 'cmd') +// which lowercases and strips all characters except [a-z0-9._-]. +func cleanJoomlaElement(name string) string { + lower := strings.ToLower(name) + var b strings.Builder + b.Grow(len(lower)) + for _, r := range lower { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '.' || r == '_' || r == '-' { + b.WriteRune(r) + } + } + return b.String() +} + +// AutoElementName returns the auto-constructed Joomla element name (e.g. pkg_mokosuitebackup). +// The name is lowercased and cleaned to match Joomla's InputFilter::clean('cmd') behavior. +func (m *RepoMetadata) AutoElementName() string { if m.Name == "" || m.PackageType == "" { return "" } - lower := strings.ToLower(strings.ReplaceAll(m.Name, "-", "")) + cleaned := cleanJoomlaElement(m.Name) if prefix, ok := joomlaTypePrefix[m.PackageType]; ok { - return prefix + lower + return prefix + cleaned } - return lower + return cleaned } // FullElementName returns the effective element name: override if set, otherwise auto-constructed. -func (m *RepoManifest) FullElementName() string { +func (m *RepoMetadata) FullElementName() string { if m.ElementName != "" { return m.ElementName } @@ -94,7 +107,7 @@ func (m *RepoManifest) FullElementName() string { } // ElementNameMismatch returns true if an override is set that differs from the auto-constructed name. -func (m *RepoManifest) ElementNameMismatch() bool { +func (m *RepoMetadata) ElementNameMismatch() bool { if m.ElementName == "" { return false } @@ -102,9 +115,9 @@ func (m *RepoManifest) ElementNameMismatch() bool { return auto != "" && m.ElementName != auto } -// GetRepoManifest returns the manifest for a repo, or nil if none exists. -func GetRepoManifest(ctx context.Context, repoID int64) (*RepoManifest, error) { - m := new(RepoManifest) +// GetRepoMetadata returns the metadata for a repo, or nil if none exists. +func GetRepoMetadata(ctx context.Context, repoID int64) (*RepoMetadata, error) { + m := new(RepoMetadata) has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(m) if err != nil { return nil, err @@ -115,9 +128,9 @@ func GetRepoManifest(ctx context.Context, repoID int64) (*RepoManifest, error) { return m, nil } -// CreateOrUpdateRepoManifest upserts a repo manifest. -func CreateOrUpdateRepoManifest(ctx context.Context, m *RepoManifest) error { - existing := new(RepoManifest) +// CreateOrUpdateRepoMetadata upserts a repo metadata. +func CreateOrUpdateRepoMetadata(ctx context.Context, m *RepoMetadata) error { + existing := new(RepoMetadata) has, err := db.GetEngine(ctx).Where("repo_id = ?", m.RepoID).Get(existing) if err != nil { return err @@ -131,8 +144,8 @@ func CreateOrUpdateRepoManifest(ctx context.Context, m *RepoManifest) error { 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)) +// DeleteRepoMetadata deletes the metadata for a repo. +func DeleteRepoMetadata(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(RepoMetadata)) return err } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index cd24cf4a13..76566a5e50 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1480,11 +1480,11 @@ func Routes() *web.Router { }, reqAdmin()) }, reqAnyRepoReader()) m.Combo("/metadata", reqRepoReader(unit.TypeCode)). - Get(repo.GetRepoManifest). - Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest) + Get(repo.GetRepoMetadata). + Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata) m.Combo("/manifest", reqRepoReader(unit.TypeCode)). // backward compat - Get(repo.GetRepoManifest). - Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest) + Get(repo.GetRepoMetadata). + Put(reqToken(), reqAdmin(), repo.UpdateRepoMetadata) // MokoGitea badge engine m.Get("/badge/{type}.svg", repo.GetRepoBadge) m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates) diff --git a/routers/api/v1/repo/manifest.go b/routers/api/v1/repo/manifest.go index d7979f65fb..4c11037aa3 100644 --- a/routers/api/v1/repo/manifest.go +++ b/routers/api/v1/repo/manifest.go @@ -11,8 +11,8 @@ import ( "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" ) -// apiManifest is the JSON representation of a repo manifest. -type apiManifest struct { +// apiMetadata is the JSON representation of a repo manifest. +type apiMetadata struct { Name string `json:"name"` Org string `json:"org"` Description string `json:"description"` @@ -34,8 +34,8 @@ type apiManifest struct { EntryPoint string `json:"entry_point"` } -// GetRepoManifest returns the manifest settings for a repository. -func GetRepoManifest(ctx *context.APIContext) { +// GetRepoMetadata returns the manifest settings for a repository. +func GetRepoMetadata(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/manifest repository repoGetManifest // --- // summary: Get repo manifest settings @@ -46,21 +46,21 @@ func GetRepoManifest(ctx *context.APIContext) { // "$ref": "#/responses/Manifest" // "404": // "$ref": "#/responses/notFound" - m, err := repo_model.GetRepoManifest(ctx, ctx.Repo.Repository.ID) + m, err := repo_model.GetRepoMetadata(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{ + ctx.JSON(http.StatusOK, &apiMetadata{ Name: ctx.Repo.Repository.Name, Org: ctx.Repo.Repository.OwnerName, Description: ctx.Repo.Repository.Description, }) return } - ctx.JSON(http.StatusOK, &apiManifest{ + ctx.JSON(http.StatusOK, &apiMetadata{ Name: m.Name, Org: m.Org, Description: m.Description, @@ -84,8 +84,8 @@ func GetRepoManifest(ctx *context.APIContext) { }) } -// UpdateRepoManifest updates the manifest settings for a repository. -func UpdateRepoManifest(ctx *context.APIContext) { +// UpdateRepoMetadata updates the manifest settings for a repository. +func UpdateRepoMetadata(ctx *context.APIContext) { // swagger:operation PUT /repos/{owner}/{repo}/manifest repository repoUpdateManifest // --- // summary: Update repo manifest settings @@ -96,13 +96,13 @@ func UpdateRepoManifest(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/Manifest" - var req apiManifest + var req apiMetadata if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { ctx.APIError(http.StatusBadRequest, err) return } - m := &repo_model.RepoManifest{ + m := &repo_model.RepoMetadata{ RepoID: ctx.Repo.Repository.ID, Name: req.Name, Org: req.Org, @@ -126,12 +126,12 @@ func UpdateRepoManifest(ctx *context.APIContext) { EntryPoint: req.EntryPoint, } - if err := repo_model.CreateOrUpdateRepoManifest(ctx, m); err != nil { + if err := repo_model.CreateOrUpdateRepoMetadata(ctx, m); err != nil { ctx.APIErrorInternal(err) return } - ctx.JSON(http.StatusOK, &apiManifest{ + ctx.JSON(http.StatusOK, &apiMetadata{ Name: m.Name, Org: m.Org, Description: m.Description, diff --git a/routers/web/repo/setting/metadata.go b/routers/web/repo/setting/metadata.go index a7acda7b48..b350dfe0ce 100644 --- a/routers/web/repo/setting/metadata.go +++ b/routers/web/repo/setting/metadata.go @@ -26,13 +26,13 @@ func Metadata(ctx *context.Context) { ownerID := ctx.Repo.Repository.OwnerID // Load manifest (project identity). - manifest, err := repo_model.GetRepoManifest(ctx, repoID) + manifest, err := repo_model.GetRepoMetadata(ctx, repoID) if err != nil { - ctx.ServerError("GetRepoManifest", err) + ctx.ServerError("GetRepoMetadata", err) return } if manifest == nil { - manifest = &repo_model.RepoManifest{ + manifest = &repo_model.RepoMetadata{ RepoID: repoID, Name: ctx.Repo.Repository.Name, Org: ctx.Repo.Repository.OwnerName, @@ -68,11 +68,11 @@ func Metadata(ctx *context.Context) { func MetadataPost(ctx *context.Context) { switch ctx.FormString("action") { case "manifest": - saveManifest(ctx) + saveMetadata(ctx) case "customfields": saveCustomFields(ctx) default: - saveManifest(ctx) + saveMetadata(ctx) } } @@ -92,14 +92,14 @@ var spdxToName = map[string]string{ "proprietary": "Proprietary", } -func saveManifest(ctx *context.Context) { +func saveMetadata(ctx *context.Context) { spdx := ctx.FormString("license_spdx") licenseName := spdxToName[spdx] // Preserve existing values for fields removed from the UI. - existing, _ := repo_model.GetRepoManifest(ctx, ctx.Repo.Repository.ID) + existing, _ := repo_model.GetRepoMetadata(ctx, ctx.Repo.Repository.ID) - manifest := &repo_model.RepoManifest{ + manifest := &repo_model.RepoMetadata{ RepoID: ctx.Repo.Repository.ID, Name: ctx.FormString("name"), Org: ctx.FormString("org"), @@ -127,8 +127,8 @@ func saveManifest(ctx *context.Context) { manifest.Language = existing.Language } - if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil { - ctx.ServerError("CreateOrUpdateRepoManifest", err) + if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil { + ctx.ServerError("CreateOrUpdateRepoMetadata", err) return } diff --git a/services/repository/manifest_sync.go b/services/repository/manifest_sync.go index 765df0bf37..eb6f11af6e 100644 --- a/services/repository/manifest_sync.go +++ b/services/repository/manifest_sync.go @@ -57,11 +57,11 @@ type manifestBuild struct { EntryPoint string `xml:"entry-point"` } -// SyncManifestFromCommit reads .mokogitea/manifest.xml from the given commit +// SyncMetadataFromCommit 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) { +func SyncMetadataFromCommit(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) { if commit == nil { return } @@ -73,7 +73,7 @@ func SyncManifestFromCommit(ctx context.Context, repo *repo_model.Repository, co reader, err := entry.Blob().DataAsync() if err != nil { - log.Error("SyncManifest: read blob for %s: %v", repo.FullName(), err) + log.Error("SyncMetadata: read blob for %s: %v", repo.FullName(), err) return } defer reader.Close() @@ -81,11 +81,11 @@ func SyncManifestFromCommit(ctx context.Context, repo *repo_model.Repository, co 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) + log.Error("SyncMetadata: parse XML for %s: %v", repo.FullName(), err) return } - manifest := &repo_model.RepoManifest{ + manifest := &repo_model.RepoMetadata{ RepoID: repo.ID, Name: mxml.Identity.Name, Org: mxml.Identity.Org, @@ -109,10 +109,10 @@ func SyncManifestFromCommit(ctx context.Context, repo *repo_model.Repository, co EntryPoint: mxml.Build.EntryPoint, } - if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil { - log.Error("SyncManifest: save for %s: %v", repo.FullName(), err) + if err := repo_model.CreateOrUpdateRepoMetadata(ctx, manifest); err != nil { + log.Error("SyncMetadata: save for %s: %v", repo.FullName(), err) return } - log.Info("SyncManifest: synced .mokogitea/manifest.xml for %s", repo.FullName()) + log.Info("SyncMetadata: synced .mokogitea/manifest.xml for %s", repo.FullName()) } diff --git a/services/repository/push.go b/services/repository/push.go index e893319cdd..efc9134c3b 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -195,7 +195,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { log.Error("DelRepoDivergenceFromCache: %v", err) } // Auto-sync .mokogitea/manifest.xml to database on default branch push - SyncManifestFromCommit(ctx, repo, newCommit) + SyncMetadataFromCommit(ctx, repo, newCommit) // Run security scanners on default branch push security_service.ScanOnPush(ctx, repo, newCommit) } else { diff --git a/services/updateserver/joomla.go b/services/updateserver/joomla.go index bac8f634e2..6d515067eb 100644 --- a/services/updateserver/joomla.go +++ b/services/updateserver/joomla.go @@ -187,9 +187,9 @@ func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository, } // Manifest is the source of truth for extension metadata. - manifest, err := repo_model.GetRepoManifest(ctx, repo.ID) + manifest, err := repo_model.GetRepoMetadata(ctx, repo.ID) if err != nil { - log.Error("resolveExtensionMetadata: GetRepoManifest for repo %d: %v", repo.ID, err) + log.Error("resolveExtensionMetadata: GetRepoMetadata for repo %d: %v", repo.ID, err) } if manifest != nil { if elem := manifest.FullElementName(); elem != "" {