| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699 |
- package service
- import (
- "context"
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "cmr-backend/internal/apperr"
- "cmr-backend/internal/platform/assets"
- "cmr-backend/internal/platform/security"
- "cmr-backend/internal/store/postgres"
- )
- type ConfigService struct {
- store *postgres.Store
- localEventDir string
- assetBaseURL string
- publisher *assets.OSSUtilPublisher
- }
- type ConfigPipelineSummary struct {
- SourceTable string `json:"sourceTable"`
- BuildTable string `json:"buildTable"`
- ReleaseAssetsTable string `json:"releaseAssetsTable"`
- }
- type LocalEventFile struct {
- FileName string `json:"fileName"`
- FullPath string `json:"fullPath"`
- }
- type EventConfigSourceView struct {
- ID string `json:"id"`
- EventID string `json:"eventId"`
- SourceVersionNo int `json:"sourceVersionNo"`
- SourceKind string `json:"sourceKind"`
- SchemaID string `json:"schemaId"`
- SchemaVersion string `json:"schemaVersion"`
- Status string `json:"status"`
- Notes *string `json:"notes,omitempty"`
- Source map[string]any `json:"source"`
- }
- type EventConfigBuildView struct {
- ID string `json:"id"`
- EventID string `json:"eventId"`
- SourceID string `json:"sourceId"`
- BuildNo int `json:"buildNo"`
- BuildStatus string `json:"buildStatus"`
- BuildLog *string `json:"buildLog,omitempty"`
- Manifest map[string]any `json:"manifest"`
- AssetIndex []map[string]any `json:"assetIndex"`
- }
- type PublishedReleaseView struct {
- EventID string `json:"eventId"`
- Release ResolvedReleaseView `json:"release"`
- ReleaseNo int `json:"releaseNo"`
- PublishedAt string `json:"publishedAt"`
- }
- type ImportLocalEventConfigInput struct {
- EventPublicID string
- FileName string `json:"fileName"`
- Notes *string `json:"notes,omitempty"`
- }
- type BuildPreviewInput struct {
- SourceID string `json:"sourceId"`
- }
- type PublishBuildInput struct {
- BuildID string `json:"buildId"`
- }
- func NewConfigService(store *postgres.Store, localEventDir, assetBaseURL string, publisher *assets.OSSUtilPublisher) *ConfigService {
- return &ConfigService{
- store: store,
- localEventDir: localEventDir,
- assetBaseURL: strings.TrimRight(assetBaseURL, "/"),
- publisher: publisher,
- }
- }
- func (s *ConfigService) PipelineSummary() ConfigPipelineSummary {
- return ConfigPipelineSummary{
- SourceTable: "event_config_sources",
- BuildTable: "event_config_builds",
- ReleaseAssetsTable: "event_release_assets",
- }
- }
- func (s *ConfigService) ListLocalEventFiles() ([]LocalEventFile, error) {
- dir, err := filepath.Abs(s.localEventDir)
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "config_dir_invalid", "failed to resolve local event directory")
- }
- entries, err := os.ReadDir(dir)
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "config_dir_unavailable", "failed to read local event directory")
- }
- files := make([]LocalEventFile, 0)
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
- if strings.ToLower(filepath.Ext(entry.Name())) != ".json" {
- continue
- }
- files = append(files, LocalEventFile{
- FileName: entry.Name(),
- FullPath: filepath.Join(dir, entry.Name()),
- })
- }
- sort.Slice(files, func(i, j int) bool {
- return files[i].FileName < files[j].FileName
- })
- return files, nil
- }
- func (s *ConfigService) ListEventConfigSources(ctx context.Context, eventPublicID string, limit int) ([]EventConfigSourceView, error) {
- event, err := s.requireEvent(ctx, eventPublicID)
- if err != nil {
- return nil, err
- }
- items, err := s.store.ListEventConfigSourcesByEventID(ctx, event.ID, limit)
- if err != nil {
- return nil, err
- }
- results := make([]EventConfigSourceView, 0, len(items))
- for i := range items {
- view, err := buildEventConfigSourceView(&items[i], event.PublicID)
- if err != nil {
- return nil, err
- }
- results = append(results, *view)
- }
- return results, nil
- }
- func (s *ConfigService) GetEventConfigSource(ctx context.Context, sourceID string) (*EventConfigSourceView, error) {
- record, err := s.store.GetEventConfigSourceByID(ctx, strings.TrimSpace(sourceID))
- if err != nil {
- return nil, err
- }
- if record == nil {
- return nil, apperr.New(http.StatusNotFound, "config_source_not_found", "config source not found")
- }
- return buildEventConfigSourceView(record, "")
- }
- func (s *ConfigService) GetEventConfigBuild(ctx context.Context, buildID string) (*EventConfigBuildView, error) {
- record, err := s.store.GetEventConfigBuildByID(ctx, strings.TrimSpace(buildID))
- if err != nil {
- return nil, err
- }
- if record == nil {
- return nil, apperr.New(http.StatusNotFound, "config_build_not_found", "config build not found")
- }
- return buildEventConfigBuildView(record)
- }
- func (s *ConfigService) ImportLocalEventConfig(ctx context.Context, input ImportLocalEventConfigInput) (*EventConfigSourceView, error) {
- event, err := s.requireEvent(ctx, input.EventPublicID)
- if err != nil {
- return nil, err
- }
- fileName := strings.TrimSpace(filepath.Base(input.FileName))
- if fileName == "" || strings.Contains(fileName, "..") || strings.ToLower(filepath.Ext(fileName)) != ".json" {
- return nil, apperr.New(http.StatusBadRequest, "invalid_params", "valid json fileName is required")
- }
- dir, err := filepath.Abs(s.localEventDir)
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "config_dir_invalid", "failed to resolve local event directory")
- }
- path := filepath.Join(dir, fileName)
- raw, err := os.ReadFile(path)
- if err != nil {
- return nil, apperr.New(http.StatusNotFound, "config_file_not_found", "local config file not found")
- }
- source := map[string]any{}
- if err := json.Unmarshal(raw, &source); err != nil {
- return nil, apperr.New(http.StatusBadRequest, "config_json_invalid", "local config file is not valid json")
- }
- if err := validateSourceConfig(source); err != nil {
- return nil, err
- }
- nextVersion, err := s.store.NextEventConfigSourceVersion(ctx, event.ID)
- if err != nil {
- return nil, err
- }
- note := input.Notes
- if note == nil || strings.TrimSpace(*note) == "" {
- defaultNote := "imported from local event file: " + fileName
- note = &defaultNote
- }
- tx, err := s.store.Begin(ctx)
- if err != nil {
- return nil, err
- }
- defer tx.Rollback(ctx)
- record, err := s.store.UpsertEventConfigSource(ctx, tx, postgres.UpsertEventConfigSourceParams{
- EventID: event.ID,
- SourceVersionNo: nextVersion,
- SourceKind: "event_bundle",
- SchemaID: "event-source",
- SchemaVersion: resolveSchemaVersion(source),
- Status: "active",
- Source: source,
- Notes: note,
- })
- if err != nil {
- return nil, err
- }
- if err := tx.Commit(ctx); err != nil {
- return nil, err
- }
- return buildEventConfigSourceView(record, event.PublicID)
- }
- func (s *ConfigService) BuildPreview(ctx context.Context, input BuildPreviewInput) (*EventConfigBuildView, error) {
- sourceRecord, err := s.store.GetEventConfigSourceByID(ctx, strings.TrimSpace(input.SourceID))
- if err != nil {
- return nil, err
- }
- if sourceRecord == nil {
- return nil, apperr.New(http.StatusNotFound, "config_source_not_found", "config source not found")
- }
- source, err := decodeJSONObject(sourceRecord.SourceJSON)
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "config_source_invalid", "stored source config is invalid")
- }
- if err := validateSourceConfig(source); err != nil {
- return nil, err
- }
- buildNo, err := s.store.NextEventConfigBuildNo(ctx, sourceRecord.EventID)
- if err != nil {
- return nil, err
- }
- previewReleaseID := fmt.Sprintf("preview_%d", buildNo)
- manifest := s.buildPreviewManifest(source, previewReleaseID)
- assetIndex := s.buildAssetIndex(manifest)
- buildLog := "preview build generated from source " + sourceRecord.ID
- tx, err := s.store.Begin(ctx)
- if err != nil {
- return nil, err
- }
- defer tx.Rollback(ctx)
- record, err := s.store.UpsertEventConfigBuild(ctx, tx, postgres.UpsertEventConfigBuildParams{
- EventID: sourceRecord.EventID,
- SourceID: sourceRecord.ID,
- BuildNo: buildNo,
- BuildStatus: "success",
- BuildLog: &buildLog,
- Manifest: manifest,
- AssetIndex: assetIndex,
- })
- if err != nil {
- return nil, err
- }
- if err := tx.Commit(ctx); err != nil {
- return nil, err
- }
- return buildEventConfigBuildView(record)
- }
- func (s *ConfigService) PublishBuild(ctx context.Context, input PublishBuildInput) (*PublishedReleaseView, error) {
- buildRecord, err := s.store.GetEventConfigBuildByID(ctx, strings.TrimSpace(input.BuildID))
- if err != nil {
- return nil, err
- }
- if buildRecord == nil {
- return nil, apperr.New(http.StatusNotFound, "config_build_not_found", "config build not found")
- }
- if buildRecord.BuildStatus != "success" {
- return nil, apperr.New(http.StatusConflict, "config_build_not_publishable", "config build is not publishable")
- }
- event, err := s.store.GetEventByID(ctx, buildRecord.EventID)
- if err != nil {
- return nil, err
- }
- if event == nil {
- return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
- }
- manifest, err := decodeJSONObject(buildRecord.ManifestJSON)
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "config_build_invalid", "stored build manifest is invalid")
- }
- assetIndex, err := decodeJSONArray(buildRecord.AssetIndexJSON)
- if err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "config_build_invalid", "stored build asset index is invalid")
- }
- releaseNo, err := s.store.NextEventReleaseNo(ctx, event.ID)
- if err != nil {
- return nil, err
- }
- releasePublicID, err := security.GeneratePublicID("rel")
- if err != nil {
- return nil, err
- }
- configLabel := deriveConfigLabel(event, manifest, releaseNo)
- manifestURL := fmt.Sprintf("%s/event/releases/%s/%s/manifest.json", s.assetBaseURL, event.PublicID, releasePublicID)
- assetIndexURL := fmt.Sprintf("%s/event/releases/%s/%s/asset-index.json", s.assetBaseURL, event.PublicID, releasePublicID)
- checksum := security.HashText(buildRecord.ManifestJSON)
- routeCode := deriveRouteCode(manifest)
- if s.publisher == nil || !s.publisher.Enabled() {
- return nil, apperr.New(http.StatusInternalServerError, "asset_publish_unavailable", "asset publisher is not configured")
- }
- if err := s.publisher.UploadJSON(ctx, manifestURL, []byte(buildRecord.ManifestJSON)); err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "asset_publish_failed", "failed to upload manifest: "+err.Error())
- }
- if err := s.publisher.UploadJSON(ctx, assetIndexURL, []byte(buildRecord.AssetIndexJSON)); err != nil {
- return nil, apperr.New(http.StatusInternalServerError, "asset_publish_failed", "failed to upload asset index: "+err.Error())
- }
- tx, err := s.store.Begin(ctx)
- if err != nil {
- return nil, err
- }
- defer tx.Rollback(ctx)
- releaseRecord, err := s.store.CreateEventRelease(ctx, tx, postgres.CreateEventReleaseParams{
- PublicID: releasePublicID,
- EventID: event.ID,
- ReleaseNo: releaseNo,
- ConfigLabel: configLabel,
- ManifestURL: manifestURL,
- ManifestChecksum: &checksum,
- RouteCode: routeCode,
- BuildID: &buildRecord.ID,
- Status: "published",
- PayloadJSON: buildRecord.ManifestJSON,
- })
- if err != nil {
- return nil, err
- }
- if err := s.store.ReplaceEventReleaseAssets(ctx, tx, releaseRecord.ID, s.mapBuildAssetsToReleaseAssets(releaseRecord.ID, manifestURL, assetIndexURL, &checksum, assetIndex)); err != nil {
- return nil, err
- }
- if err := s.store.SetCurrentEventRelease(ctx, tx, event.ID, releaseRecord.ID); err != nil {
- return nil, err
- }
- if err := tx.Commit(ctx); err != nil {
- return nil, err
- }
- return &PublishedReleaseView{
- EventID: event.PublicID,
- Release: ResolvedReleaseView{
- LaunchMode: LaunchModeManifestRelease,
- Source: LaunchSourceEventCurrentRelease,
- EventID: event.PublicID,
- ReleaseID: releaseRecord.PublicID,
- ConfigLabel: releaseRecord.ConfigLabel,
- ManifestURL: releaseRecord.ManifestURL,
- ManifestChecksumSha256: releaseRecord.ManifestChecksum,
- RouteCode: releaseRecord.RouteCode,
- },
- ReleaseNo: releaseRecord.ReleaseNo,
- PublishedAt: releaseRecord.PublishedAt.Format(timeRFC3339),
- }, nil
- }
- func (s *ConfigService) requireEvent(ctx context.Context, eventPublicID string) (*postgres.Event, error) {
- eventPublicID = strings.TrimSpace(eventPublicID)
- if eventPublicID == "" {
- return nil, apperr.New(http.StatusBadRequest, "invalid_params", "event id is required")
- }
- event, err := s.store.GetEventByPublicID(ctx, eventPublicID)
- if err != nil {
- return nil, err
- }
- if event == nil {
- return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
- }
- return event, nil
- }
- func buildEventConfigSourceView(record *postgres.EventConfigSource, eventPublicID string) (*EventConfigSourceView, error) {
- source, err := decodeJSONObject(record.SourceJSON)
- if err != nil {
- return nil, err
- }
- view := &EventConfigSourceView{
- ID: record.ID,
- EventID: eventPublicID,
- SourceVersionNo: record.SourceVersionNo,
- SourceKind: record.SourceKind,
- SchemaID: record.SchemaID,
- SchemaVersion: record.SchemaVersion,
- Status: record.Status,
- Notes: record.Notes,
- Source: source,
- }
- return view, nil
- }
- func buildEventConfigBuildView(record *postgres.EventConfigBuild) (*EventConfigBuildView, error) {
- manifest, err := decodeJSONObject(record.ManifestJSON)
- if err != nil {
- return nil, err
- }
- assetIndex, err := decodeJSONArray(record.AssetIndexJSON)
- if err != nil {
- return nil, err
- }
- return &EventConfigBuildView{
- ID: record.ID,
- EventID: record.EventID,
- SourceID: record.SourceID,
- BuildNo: record.BuildNo,
- BuildStatus: record.BuildStatus,
- BuildLog: record.BuildLog,
- Manifest: manifest,
- AssetIndex: assetIndex,
- }, nil
- }
- func validateSourceConfig(source map[string]any) error {
- requiredMap := func(parent map[string]any, key string) (map[string]any, error) {
- value, ok := parent[key]
- if !ok {
- return nil, apperr.New(http.StatusBadRequest, "config_missing_field", "missing required field: "+key)
- }
- asMap, ok := value.(map[string]any)
- if !ok {
- return nil, apperr.New(http.StatusBadRequest, "config_invalid_field", "invalid object field: "+key)
- }
- return asMap, nil
- }
- requiredString := func(parent map[string]any, key string) error {
- value, ok := parent[key]
- if !ok {
- return apperr.New(http.StatusBadRequest, "config_missing_field", "missing required field: "+key)
- }
- text, ok := value.(string)
- if !ok || strings.TrimSpace(text) == "" {
- return apperr.New(http.StatusBadRequest, "config_invalid_field", "invalid string field: "+key)
- }
- return nil
- }
- if err := requiredString(source, "schemaVersion"); err != nil {
- return err
- }
- app, err := requiredMap(source, "app")
- if err != nil {
- return err
- }
- if err := requiredString(app, "id"); err != nil {
- return err
- }
- if err := requiredString(app, "title"); err != nil {
- return err
- }
- m, err := requiredMap(source, "map")
- if err != nil {
- return err
- }
- if err := requiredString(m, "tiles"); err != nil {
- return err
- }
- if err := requiredString(m, "mapmeta"); err != nil {
- return err
- }
- playfield, err := requiredMap(source, "playfield")
- if err != nil {
- return err
- }
- if err := requiredString(playfield, "kind"); err != nil {
- return err
- }
- playfieldSource, err := requiredMap(playfield, "source")
- if err != nil {
- return err
- }
- if err := requiredString(playfieldSource, "type"); err != nil {
- return err
- }
- if err := requiredString(playfieldSource, "url"); err != nil {
- return err
- }
- game, err := requiredMap(source, "game")
- if err != nil {
- return err
- }
- if err := requiredString(game, "mode"); err != nil {
- return err
- }
- return nil
- }
- func resolveSchemaVersion(source map[string]any) string {
- if value, ok := source["schemaVersion"].(string); ok && strings.TrimSpace(value) != "" {
- return value
- }
- return "1"
- }
- func (s *ConfigService) buildPreviewManifest(source map[string]any, previewReleaseID string) map[string]any {
- manifest := cloneJSONObject(source)
- manifest["releaseId"] = previewReleaseID
- manifest["preview"] = true
- manifest["assetBaseUrl"] = s.assetBaseURL
- if version, ok := manifest["version"]; !ok || version == "" {
- manifest["version"] = "preview"
- }
- if m, ok := manifest["map"].(map[string]any); ok {
- if tiles, ok := m["tiles"].(string); ok {
- m["tiles"] = s.normalizeAssetURL(tiles)
- }
- if meta, ok := m["mapmeta"].(string); ok {
- m["mapmeta"] = s.normalizeAssetURL(meta)
- }
- }
- if playfield, ok := manifest["playfield"].(map[string]any); ok {
- if src, ok := playfield["source"].(map[string]any); ok {
- if url, ok := src["url"].(string); ok {
- src["url"] = s.normalizeAssetURL(url)
- }
- }
- }
- if assets, ok := manifest["assets"].(map[string]any); ok {
- for key, value := range assets {
- if text, ok := value.(string); ok {
- assets[key] = s.normalizeAssetURL(text)
- }
- }
- }
- return manifest
- }
- func (s *ConfigService) buildAssetIndex(manifest map[string]any) []map[string]any {
- var assets []map[string]any
- if m, ok := manifest["map"].(map[string]any); ok {
- if tiles, ok := m["tiles"].(string); ok {
- assets = append(assets, map[string]any{"assetType": "tiles", "assetKey": "tiles-root", "assetUrl": tiles})
- }
- if meta, ok := m["mapmeta"].(string); ok {
- assets = append(assets, map[string]any{"assetType": "mapmeta", "assetKey": "mapmeta", "assetUrl": meta})
- }
- }
- if playfield, ok := manifest["playfield"].(map[string]any); ok {
- if src, ok := playfield["source"].(map[string]any); ok {
- if url, ok := src["url"].(string); ok {
- assets = append(assets, map[string]any{"assetType": "playfield", "assetKey": "playfield-source", "assetUrl": url})
- }
- }
- }
- if rawAssets, ok := manifest["assets"].(map[string]any); ok {
- keys := make([]string, 0, len(rawAssets))
- for key := range rawAssets {
- keys = append(keys, key)
- }
- sort.Strings(keys)
- for _, key := range keys {
- if url, ok := rawAssets[key].(string); ok {
- assets = append(assets, map[string]any{"assetType": "other", "assetKey": key, "assetUrl": url})
- }
- }
- }
- return assets
- }
- func (s *ConfigService) normalizeAssetURL(value string) string {
- value = strings.TrimSpace(value)
- if value == "" {
- return value
- }
- if strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") {
- return value
- }
- trimmed := strings.TrimPrefix(value, "../")
- trimmed = strings.TrimPrefix(trimmed, "./")
- trimmed = strings.TrimLeft(trimmed, "/")
- return s.assetBaseURL + "/" + trimmed
- }
- func cloneJSONObject(source map[string]any) map[string]any {
- raw, _ := json.Marshal(source)
- cloned := map[string]any{}
- _ = json.Unmarshal(raw, &cloned)
- return cloned
- }
- func decodeJSONObject(raw string) (map[string]any, error) {
- result := map[string]any{}
- if err := json.Unmarshal([]byte(raw), &result); err != nil {
- return nil, err
- }
- return result, nil
- }
- func decodeJSONArray(raw string) ([]map[string]any, error) {
- if strings.TrimSpace(raw) == "" {
- return []map[string]any{}, nil
- }
- var result []map[string]any
- if err := json.Unmarshal([]byte(raw), &result); err != nil {
- return nil, err
- }
- return result, nil
- }
- func deriveConfigLabel(event *postgres.Event, manifest map[string]any, releaseNo int) string {
- if app, ok := manifest["app"].(map[string]any); ok {
- if title, ok := app["title"].(string); ok && strings.TrimSpace(title) != "" {
- return fmt.Sprintf("%s Release %d", strings.TrimSpace(title), releaseNo)
- }
- }
- if event != nil && strings.TrimSpace(event.DisplayName) != "" {
- return fmt.Sprintf("%s Release %d", event.DisplayName, releaseNo)
- }
- return fmt.Sprintf("Release %d", releaseNo)
- }
- func deriveRouteCode(manifest map[string]any) *string {
- if playfield, ok := manifest["playfield"].(map[string]any); ok {
- if value, ok := playfield["kind"].(string); ok && strings.TrimSpace(value) != "" {
- route := strings.TrimSpace(value)
- return &route
- }
- }
- return nil
- }
- func (s *ConfigService) mapBuildAssetsToReleaseAssets(eventReleaseID, manifestURL, assetIndexURL string, checksum *string, assetIndex []map[string]any) []postgres.UpsertEventReleaseAssetParams {
- assets := []postgres.UpsertEventReleaseAssetParams{
- {
- EventReleaseID: eventReleaseID,
- AssetType: "manifest",
- AssetKey: "manifest",
- AssetURL: manifestURL,
- Checksum: checksum,
- Meta: map[string]any{"source": "published-build"},
- },
- {
- EventReleaseID: eventReleaseID,
- AssetType: "other",
- AssetKey: "asset-index",
- AssetURL: assetIndexURL,
- Meta: map[string]any{"source": "published-build"},
- },
- }
- for _, asset := range assetIndex {
- assetType, _ := asset["assetType"].(string)
- assetKey, _ := asset["assetKey"].(string)
- assetURL, _ := asset["assetUrl"].(string)
- if strings.TrimSpace(assetType) == "" || strings.TrimSpace(assetKey) == "" || strings.TrimSpace(assetURL) == "" {
- continue
- }
- mappedType := assetType
- if mappedType != "manifest" && mappedType != "mapmeta" && mappedType != "tiles" && mappedType != "playfield" && mappedType != "content_html" && mappedType != "media" {
- mappedType = "other"
- }
- assets = append(assets, postgres.UpsertEventReleaseAssetParams{
- EventReleaseID: eventReleaseID,
- AssetType: mappedType,
- AssetKey: assetKey,
- AssetURL: assetURL,
- Meta: asset,
- })
- }
- return assets
- }
|