Merge pull request 'fix: lowercase Joomla element names, rename Manifest → Metadata (#635)' (#644) from fix/635-joomla-element-name into main
Deploy MokoGitea / deploy (push) Failing after 3m22s
Deploy MokoGitea / deploy (push) Failing after 3m22s
This commit was merged in pull request #644.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
Reference in New Issue
Block a user