mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
144 lines
4.1 KiB
Go
144 lines
4.1 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package composer
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"time"
|
|
|
|
packages_model "github.com/hanzoai/git/models/packages"
|
|
access_model "github.com/hanzoai/git/models/perm/access"
|
|
"github.com/hanzoai/git/modules/log"
|
|
composer_module "github.com/hanzoai/git/modules/packages/composer"
|
|
"github.com/hanzoai/git/services/context"
|
|
)
|
|
|
|
// ServiceIndexResponse contains registry endpoints
|
|
type ServiceIndexResponse struct {
|
|
SearchTemplate string `json:"search"`
|
|
MetadataTemplate string `json:"metadata-url"`
|
|
PackageList string `json:"list"`
|
|
}
|
|
|
|
func createServiceIndexResponse(registryURL string) *ServiceIndexResponse {
|
|
return &ServiceIndexResponse{
|
|
SearchTemplate: registryURL + "/search.json?q=%query%&type=%type%",
|
|
MetadataTemplate: registryURL + "/p2/%package%.json",
|
|
PackageList: registryURL + "/list.json",
|
|
}
|
|
}
|
|
|
|
// SearchResultResponse contains search results
|
|
type SearchResultResponse struct {
|
|
Total int64 `json:"total"`
|
|
Results []*SearchResult `json:"results"`
|
|
NextLink string `json:"next,omitempty"`
|
|
}
|
|
|
|
// SearchResult contains a search result
|
|
type SearchResult struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Downloads int64 `json:"downloads"`
|
|
}
|
|
|
|
func createSearchResultResponse(total int64, pds []*packages_model.PackageDescriptor, nextLink string) *SearchResultResponse {
|
|
results := make([]*SearchResult, 0, len(pds))
|
|
|
|
for _, pd := range pds {
|
|
results = append(results, &SearchResult{
|
|
Name: pd.Package.Name,
|
|
Description: pd.Metadata.(*composer_module.Metadata).Description,
|
|
Downloads: pd.Version.DownloadCount,
|
|
})
|
|
}
|
|
|
|
return &SearchResultResponse{
|
|
Total: total,
|
|
Results: results,
|
|
NextLink: nextLink,
|
|
}
|
|
}
|
|
|
|
// PackageMetadataResponse contains packages metadata
|
|
type PackageMetadataResponse struct {
|
|
Minified string `json:"minified"`
|
|
Packages map[string][]*PackageVersionMetadata `json:"packages"`
|
|
}
|
|
|
|
// PackageVersionMetadata contains package metadata
|
|
// https://getcomposer.org/doc/05-repositories.md#package
|
|
type PackageVersionMetadata struct {
|
|
*composer_module.Metadata
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Type string `json:"type"`
|
|
Created time.Time `json:"time"`
|
|
Dist Dist `json:"dist"`
|
|
Source Source `json:"source"`
|
|
}
|
|
|
|
// Dist contains package download information
|
|
type Dist struct {
|
|
Type string `json:"type"`
|
|
URL string `json:"url"`
|
|
Checksum string `json:"shasum"`
|
|
}
|
|
|
|
// Source contains package source information
|
|
type Source struct {
|
|
URL string `json:"url"`
|
|
Type string `json:"type"`
|
|
Reference string `json:"reference"`
|
|
}
|
|
|
|
func createPackageMetadataResponse(ctx *context.Context, registryURL string, pds []*packages_model.PackageDescriptor) *PackageMetadataResponse {
|
|
versions := make([]*PackageVersionMetadata, 0, len(pds))
|
|
|
|
for _, pd := range pds {
|
|
packageType := ""
|
|
for _, pvp := range pd.VersionProperties {
|
|
if pvp.Name == composer_module.TypeProperty {
|
|
packageType = pvp.Value
|
|
break
|
|
}
|
|
}
|
|
|
|
pkg := PackageVersionMetadata{
|
|
Name: pd.Package.Name,
|
|
Version: pd.Version.Version,
|
|
Type: packageType,
|
|
Created: pd.Version.CreatedUnix.AsLocalTime(),
|
|
Metadata: pd.Metadata.(*composer_module.Metadata),
|
|
Dist: Dist{
|
|
Type: "zip",
|
|
URL: fmt.Sprintf("%s/files/%s/%s/%s", registryURL, url.PathEscape(pd.Package.LowerName), url.PathEscape(pd.Version.LowerVersion), url.PathEscape(pd.Files[0].File.LowerName)),
|
|
Checksum: pd.Files[0].Blob.HashSHA1,
|
|
},
|
|
}
|
|
if pd.Repository != nil {
|
|
permission, err := access_model.GetDoerRepoPermission(ctx, pd.Repository, ctx.Doer)
|
|
if err != nil {
|
|
log.Error("GetDoerRepoPermission[%d]: %v", pd.Repository.ID, err)
|
|
} else if permission.HasAnyUnitAccessOrPublicAccess() {
|
|
pkg.Source = Source{
|
|
URL: pd.Repository.HTMLURL(),
|
|
Type: "git",
|
|
Reference: pd.Version.Version,
|
|
}
|
|
}
|
|
}
|
|
|
|
versions = append(versions, &pkg)
|
|
}
|
|
|
|
return &PackageMetadataResponse{
|
|
Minified: "composer/2.0",
|
|
Packages: map[string][]*PackageVersionMetadata{
|
|
pds[0].Package.Name: versions,
|
|
},
|
|
}
|
|
}
|