549e890cd0
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Generic: Project CI / Lint & Validate (push) Successful in 40s
Deploy MokoGitea / deploy (push) Successful in 5m29s
Generic: Project CI / Tests (push) Has been cancelled
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
Rename the licensing/update server model package to better reflect its purpose. All imports updated from licenses_model to updateserver_model. License key management UI files kept (only imports updated).
149 lines
4.2 KiB
Go
149 lines
4.2 KiB
Go
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package updateserver
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
|
)
|
|
|
|
// PrestaShop module update XML structures.
|
|
|
|
type psUpdates struct {
|
|
XMLName xml.Name `xml:"modules"`
|
|
Modules []psModule `xml:"module"`
|
|
}
|
|
|
|
type psModule struct {
|
|
Name string `xml:"name,attr"`
|
|
Version string `xml:"version,attr"`
|
|
DisplayName string `xml:"displayName"`
|
|
Description string `xml:"description,omitempty"`
|
|
Author string `xml:"author"`
|
|
Tab string `xml:"tab,omitempty"`
|
|
Download string `xml:"download"`
|
|
Date string `xml:"date"`
|
|
SHA256 string `xml:"sha256,omitempty"`
|
|
}
|
|
|
|
// GeneratePrestaShopXML builds a PrestaShop-compatible module update XML.
|
|
func GeneratePrestaShopXML(ctx context.Context, repo *repo_model.Repository, allowedChannels ...string) ([]byte, error) {
|
|
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
|
RepoID: repo.ID,
|
|
ListOptions: db.ListOptionsAll,
|
|
IncludeDrafts: false,
|
|
IncludeTags: false,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("FindReleases: %w", err)
|
|
}
|
|
|
|
if err := repo.LoadOwner(ctx); err != nil {
|
|
return nil, fmt.Errorf("LoadOwner: %w", err)
|
|
}
|
|
|
|
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
|
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
|
|
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
|
moduleName := meta.Element
|
|
displayName := meta.DisplayName
|
|
description := meta.Description
|
|
maintainer := repo.Owner.Name
|
|
if cfg != nil && cfg.Maintainer != "" {
|
|
maintainer = cfg.Maintainer
|
|
}
|
|
|
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
|
|
|
// Channel filtering.
|
|
channelAllowed := make(map[string]bool)
|
|
if len(allowedChannels) > 0 {
|
|
for _, c := range allowedChannels {
|
|
channelAllowed[NormalizeChannel(c)] = true
|
|
}
|
|
}
|
|
|
|
// Track best release per channel.
|
|
bestByChannel := make(map[string]*repo_model.Release)
|
|
for _, rel := range releases {
|
|
if rel.IsDraft || rel.IsTag {
|
|
continue
|
|
}
|
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
|
existing, ok := bestByChannel[ch]
|
|
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
|
bestByChannel[ch] = rel
|
|
}
|
|
}
|
|
|
|
var result psUpdates
|
|
for _, stream := range streams {
|
|
ch := stream.Name
|
|
if len(channelAllowed) > 0 && !channelAllowed[ch] {
|
|
continue
|
|
}
|
|
rel, ok := bestByChannel[ch]
|
|
if !ok || ch != "stable" {
|
|
continue // PrestaShop typically only serves stable
|
|
}
|
|
|
|
if err := rel.LoadAttributes(ctx); err != nil {
|
|
continue
|
|
}
|
|
|
|
var downloadURL, sha256Hash string
|
|
for _, att := range rel.Attachments {
|
|
if strings.HasSuffix(strings.ToLower(att.Name), ".zip") && !strings.HasSuffix(att.Name, ".sha256") {
|
|
downloadURL = fmt.Sprintf("%s/releases/download/%s/%s", repoLink, rel.TagName, att.Name)
|
|
break
|
|
}
|
|
}
|
|
if downloadURL == "" {
|
|
downloadURL = fmt.Sprintf("%s/archive/%s.zip", repoLink, rel.TagName)
|
|
}
|
|
for _, att := range rel.Attachments {
|
|
if strings.HasSuffix(att.Name, ".sha256") {
|
|
sha256Hash = readSHA256FromSidecar(ctx, att)
|
|
break
|
|
}
|
|
}
|
|
|
|
version := extractVersion(rel.TagName)
|
|
if isStreamName(version, streams) || version == "" {
|
|
version = extractVersion(rel.Title)
|
|
}
|
|
if version == "" {
|
|
version = rel.TagName
|
|
}
|
|
|
|
result.Modules = append(result.Modules, psModule{
|
|
Name: moduleName,
|
|
Version: version,
|
|
DisplayName: displayName,
|
|
Description: description,
|
|
Author: maintainer,
|
|
Download: downloadURL,
|
|
Date: time.Unix(int64(rel.CreatedUnix), 0).Format("2006-01-02"),
|
|
SHA256: sha256Hash,
|
|
})
|
|
}
|
|
|
|
output, err := xml.MarshalIndent(result, "", " ")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("xml.MarshalIndent: %w", err)
|
|
}
|
|
|
|
return append([]byte(xml.Header), output...), nil
|
|
}
|