feat: use manifest API as source of truth for update feed metadata (#592)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 3s
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

Replace custom field + config table cascade with manifest-only read
for extension identity fields (element_name, package_type, display_name,
target_version, php_minimum, description). Config table retained only
for licensing fields (download_gating, key_prefix, support_url fallback).

Fix client field: package/component/library/file → administrator.

Remove issues_model import (custom field lookups removed).
This commit is contained in:
Jonathan Miller
2026-06-09 14:32:39 -05:00
parent 2135f4c37c
commit aeb36e4312
+43 -85
View File
@@ -13,7 +13,6 @@ 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"
@@ -163,7 +162,7 @@ func NormalizeChannel(ch string) string {
}
// extensionMetadata holds resolved metadata for feed generation.
// Fields are resolved with priority: custom field → config table → default.
// Fields are resolved with priority: manifest → config table (gating only) → default.
type extensionMetadata struct {
Element string
DisplayName string
@@ -176,8 +175,9 @@ type extensionMetadata struct {
KeyPrefix string
}
// resolveExtensionMetadata loads extension metadata with cascading fallback:
// org-level repo-scoped custom fields → update_stream_config → repo-derived defaults.
// resolveExtensionMetadata loads extension metadata from the repo manifest API.
// The manifest is the single source of truth for extension identity fields.
// The config table is only used for licensing/gating fields not in the manifest.
func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository, cfg *licenses.UpdateStreamConfig) extensionMetadata {
m := extensionMetadata{
Element: strings.ToLower(repo.Name),
@@ -186,91 +186,49 @@ func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository,
TargetVersion: "(5|6)\\..*",
}
// Apply config table values.
// Manifest is the source of truth for extension metadata.
manifest, err := repo_model.GetRepoManifest(ctx, repo.ID)
if err != nil {
log.Error("resolveExtensionMetadata: GetRepoManifest for repo %d: %v", repo.ID, err)
}
if manifest != nil {
if manifest.ElementName != "" {
m.Element = manifest.ElementName
}
if manifest.PackageType != "" {
m.ExtType = manifest.PackageType
}
if manifest.DisplayName != "" {
m.DisplayName = manifest.DisplayName
}
if manifest.TargetVersion != "" {
m.TargetVersion = manifest.TargetVersion
}
if manifest.PHPMinimum != "" {
m.PHPMinimum = manifest.PHPMinimum
}
if manifest.Description != "" {
m.Description = manifest.Description
}
if manifest.InfoURL != "" {
m.SupportURL = manifest.InfoURL
}
}
// Config table: only licensing/gating fields (not in manifest).
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
// SupportURL from config as fallback if manifest.InfoURL is empty
if m.SupportURL == "" && cfg.SupportURL != "" {
m.SupportURL = cfg.SupportURL
}
}
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
}
@@ -422,13 +380,13 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
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).
// Joomla <client> element: admin-side extensions use "administrator",
// site-side extensions use "site". Packages, components, libraries,
// and files are admin-side by default.
client := "site"
if extType == "package" {
client = "0"
switch extType {
case "package", "component", "library", "file":
client = "administrator"
}
u := xmlUpdate{