720 lines
24 KiB
Go
720 lines
24 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"cmr-backend/internal/apperr"
|
|
"cmr-backend/internal/platform/security"
|
|
"cmr-backend/internal/store/postgres"
|
|
)
|
|
|
|
type AdminResourceService struct {
|
|
store *postgres.Store
|
|
}
|
|
|
|
type AdminMapSummary struct {
|
|
ID string `json:"id"`
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Description *string `json:"description,omitempty"`
|
|
CurrentVersionID *string `json:"currentVersionId,omitempty"`
|
|
CurrentVersion *AdminMapVersionBrief `json:"currentVersion,omitempty"`
|
|
}
|
|
|
|
type AdminMapVersionBrief struct {
|
|
ID string `json:"id"`
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type AdminMapVersion struct {
|
|
ID string `json:"id"`
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
MapmetaURL string `json:"mapmetaUrl"`
|
|
TilesRootURL string `json:"tilesRootUrl"`
|
|
PublishedAssetRoot *string `json:"publishedAssetRoot,omitempty"`
|
|
Bounds map[string]any `json:"bounds,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type AdminMapDetail struct {
|
|
Map AdminMapSummary `json:"map"`
|
|
Versions []AdminMapVersion `json:"versions"`
|
|
}
|
|
|
|
type CreateAdminMapInput struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Description *string `json:"description,omitempty"`
|
|
}
|
|
|
|
type CreateAdminMapVersionInput struct {
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
MapmetaURL string `json:"mapmetaUrl"`
|
|
TilesRootURL string `json:"tilesRootUrl"`
|
|
PublishedAssetRoot *string `json:"publishedAssetRoot,omitempty"`
|
|
Bounds map[string]any `json:"bounds,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
SetAsCurrent bool `json:"setAsCurrent"`
|
|
}
|
|
|
|
type AdminPlayfieldSummary struct {
|
|
ID string `json:"id"`
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Kind string `json:"kind"`
|
|
Status string `json:"status"`
|
|
Description *string `json:"description,omitempty"`
|
|
CurrentVersionID *string `json:"currentVersionId,omitempty"`
|
|
CurrentVersion *AdminPlayfieldVersionBrief `json:"currentVersion,omitempty"`
|
|
}
|
|
|
|
type AdminPlayfieldVersionBrief struct {
|
|
ID string `json:"id"`
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
SourceType string `json:"sourceType"`
|
|
}
|
|
|
|
type AdminPlayfieldVersion struct {
|
|
ID string `json:"id"`
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
SourceType string `json:"sourceType"`
|
|
SourceURL string `json:"sourceUrl"`
|
|
PublishedAssetRoot *string `json:"publishedAssetRoot,omitempty"`
|
|
ControlCount *int `json:"controlCount,omitempty"`
|
|
Bounds map[string]any `json:"bounds,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type AdminPlayfieldDetail struct {
|
|
Playfield AdminPlayfieldSummary `json:"playfield"`
|
|
Versions []AdminPlayfieldVersion `json:"versions"`
|
|
}
|
|
|
|
type CreateAdminPlayfieldInput struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Kind string `json:"kind"`
|
|
Status string `json:"status"`
|
|
Description *string `json:"description,omitempty"`
|
|
}
|
|
|
|
type CreateAdminPlayfieldVersionInput struct {
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
SourceType string `json:"sourceType"`
|
|
SourceURL string `json:"sourceUrl"`
|
|
PublishedAssetRoot *string `json:"publishedAssetRoot,omitempty"`
|
|
ControlCount *int `json:"controlCount,omitempty"`
|
|
Bounds map[string]any `json:"bounds,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
SetAsCurrent bool `json:"setAsCurrent"`
|
|
}
|
|
|
|
type AdminResourcePackSummary struct {
|
|
ID string `json:"id"`
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Description *string `json:"description,omitempty"`
|
|
CurrentVersionID *string `json:"currentVersionId,omitempty"`
|
|
CurrentVersion *AdminResourcePackVersionBrief `json:"currentVersion,omitempty"`
|
|
}
|
|
|
|
type AdminResourcePackVersionBrief struct {
|
|
ID string `json:"id"`
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
type AdminResourcePackVersion struct {
|
|
ID string `json:"id"`
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
ContentEntryURL *string `json:"contentEntryUrl,omitempty"`
|
|
AudioRootURL *string `json:"audioRootUrl,omitempty"`
|
|
ThemeProfileCode *string `json:"themeProfileCode,omitempty"`
|
|
PublishedAssetRoot *string `json:"publishedAssetRoot,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type AdminResourcePackDetail struct {
|
|
ResourcePack AdminResourcePackSummary `json:"resourcePack"`
|
|
Versions []AdminResourcePackVersion `json:"versions"`
|
|
}
|
|
|
|
type CreateAdminResourcePackInput struct {
|
|
Code string `json:"code"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Description *string `json:"description,omitempty"`
|
|
}
|
|
|
|
type CreateAdminResourcePackVersionInput struct {
|
|
VersionCode string `json:"versionCode"`
|
|
Status string `json:"status"`
|
|
ContentEntryURL *string `json:"contentEntryUrl,omitempty"`
|
|
AudioRootURL *string `json:"audioRootUrl,omitempty"`
|
|
ThemeProfileCode *string `json:"themeProfileCode,omitempty"`
|
|
PublishedAssetRoot *string `json:"publishedAssetRoot,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
SetAsCurrent bool `json:"setAsCurrent"`
|
|
}
|
|
|
|
func NewAdminResourceService(store *postgres.Store) *AdminResourceService {
|
|
return &AdminResourceService{store: store}
|
|
}
|
|
|
|
func (s *AdminResourceService) ListMaps(ctx context.Context, limit int) ([]AdminMapSummary, error) {
|
|
items, err := s.store.ListResourceMaps(ctx, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := make([]AdminMapSummary, 0, len(items))
|
|
for _, item := range items {
|
|
results = append(results, AdminMapSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
})
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) CreateMap(ctx context.Context, input CreateAdminMapInput) (*AdminMapSummary, error) {
|
|
input.Code = strings.TrimSpace(input.Code)
|
|
input.Name = strings.TrimSpace(input.Name)
|
|
status := normalizeCatalogStatus(input.Status)
|
|
if input.Code == "" || input.Name == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
|
|
}
|
|
|
|
publicID, err := security.GeneratePublicID("map")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx, err := s.store.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
item, err := s.store.CreateResourceMap(ctx, tx, postgres.CreateResourceMapParams{
|
|
PublicID: publicID,
|
|
Code: input.Code,
|
|
Name: input.Name,
|
|
Status: status,
|
|
Description: trimStringPtr(input.Description),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminMapSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
}, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) GetMapDetail(ctx context.Context, mapPublicID string) (*AdminMapDetail, error) {
|
|
item, err := s.store.GetResourceMapByPublicID(ctx, strings.TrimSpace(mapPublicID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if item == nil {
|
|
return nil, apperr.New(http.StatusNotFound, "map_not_found", "map not found")
|
|
}
|
|
versions, err := s.store.ListResourceMapVersions(ctx, item.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := &AdminMapDetail{
|
|
Map: AdminMapSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
},
|
|
Versions: make([]AdminMapVersion, 0, len(versions)),
|
|
}
|
|
for _, version := range versions {
|
|
view := AdminMapVersion{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
MapmetaURL: version.MapmetaURL,
|
|
TilesRootURL: version.TilesRootURL,
|
|
PublishedAssetRoot: version.PublishedAssetRoot,
|
|
Bounds: decodeJSONMap(version.BoundsJSON),
|
|
Metadata: decodeJSONMap(version.MetadataJSON),
|
|
}
|
|
result.Versions = append(result.Versions, view)
|
|
if item.CurrentVersionID != nil && *item.CurrentVersionID == version.ID {
|
|
result.Map.CurrentVersion = &AdminMapVersionBrief{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
}
|
|
result.Map.CurrentVersionID = &view.ID
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) CreateMapVersion(ctx context.Context, mapPublicID string, input CreateAdminMapVersionInput) (*AdminMapVersion, error) {
|
|
mapItem, err := s.store.GetResourceMapByPublicID(ctx, strings.TrimSpace(mapPublicID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mapItem == nil {
|
|
return nil, apperr.New(http.StatusNotFound, "map_not_found", "map not found")
|
|
}
|
|
input.VersionCode = strings.TrimSpace(input.VersionCode)
|
|
input.MapmetaURL = strings.TrimSpace(input.MapmetaURL)
|
|
input.TilesRootURL = strings.TrimSpace(input.TilesRootURL)
|
|
if input.VersionCode == "" || input.MapmetaURL == "" || input.TilesRootURL == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "versionCode, mapmetaUrl and tilesRootUrl are required")
|
|
}
|
|
|
|
publicID, err := security.GeneratePublicID("mapv")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx, err := s.store.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
version, err := s.store.CreateResourceMapVersion(ctx, tx, postgres.CreateResourceMapVersionParams{
|
|
PublicID: publicID,
|
|
MapID: mapItem.ID,
|
|
VersionCode: input.VersionCode,
|
|
Status: normalizeVersionStatus(input.Status),
|
|
MapmetaURL: input.MapmetaURL,
|
|
TilesRootURL: input.TilesRootURL,
|
|
PublishedAssetRoot: trimStringPtr(input.PublishedAssetRoot),
|
|
BoundsJSON: input.Bounds,
|
|
MetadataJSON: input.Metadata,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if input.SetAsCurrent {
|
|
if err := s.store.SetResourceMapCurrentVersion(ctx, tx, mapItem.ID, version.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminMapVersion{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
MapmetaURL: version.MapmetaURL,
|
|
TilesRootURL: version.TilesRootURL,
|
|
PublishedAssetRoot: version.PublishedAssetRoot,
|
|
Bounds: decodeJSONMap(version.BoundsJSON),
|
|
Metadata: decodeJSONMap(version.MetadataJSON),
|
|
}, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) ListPlayfields(ctx context.Context, limit int) ([]AdminPlayfieldSummary, error) {
|
|
items, err := s.store.ListResourcePlayfields(ctx, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := make([]AdminPlayfieldSummary, 0, len(items))
|
|
for _, item := range items {
|
|
results = append(results, AdminPlayfieldSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Kind: item.Kind,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
})
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) CreatePlayfield(ctx context.Context, input CreateAdminPlayfieldInput) (*AdminPlayfieldSummary, error) {
|
|
input.Code = strings.TrimSpace(input.Code)
|
|
input.Name = strings.TrimSpace(input.Name)
|
|
kind := strings.TrimSpace(input.Kind)
|
|
if kind == "" {
|
|
kind = "course"
|
|
}
|
|
if input.Code == "" || input.Name == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
|
|
}
|
|
publicID, err := security.GeneratePublicID("pf")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx, err := s.store.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
item, err := s.store.CreateResourcePlayfield(ctx, tx, postgres.CreateResourcePlayfieldParams{
|
|
PublicID: publicID,
|
|
Code: input.Code,
|
|
Name: input.Name,
|
|
Kind: kind,
|
|
Status: normalizeCatalogStatus(input.Status),
|
|
Description: trimStringPtr(input.Description),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminPlayfieldSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Kind: item.Kind,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
}, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) GetPlayfieldDetail(ctx context.Context, publicID string) (*AdminPlayfieldDetail, error) {
|
|
item, err := s.store.GetResourcePlayfieldByPublicID(ctx, strings.TrimSpace(publicID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if item == nil {
|
|
return nil, apperr.New(http.StatusNotFound, "playfield_not_found", "playfield not found")
|
|
}
|
|
versions, err := s.store.ListResourcePlayfieldVersions(ctx, item.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := &AdminPlayfieldDetail{
|
|
Playfield: AdminPlayfieldSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Kind: item.Kind,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
},
|
|
Versions: make([]AdminPlayfieldVersion, 0, len(versions)),
|
|
}
|
|
for _, version := range versions {
|
|
view := AdminPlayfieldVersion{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
SourceType: version.SourceType,
|
|
SourceURL: version.SourceURL,
|
|
PublishedAssetRoot: version.PublishedAssetRoot,
|
|
ControlCount: version.ControlCount,
|
|
Bounds: decodeJSONMap(version.BoundsJSON),
|
|
Metadata: decodeJSONMap(version.MetadataJSON),
|
|
}
|
|
result.Versions = append(result.Versions, view)
|
|
if item.CurrentVersionID != nil && *item.CurrentVersionID == version.ID {
|
|
result.Playfield.CurrentVersion = &AdminPlayfieldVersionBrief{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
SourceType: version.SourceType,
|
|
}
|
|
result.Playfield.CurrentVersionID = &view.ID
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) CreatePlayfieldVersion(ctx context.Context, publicID string, input CreateAdminPlayfieldVersionInput) (*AdminPlayfieldVersion, error) {
|
|
item, err := s.store.GetResourcePlayfieldByPublicID(ctx, strings.TrimSpace(publicID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if item == nil {
|
|
return nil, apperr.New(http.StatusNotFound, "playfield_not_found", "playfield not found")
|
|
}
|
|
input.VersionCode = strings.TrimSpace(input.VersionCode)
|
|
input.SourceType = strings.TrimSpace(input.SourceType)
|
|
input.SourceURL = strings.TrimSpace(input.SourceURL)
|
|
if input.VersionCode == "" || input.SourceType == "" || input.SourceURL == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "versionCode, sourceType and sourceUrl are required")
|
|
}
|
|
publicVersionID, err := security.GeneratePublicID("pfv")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx, err := s.store.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
version, err := s.store.CreateResourcePlayfieldVersion(ctx, tx, postgres.CreateResourcePlayfieldVersionParams{
|
|
PublicID: publicVersionID,
|
|
PlayfieldID: item.ID,
|
|
VersionCode: input.VersionCode,
|
|
Status: normalizeVersionStatus(input.Status),
|
|
SourceType: input.SourceType,
|
|
SourceURL: input.SourceURL,
|
|
PublishedAssetRoot: trimStringPtr(input.PublishedAssetRoot),
|
|
ControlCount: input.ControlCount,
|
|
BoundsJSON: input.Bounds,
|
|
MetadataJSON: input.Metadata,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if input.SetAsCurrent {
|
|
if err := s.store.SetResourcePlayfieldCurrentVersion(ctx, tx, item.ID, version.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminPlayfieldVersion{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
SourceType: version.SourceType,
|
|
SourceURL: version.SourceURL,
|
|
PublishedAssetRoot: version.PublishedAssetRoot,
|
|
ControlCount: version.ControlCount,
|
|
Bounds: decodeJSONMap(version.BoundsJSON),
|
|
Metadata: decodeJSONMap(version.MetadataJSON),
|
|
}, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) ListResourcePacks(ctx context.Context, limit int) ([]AdminResourcePackSummary, error) {
|
|
items, err := s.store.ListResourcePacks(ctx, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := make([]AdminResourcePackSummary, 0, len(items))
|
|
for _, item := range items {
|
|
results = append(results, AdminResourcePackSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
})
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) CreateResourcePack(ctx context.Context, input CreateAdminResourcePackInput) (*AdminResourcePackSummary, error) {
|
|
input.Code = strings.TrimSpace(input.Code)
|
|
input.Name = strings.TrimSpace(input.Name)
|
|
if input.Code == "" || input.Name == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
|
|
}
|
|
publicID, err := security.GeneratePublicID("rp")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx, err := s.store.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
item, err := s.store.CreateResourcePack(ctx, tx, postgres.CreateResourcePackParams{
|
|
PublicID: publicID,
|
|
Code: input.Code,
|
|
Name: input.Name,
|
|
Status: normalizeCatalogStatus(input.Status),
|
|
Description: trimStringPtr(input.Description),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminResourcePackSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
}, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) GetResourcePackDetail(ctx context.Context, publicID string) (*AdminResourcePackDetail, error) {
|
|
item, err := s.store.GetResourcePackByPublicID(ctx, strings.TrimSpace(publicID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if item == nil {
|
|
return nil, apperr.New(http.StatusNotFound, "resource_pack_not_found", "resource pack not found")
|
|
}
|
|
versions, err := s.store.ListResourcePackVersions(ctx, item.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := &AdminResourcePackDetail{
|
|
ResourcePack: AdminResourcePackSummary{
|
|
ID: item.PublicID,
|
|
Code: item.Code,
|
|
Name: item.Name,
|
|
Status: item.Status,
|
|
Description: item.Description,
|
|
CurrentVersionID: item.CurrentVersionID,
|
|
},
|
|
Versions: make([]AdminResourcePackVersion, 0, len(versions)),
|
|
}
|
|
for _, version := range versions {
|
|
view := AdminResourcePackVersion{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
ContentEntryURL: version.ContentEntryURL,
|
|
AudioRootURL: version.AudioRootURL,
|
|
ThemeProfileCode: version.ThemeProfileCode,
|
|
PublishedAssetRoot: version.PublishedAssetRoot,
|
|
Metadata: decodeJSONMap(version.MetadataJSON),
|
|
}
|
|
result.Versions = append(result.Versions, view)
|
|
if item.CurrentVersionID != nil && *item.CurrentVersionID == version.ID {
|
|
result.ResourcePack.CurrentVersion = &AdminResourcePackVersionBrief{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
}
|
|
result.ResourcePack.CurrentVersionID = &view.ID
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *AdminResourceService) CreateResourcePackVersion(ctx context.Context, publicID string, input CreateAdminResourcePackVersionInput) (*AdminResourcePackVersion, error) {
|
|
item, err := s.store.GetResourcePackByPublicID(ctx, strings.TrimSpace(publicID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if item == nil {
|
|
return nil, apperr.New(http.StatusNotFound, "resource_pack_not_found", "resource pack not found")
|
|
}
|
|
input.VersionCode = strings.TrimSpace(input.VersionCode)
|
|
if input.VersionCode == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "versionCode is required")
|
|
}
|
|
publicVersionID, err := security.GeneratePublicID("rpv")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx, err := s.store.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
version, err := s.store.CreateResourcePackVersion(ctx, tx, postgres.CreateResourcePackVersionParams{
|
|
PublicID: publicVersionID,
|
|
ResourcePackID: item.ID,
|
|
VersionCode: input.VersionCode,
|
|
Status: normalizeVersionStatus(input.Status),
|
|
ContentEntryURL: trimStringPtr(input.ContentEntryURL),
|
|
AudioRootURL: trimStringPtr(input.AudioRootURL),
|
|
ThemeProfileCode: trimStringPtr(input.ThemeProfileCode),
|
|
PublishedAssetRoot: trimStringPtr(input.PublishedAssetRoot),
|
|
MetadataJSON: input.Metadata,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if input.SetAsCurrent {
|
|
if err := s.store.SetResourcePackCurrentVersion(ctx, tx, item.ID, version.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminResourcePackVersion{
|
|
ID: version.PublicID,
|
|
VersionCode: version.VersionCode,
|
|
Status: version.Status,
|
|
ContentEntryURL: version.ContentEntryURL,
|
|
AudioRootURL: version.AudioRootURL,
|
|
ThemeProfileCode: version.ThemeProfileCode,
|
|
PublishedAssetRoot: version.PublishedAssetRoot,
|
|
Metadata: decodeJSONMap(version.MetadataJSON),
|
|
}, nil
|
|
}
|
|
|
|
func normalizeCatalogStatus(value string) string {
|
|
switch strings.TrimSpace(value) {
|
|
case "active":
|
|
return "active"
|
|
case "disabled":
|
|
return "disabled"
|
|
case "archived":
|
|
return "archived"
|
|
default:
|
|
return "draft"
|
|
}
|
|
}
|
|
|
|
func normalizeVersionStatus(value string) string {
|
|
switch strings.TrimSpace(value) {
|
|
case "active":
|
|
return "active"
|
|
case "archived":
|
|
return "archived"
|
|
default:
|
|
return "draft"
|
|
}
|
|
}
|
|
|
|
func trimStringPtr(value *string) *string {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
trimmed := strings.TrimSpace(*value)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
return &trimmed
|
|
}
|
|
|
|
func decodeJSONMap(raw json.RawMessage) map[string]any {
|
|
if len(raw) == 0 {
|
|
return nil
|
|
}
|
|
result := map[string]any{}
|
|
if err := json.Unmarshal(raw, &result); err != nil || len(result) == 0 {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|