@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package updateserver
|
||||
|
||||
import "strings"
|
||||
|
||||
// Update-feed platform identifiers. These are the recognized values for the
|
||||
// UpdateStreamConfig.Platform column and for the resolved "RepoUpdatePlatform"
|
||||
// context value used by the Serve* update-feed handlers.
|
||||
//
|
||||
// The Platform column is a plain string (no DB-level enum/CHECK constraint), so
|
||||
// these constants are enforced at the application layer only — adding new values
|
||||
// requires no schema migration.
|
||||
const (
|
||||
PlatformJoomla = "joomla"
|
||||
PlatformDolibarr = "dolibarr"
|
||||
PlatformBoth = "both" // joomla + dolibarr
|
||||
PlatformWordPress = "wordpress"
|
||||
PlatformPrestaShop = "prestashop"
|
||||
PlatformDrupal = "drupal"
|
||||
PlatformComposer = "composer"
|
||||
PlatformWHMCS = "whmcs"
|
||||
|
||||
// Feedless platforms: repositories whose distribution channel is an external
|
||||
// package registry (npm, MCP). They must NOT emit any Joomla/Dolibarr/etc.
|
||||
// update feed — the Serve* handlers return NotFound for these. See #810.
|
||||
PlatformNPM = "npm"
|
||||
PlatformMCP = "mcp"
|
||||
)
|
||||
|
||||
// feedlessPlatforms are platforms that never emit a legacy update feed.
|
||||
var feedlessPlatforms = map[string]struct{}{
|
||||
PlatformNPM: {},
|
||||
PlatformMCP: {},
|
||||
}
|
||||
|
||||
// IsFeedlessPlatform reports whether the given platform must not serve any
|
||||
// update feed (npm / mcp). These repositories are published to an external
|
||||
// registry, so every Serve* handler returns NotFound for them. See #810.
|
||||
func IsFeedlessPlatform(platform string) bool {
|
||||
_, ok := feedlessPlatforms[strings.ToLower(strings.TrimSpace(platform))]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResolvePlatform determines the effective update-feed platform for a repo.
|
||||
//
|
||||
// - If the update-stream config sets an explicit platform, that wins.
|
||||
// - Otherwise the platform is derived from the repository metadata platform
|
||||
// (metadata.platform), so the two stores can no longer drift. See #811.
|
||||
// - Feedless metadata values (npm / mcp) are propagated verbatim so the
|
||||
// handlers can return NotFound instead of falling back to a Joomla feed.
|
||||
// - Only when neither source is set do we fall back to the historical
|
||||
// "joomla" default (used by legacy Joomla extension repos).
|
||||
//
|
||||
// This is the single source of truth for the old "empty ⇒ joomla" fallback,
|
||||
// which previously caused npm/mcp repos with no config row to emit a Joomla
|
||||
// feed. See #810 and #811.
|
||||
func ResolvePlatform(cfgPlatform, metadataPlatform string) string {
|
||||
if p := strings.ToLower(strings.TrimSpace(cfgPlatform)); p != "" {
|
||||
return p
|
||||
}
|
||||
if p := strings.ToLower(strings.TrimSpace(metadataPlatform)); p != "" {
|
||||
return p
|
||||
}
|
||||
return PlatformJoomla
|
||||
}
|
||||
@@ -78,10 +78,13 @@ func validateUpdateKey(ctx *context.Context) (allowedChannels []string, ok bool,
|
||||
// ServeUpdatesXML generates and serves a Joomla-compatible updates.xml
|
||||
// from the repository's releases.
|
||||
func ServeUpdatesXML(ctx *context.Context) {
|
||||
// Block if platform is set to a non-Joomla value.
|
||||
// Empty/unset defaults to joomla for backwards compatibility.
|
||||
// Only serve the Joomla feed for joomla/both platforms. The platform is
|
||||
// resolved upstream (config override → repo metadata → "joomla" default),
|
||||
// so an unset or feedless (npm/mcp) platform never reaches the Joomla
|
||||
// generator. Any other platform — including npm/mcp — returns NotFound.
|
||||
// See #810 / #811.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != "" && platform != "joomla" && platform != "both" {
|
||||
if platform != updateserver_model.PlatformJoomla && platform != updateserver_model.PlatformBoth {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
@@ -114,9 +117,10 @@ func ServeUpdatesXML(ctx *context.Context) {
|
||||
// from the repository's releases. Uses the same license key validation as the
|
||||
// Joomla XML feed — all platforms share the same licensing system.
|
||||
func ServeDolibarrJSON(ctx *context.Context) {
|
||||
// Block if platform doesn't include dolibarr.
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform == "joomla" || platform == "" {
|
||||
// Only serve for dolibarr/both. Feedless platforms (npm/mcp) and any other
|
||||
// platform return NotFound rather than a feed. See #810 / #811.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != updateserver_model.PlatformDolibarr && platform != updateserver_model.PlatformBoth {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
@@ -149,9 +153,10 @@ func ServeDolibarrJSON(ctx *context.Context) {
|
||||
// ServeWordPressJSON generates and serves a WordPress PUC-compatible update feed.
|
||||
// Compatible with the YahnisElsts plugin-update-checker library.
|
||||
func ServeWordPressJSON(ctx *context.Context) {
|
||||
// Block if platform doesn't include wordpress.
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform != "wordpress" && platform != "both" {
|
||||
// Only serve for wordpress/both. Feedless platforms (npm/mcp) and any other
|
||||
// platform return NotFound. See #810.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != updateserver_model.PlatformWordPress && platform != updateserver_model.PlatformBoth {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
@@ -195,8 +200,10 @@ func ServeWordPressJSON(ctx *context.Context) {
|
||||
|
||||
// ServeComposerJSON generates and serves a Composer packages.json feed.
|
||||
func ServeComposerJSON(ctx *context.Context) {
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform != "composer" {
|
||||
// Only serve for composer. Feedless platforms (npm/mcp) and any other
|
||||
// platform return NotFound. See #810.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != updateserver_model.PlatformComposer {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
@@ -233,8 +240,10 @@ func ServeComposerJSON(ctx *context.Context) {
|
||||
|
||||
// ServePrestaShopXML generates and serves a PrestaShop module update XML.
|
||||
func ServePrestaShopXML(ctx *context.Context) {
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform != "prestashop" {
|
||||
// Only serve for prestashop. Feedless platforms (npm/mcp) and any other
|
||||
// platform return NotFound. See #810.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != updateserver_model.PlatformPrestaShop {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
@@ -260,8 +269,10 @@ func ServePrestaShopXML(ctx *context.Context) {
|
||||
|
||||
// ServeDrupalXML generates and serves a Drupal update status XML.
|
||||
func ServeDrupalXML(ctx *context.Context) {
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform != "drupal" {
|
||||
// Only serve for drupal. Feedless platforms (npm/mcp) and any other
|
||||
// platform return NotFound. See #810.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != updateserver_model.PlatformDrupal {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
@@ -287,8 +298,10 @@ func ServeDrupalXML(ctx *context.Context) {
|
||||
|
||||
// ServeWHMCSJSON generates and serves a WHMCS module update JSON.
|
||||
func ServeWHMCSJSON(ctx *context.Context) {
|
||||
platform := ctx.Data["RepoUpdatePlatform"]
|
||||
if platform != "whmcs" {
|
||||
// Only serve for whmcs. Feedless platforms (npm/mcp) and any other
|
||||
// platform return NotFound. See #810.
|
||||
platform, _ := ctx.Data["RepoUpdatePlatform"].(string)
|
||||
if platform != updateserver_model.PlatformWHMCS {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -701,12 +701,21 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
|
||||
ctx.Data["IsRepoAdmin"] = ctx.Repo.Permission.IsAdmin()
|
||||
ctx.Data["IsSiteAdmin"] = ctx.IsUserSiteAdmin()
|
||||
|
||||
// Load repo update config for platform-aware UI.
|
||||
// Resolve the effective update platform for platform-aware UI. When the
|
||||
// update-stream config has no explicit platform, derive it from the repo
|
||||
// metadata (metadata.platform) so npm/mcp repos are recognized and the two
|
||||
// stores can't drift. See #810 / #811.
|
||||
cfgPlatform := ""
|
||||
if repoUpdateCfg != nil {
|
||||
ctx.Data["RepoUpdatePlatform"] = repoUpdateCfg.Platform
|
||||
} else {
|
||||
ctx.Data["RepoUpdatePlatform"] = "joomla"
|
||||
cfgPlatform = repoUpdateCfg.Platform
|
||||
}
|
||||
metadataPlatform := ""
|
||||
if meta, err := repo_model.GetRepoMetadata(ctx, repo.ID); err != nil {
|
||||
log.Error("GetRepoMetadata: %v", err)
|
||||
} else if meta != nil {
|
||||
metadataPlatform = meta.Platform
|
||||
}
|
||||
ctx.Data["RepoUpdatePlatform"] = updateserver_model.ResolvePlatform(cfgPlatform, metadataPlatform)
|
||||
|
||||
ctx.Data["Title"] = repo.Owner.Name + "/" + repo.Name
|
||||
ctx.Data["PageTitleCommon"] = repo.Name + " - " + setting.AppName
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/updateserver"
|
||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/repo"
|
||||
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/updateserver"
|
||||
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGIT/models/user"
|
||||
"code.mokoconsulting.tech/MokoConsulting/MokoGIT/modules/log"
|
||||
)
|
||||
@@ -50,10 +50,18 @@ func RepoAssignmentPublicFeed() func(ctx *Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["RepoUpdatePlatform"] = cfg.Platform
|
||||
if cfg.Platform == "" {
|
||||
ctx.Data["RepoUpdatePlatform"] = "joomla"
|
||||
// Resolve the effective feed platform. When the update-stream config has
|
||||
// no explicit platform override, derive it from the repo metadata
|
||||
// (metadata.platform) so the two stores can't drift, and so npm/mcp repos
|
||||
// are recognized as feedless instead of falling back to a Joomla feed.
|
||||
// See #810 / #811.
|
||||
metadataPlatform := ""
|
||||
if meta, err := repo_model.GetRepoMetadata(ctx, repo.ID); err != nil {
|
||||
log.Error("GetRepoMetadata: %v", err)
|
||||
} else if meta != nil {
|
||||
metadataPlatform = meta.Platform
|
||||
}
|
||||
ctx.Data["RepoUpdatePlatform"] = updateserver_model.ResolvePlatform(cfg.Platform, metadataPlatform)
|
||||
|
||||
log.Trace("Public feed access: %s/%s", ownerName, repoName)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user