同步前后端联调与文档更新
This commit is contained in:
719
backend/internal/service/admin_resource_service.go
Normal file
719
backend/internal/service/admin_resource_service.go
Normal file
@@ -0,0 +1,719 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user