diff --git a/services/updateserver/composer.go b/services/updateserver/composer.go index 4353daa6d1..ad9326acfc 100644 --- a/services/updateserver/composer.go +++ b/services/updateserver/composer.go @@ -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 custom field "Extension Name") 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,11 +86,6 @@ 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 diff --git a/services/updateserver/drupal.go b/services/updateserver/drupal.go index 1c9495021c..8e5472d4d7 100644 --- a/services/updateserver/drupal.go +++ b/services/updateserver/drupal.go @@ -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) diff --git a/services/updateserver/joomla.go b/services/updateserver/joomla.go index 11139ddbc7..51319bc028 100644 --- a/services/updateserver/joomla.go +++ b/services/updateserver/joomla.go @@ -13,6 +13,7 @@ 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/storage" @@ -160,9 +161,102 @@ 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 +} + +// 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 + } + } + + // Override with custom field values (highest priority). + fields, err := issues_model.GetCustomFieldsByOwner(ctx, repo.OwnerID, issues_model.CustomFieldScopeRepo) + if err != nil || len(fields) == 0 { + return m + } + values, err := issues_model.GetCustomFieldValuesMap(ctx, repo.ID) + if err != nil || 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["Download Gating"]; v != "" && cfg != nil { + cfg.DownloadGating = v + } + if v := named["Key Prefix"]; v != "" && cfg != nil { + cfg.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{ @@ -185,21 +279,18 @@ 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 := meta.Element + displayName := meta.DisplayName + extType := meta.ExtType + targetVersion := meta.TargetVersion + phpMinimum := meta.PHPMinimum + feedDescription := meta.Description - element := strings.ToLower(repo.Name) - if cfg != nil && cfg.ExtensionName != "" { - element = cfg.ExtensionName - } - 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 and URL always come from the org profile. maintainer := repo.Owner.FullName if maintainer == "" { @@ -209,18 +300,6 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require if maintainerURL == "" { maintainerURL = fmt.Sprintf("%s/%s", baseURL, repo.Owner.Name) } - targetVersion := "(5|6)\\..*" - 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 - } // Resolve effective streams (repo override → org default → Joomla default). streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID) @@ -319,8 +398,8 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require // 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 + if meta.SupportURL != "" { + infoURL = meta.SupportURL } // Joomla element: packages use client_id=0 in #__extensions, diff --git a/services/updateserver/prestashop.go b/services/updateserver/prestashop.go index 81442f756e..8795500c71 100644 --- a/services/updateserver/prestashop.go +++ b/services/updateserver/prestashop.go @@ -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) diff --git a/services/updateserver/whmcs.go b/services/updateserver/whmcs.go index a3a95a50cb..da188162f9 100644 --- a/services/updateserver/whmcs.go +++ b/services/updateserver/whmcs.go @@ -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) diff --git a/services/updateserver/wordpress.go b/services/updateserver/wordpress.go index d17ef645d0..619ffa04ba 100644 --- a/services/updateserver/wordpress.go +++ b/services/updateserver/wordpress.go @@ -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.