完善活动运营域与联调标准化
This commit is contained in:
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -27,18 +28,25 @@ type AdminEventSummary struct {
|
||||
}
|
||||
|
||||
type AdminEventReleaseRef struct {
|
||||
ID string `json:"id"`
|
||||
ConfigLabel *string `json:"configLabel,omitempty"`
|
||||
ManifestURL *string `json:"manifestUrl,omitempty"`
|
||||
ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
|
||||
RouteCode *string `json:"routeCode,omitempty"`
|
||||
ID string `json:"id"`
|
||||
ConfigLabel *string `json:"configLabel,omitempty"`
|
||||
ManifestURL *string `json:"manifestUrl,omitempty"`
|
||||
ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
|
||||
RouteCode *string `json:"routeCode,omitempty"`
|
||||
Presentation *PresentationSummaryView `json:"presentation,omitempty"`
|
||||
ContentBundle *ContentBundleSummaryView `json:"contentBundle,omitempty"`
|
||||
}
|
||||
|
||||
type AdminEventDetail struct {
|
||||
Event AdminEventSummary `json:"event"`
|
||||
LatestSource *EventConfigSourceView `json:"latestSource,omitempty"`
|
||||
SourceCount int `json:"sourceCount"`
|
||||
CurrentSource *AdminAssembledSource `json:"currentSource,omitempty"`
|
||||
Event AdminEventSummary `json:"event"`
|
||||
LatestSource *EventConfigSourceView `json:"latestSource,omitempty"`
|
||||
SourceCount int `json:"sourceCount"`
|
||||
CurrentSource *AdminAssembledSource `json:"currentSource,omitempty"`
|
||||
PresentationCount int `json:"presentationCount"`
|
||||
ContentBundleCount int `json:"contentBundleCount"`
|
||||
CurrentPresentation *PresentationSummaryView `json:"currentPresentation,omitempty"`
|
||||
CurrentContentBundle *ContentBundleSummaryView `json:"currentContentBundle,omitempty"`
|
||||
CurrentRuntime *RuntimeSummaryView `json:"currentRuntime,omitempty"`
|
||||
}
|
||||
|
||||
type CreateAdminEventInput struct {
|
||||
@@ -76,6 +84,84 @@ type SaveAdminEventSourceInput struct {
|
||||
Notes *string `json:"notes,omitempty"`
|
||||
}
|
||||
|
||||
type AdminEventPresentationView struct {
|
||||
ID string `json:"id"`
|
||||
EventID string `json:"eventId"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
PresentationType string `json:"presentationType"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
TemplateKey *string `json:"templateKey,omitempty"`
|
||||
Version *string `json:"version,omitempty"`
|
||||
SourceType *string `json:"sourceType,omitempty"`
|
||||
SchemaURL *string `json:"schemaUrl,omitempty"`
|
||||
Schema map[string]any `json:"schema"`
|
||||
}
|
||||
|
||||
type CreateAdminEventPresentationInput struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
PresentationType string `json:"presentationType"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
Schema map[string]any `json:"schema,omitempty"`
|
||||
}
|
||||
|
||||
type ImportAdminEventPresentationInput struct {
|
||||
Title string `json:"title"`
|
||||
TemplateKey string `json:"templateKey"`
|
||||
SourceType string `json:"sourceType"`
|
||||
SchemaURL string `json:"schemaUrl"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
}
|
||||
|
||||
type AdminContentBundleView struct {
|
||||
ID string `json:"id"`
|
||||
EventID string `json:"eventId"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
BundleType *string `json:"bundleType,omitempty"`
|
||||
Version *string `json:"version,omitempty"`
|
||||
SourceType *string `json:"sourceType,omitempty"`
|
||||
ManifestURL *string `json:"manifestUrl,omitempty"`
|
||||
AssetManifest any `json:"assetManifest,omitempty"`
|
||||
EntryURL *string `json:"entryUrl,omitempty"`
|
||||
AssetRootURL *string `json:"assetRootUrl,omitempty"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
type CreateAdminContentBundleInput struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
EntryURL *string `json:"entryUrl,omitempty"`
|
||||
AssetRootURL *string `json:"assetRootUrl,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ImportAdminContentBundleInput struct {
|
||||
Title string `json:"title"`
|
||||
BundleType string `json:"bundleType"`
|
||||
SourceType string `json:"sourceType"`
|
||||
ManifestURL string `json:"manifestUrl"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
IsDefault bool `json:"isDefault"`
|
||||
AssetManifest map[string]any `json:"assetManifest,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateAdminEventDefaultsInput struct {
|
||||
PresentationID *string `json:"presentationId,omitempty"`
|
||||
ContentBundleID *string `json:"contentBundleId,omitempty"`
|
||||
RuntimeBindingID *string `json:"runtimeBindingId,omitempty"`
|
||||
}
|
||||
|
||||
type AdminAssembledSource struct {
|
||||
Refs map[string]any `json:"refs"`
|
||||
Runtime map[string]any `json:"runtime"`
|
||||
@@ -240,10 +326,20 @@ func (s *AdminEventService) GetEventDetail(ctx context.Context, eventPublicID st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
presentations, err := s.store.ListEventPresentationsByEventID(ctx, record.ID, 200)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contentBundles, err := s.store.ListContentBundlesByEventID(ctx, record.ID, 200)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &AdminEventDetail{
|
||||
Event: buildAdminEventSummary(*record),
|
||||
SourceCount: len(allSources),
|
||||
Event: buildAdminEventSummary(*record),
|
||||
SourceCount: len(allSources),
|
||||
PresentationCount: len(presentations),
|
||||
ContentBundleCount: len(contentBundles),
|
||||
}
|
||||
if len(sources) > 0 {
|
||||
latest, err := buildEventConfigSourceView(&sources[0], record.PublicID)
|
||||
@@ -253,9 +349,427 @@ func (s *AdminEventService) GetEventDetail(ctx context.Context, eventPublicID st
|
||||
result.LatestSource = latest
|
||||
result.CurrentSource = buildAdminAssembledSource(latest.Source)
|
||||
}
|
||||
result.CurrentPresentation = buildPresentationSummaryFromEventRecord(record)
|
||||
result.CurrentContentBundle = buildContentBundleSummaryFromEventRecord(record)
|
||||
result.CurrentRuntime = buildRuntimeSummaryFromAdminEventRecord(record)
|
||||
if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, record.CurrentPresentationID); err != nil {
|
||||
return nil, err
|
||||
} else if enrichedPresentation != nil {
|
||||
result.CurrentPresentation = enrichedPresentation
|
||||
}
|
||||
if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, record.CurrentContentBundleID); err != nil {
|
||||
return nil, err
|
||||
} else if enrichedBundle != nil {
|
||||
result.CurrentContentBundle = enrichedBundle
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) ListEventPresentations(ctx context.Context, eventPublicID string, limit int) ([]AdminEventPresentationView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eventRecord == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
items, err := s.store.ListEventPresentationsByEventID(ctx, eventRecord.ID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]AdminEventPresentationView, 0, len(items))
|
||||
for _, item := range items {
|
||||
view, err := buildAdminEventPresentationView(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, view)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) CreateEventPresentation(ctx context.Context, eventPublicID string, input CreateAdminEventPresentationInput) (*AdminEventPresentationView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eventRecord == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
input.Code = strings.TrimSpace(input.Code)
|
||||
input.Name = strings.TrimSpace(input.Name)
|
||||
input.PresentationType = normalizePresentationType(input.PresentationType)
|
||||
if input.Code == "" || input.Name == "" {
|
||||
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "code and name are required")
|
||||
}
|
||||
publicID, err := security.GeneratePublicID("pres")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schema := input.Schema
|
||||
if schema == nil {
|
||||
schema = map[string]any{}
|
||||
}
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
record, err := s.store.CreateEventPresentation(ctx, tx, postgres.CreateEventPresentationParams{
|
||||
PublicID: publicID,
|
||||
EventID: eventRecord.ID,
|
||||
Code: input.Code,
|
||||
Name: input.Name,
|
||||
PresentationType: input.PresentationType,
|
||||
Status: normalizeEventCatalogStatus(input.Status),
|
||||
IsDefault: input.IsDefault,
|
||||
SchemaJSON: mustMarshalJSONObject(schema),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view, err := buildAdminEventPresentationView(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) ImportEventPresentation(ctx context.Context, eventPublicID string, input ImportAdminEventPresentationInput) (*AdminEventPresentationView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eventRecord == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
|
||||
input.Title = strings.TrimSpace(input.Title)
|
||||
input.TemplateKey = strings.TrimSpace(input.TemplateKey)
|
||||
input.SourceType = strings.TrimSpace(input.SourceType)
|
||||
input.SchemaURL = strings.TrimSpace(input.SchemaURL)
|
||||
input.Version = strings.TrimSpace(input.Version)
|
||||
if input.Title == "" || input.TemplateKey == "" || input.SourceType == "" || input.SchemaURL == "" || input.Version == "" {
|
||||
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "title, templateKey, sourceType, schemaUrl and version are required")
|
||||
}
|
||||
|
||||
publicID, err := security.GeneratePublicID("pres")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := generateImportedPresentationCode(input.Title, publicID)
|
||||
status := normalizeEventCatalogStatus(input.Status)
|
||||
if strings.TrimSpace(input.Status) == "" {
|
||||
status = "active"
|
||||
}
|
||||
schema := map[string]any{
|
||||
"templateKey": input.TemplateKey,
|
||||
"sourceType": input.SourceType,
|
||||
"schemaUrl": input.SchemaURL,
|
||||
"version": input.Version,
|
||||
}
|
||||
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
record, err := s.store.CreateEventPresentation(ctx, tx, postgres.CreateEventPresentationParams{
|
||||
PublicID: publicID,
|
||||
EventID: eventRecord.ID,
|
||||
Code: code,
|
||||
Name: input.Title,
|
||||
PresentationType: "generic",
|
||||
Status: status,
|
||||
IsDefault: input.IsDefault,
|
||||
SchemaJSON: mustMarshalJSONObject(schema),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view, err := buildAdminEventPresentationView(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) GetEventPresentation(ctx context.Context, presentationPublicID string) (*AdminEventPresentationView, error) {
|
||||
record, err := s.store.GetEventPresentationByPublicID(ctx, strings.TrimSpace(presentationPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "presentation_not_found", "presentation not found")
|
||||
}
|
||||
view, err := buildAdminEventPresentationView(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) ListContentBundles(ctx context.Context, eventPublicID string, limit int) ([]AdminContentBundleView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eventRecord == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
items, err := s.store.ListContentBundlesByEventID(ctx, eventRecord.ID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]AdminContentBundleView, 0, len(items))
|
||||
for _, item := range items {
|
||||
view, err := buildAdminContentBundleView(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, view)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) CreateContentBundle(ctx context.Context, eventPublicID string, input CreateAdminContentBundleInput) (*AdminContentBundleView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eventRecord == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
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("bundle")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata := input.Metadata
|
||||
if metadata == nil {
|
||||
metadata = map[string]any{}
|
||||
}
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
record, err := s.store.CreateContentBundle(ctx, tx, postgres.CreateContentBundleParams{
|
||||
PublicID: publicID,
|
||||
EventID: eventRecord.ID,
|
||||
Code: input.Code,
|
||||
Name: input.Name,
|
||||
Status: normalizeEventCatalogStatus(input.Status),
|
||||
IsDefault: input.IsDefault,
|
||||
EntryURL: trimStringPtr(input.EntryURL),
|
||||
AssetRootURL: trimStringPtr(input.AssetRootURL),
|
||||
MetadataJSON: mustMarshalJSONObject(metadata),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
view, err := buildAdminContentBundleView(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) ImportContentBundle(ctx context.Context, eventPublicID string, input ImportAdminContentBundleInput) (*AdminContentBundleView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eventRecord == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
|
||||
input.Title = strings.TrimSpace(input.Title)
|
||||
input.BundleType = strings.TrimSpace(input.BundleType)
|
||||
input.SourceType = strings.TrimSpace(input.SourceType)
|
||||
input.ManifestURL = strings.TrimSpace(input.ManifestURL)
|
||||
input.Version = strings.TrimSpace(input.Version)
|
||||
if input.Title == "" || input.BundleType == "" || input.SourceType == "" || input.ManifestURL == "" || input.Version == "" {
|
||||
return nil, apperr.New(http.StatusBadRequest, "invalid_params", "title, bundleType, sourceType, manifestUrl and version are required")
|
||||
}
|
||||
|
||||
publicID, err := security.GeneratePublicID("bundle")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := generateImportedBundleCode(input.Title, publicID)
|
||||
assetManifest := input.AssetManifest
|
||||
if assetManifest == nil {
|
||||
assetManifest = map[string]any{
|
||||
"manifestUrl": input.ManifestURL,
|
||||
"sourceType": input.SourceType,
|
||||
}
|
||||
}
|
||||
metadata := map[string]any{
|
||||
"bundleType": input.BundleType,
|
||||
"sourceType": input.SourceType,
|
||||
"manifestUrl": input.ManifestURL,
|
||||
"version": input.Version,
|
||||
"assetManifest": assetManifest,
|
||||
}
|
||||
status := normalizeEventCatalogStatus(input.Status)
|
||||
if strings.TrimSpace(input.Status) == "" {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
record, err := s.store.CreateContentBundle(ctx, tx, postgres.CreateContentBundleParams{
|
||||
PublicID: publicID,
|
||||
EventID: eventRecord.ID,
|
||||
Code: code,
|
||||
Name: input.Title,
|
||||
Status: status,
|
||||
IsDefault: input.IsDefault,
|
||||
EntryURL: nil,
|
||||
AssetRootURL: nil,
|
||||
MetadataJSON: mustMarshalJSONObject(metadata),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
view, err := buildAdminContentBundleView(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) GetContentBundle(ctx context.Context, contentBundlePublicID string) (*AdminContentBundleView, error) {
|
||||
record, err := s.store.GetContentBundleByPublicID(ctx, strings.TrimSpace(contentBundlePublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "content_bundle_not_found", "content bundle not found")
|
||||
}
|
||||
view, err := buildAdminContentBundleView(*record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *AdminEventService) UpdateEventDefaults(ctx context.Context, eventPublicID string, input UpdateAdminEventDefaultsInput) (*AdminEventDetail, error) {
|
||||
record, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
|
||||
}
|
||||
|
||||
var presentationID *string
|
||||
updatePresentation := false
|
||||
if input.PresentationID != nil {
|
||||
updatePresentation = true
|
||||
trimmed := strings.TrimSpace(*input.PresentationID)
|
||||
if trimmed != "" {
|
||||
presentation, err := s.store.GetEventPresentationByPublicID(ctx, trimmed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if presentation == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "presentation_not_found", "presentation not found")
|
||||
}
|
||||
if presentation.EventID != record.ID {
|
||||
return nil, apperr.New(http.StatusConflict, "presentation_not_belong_to_event", "presentation does not belong to event")
|
||||
}
|
||||
presentationID = &presentation.ID
|
||||
}
|
||||
}
|
||||
|
||||
var contentBundleID *string
|
||||
updateContent := false
|
||||
if input.ContentBundleID != nil {
|
||||
updateContent = true
|
||||
trimmed := strings.TrimSpace(*input.ContentBundleID)
|
||||
if trimmed != "" {
|
||||
contentBundle, err := s.store.GetContentBundleByPublicID(ctx, trimmed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if contentBundle == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "content_bundle_not_found", "content bundle not found")
|
||||
}
|
||||
if contentBundle.EventID != record.ID {
|
||||
return nil, apperr.New(http.StatusConflict, "content_bundle_not_belong_to_event", "content bundle does not belong to event")
|
||||
}
|
||||
contentBundleID = &contentBundle.ID
|
||||
}
|
||||
}
|
||||
|
||||
var runtimeBindingID *string
|
||||
updateRuntime := false
|
||||
if input.RuntimeBindingID != nil {
|
||||
updateRuntime = true
|
||||
trimmed := strings.TrimSpace(*input.RuntimeBindingID)
|
||||
if trimmed != "" {
|
||||
runtimeBinding, err := s.store.GetMapRuntimeBindingByPublicID(ctx, trimmed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if runtimeBinding == nil {
|
||||
return nil, apperr.New(http.StatusNotFound, "runtime_binding_not_found", "runtime binding not found")
|
||||
}
|
||||
if runtimeBinding.EventID != record.ID {
|
||||
return nil, apperr.New(http.StatusConflict, "runtime_binding_not_belong_to_event", "runtime binding does not belong to event")
|
||||
}
|
||||
runtimeBindingID = &runtimeBinding.ID
|
||||
}
|
||||
}
|
||||
|
||||
tx, err := s.store.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
if err := s.store.SetEventDefaultBindings(ctx, tx, postgres.SetEventDefaultBindingsParams{
|
||||
EventID: record.ID,
|
||||
PresentationID: presentationID,
|
||||
ContentBundleID: contentBundleID,
|
||||
RuntimeBindingID: runtimeBindingID,
|
||||
UpdatePresentation: updatePresentation,
|
||||
UpdateContent: updateContent,
|
||||
UpdateRuntime: updateRuntime,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.GetEventDetail(ctx, eventPublicID)
|
||||
}
|
||||
|
||||
func (s *AdminEventService) SaveEventSource(ctx context.Context, eventPublicID string, input SaveAdminEventSourceInput) (*EventConfigSourceView, error) {
|
||||
eventRecord, err := s.store.GetAdminEventByPublicID(ctx, strings.TrimSpace(eventPublicID))
|
||||
if err != nil {
|
||||
@@ -441,11 +955,160 @@ func buildAdminEventSummary(item postgres.AdminEventRecord) AdminEventSummary {
|
||||
ManifestURL: item.ManifestURL,
|
||||
ManifestChecksumSha256: item.ManifestChecksum,
|
||||
RouteCode: item.RouteCode,
|
||||
Presentation: buildPresentationSummaryFromEventRecord(&item),
|
||||
ContentBundle: buildContentBundleSummaryFromEventRecord(&item),
|
||||
}
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func buildPresentationSummaryFromEventRecord(item *postgres.AdminEventRecord) *PresentationSummaryView {
|
||||
if item == nil || item.CurrentPresentationID == nil {
|
||||
return nil
|
||||
}
|
||||
return &PresentationSummaryView{
|
||||
PresentationID: *item.CurrentPresentationID,
|
||||
Name: item.CurrentPresentationName,
|
||||
PresentationType: item.CurrentPresentationType,
|
||||
}
|
||||
}
|
||||
|
||||
func buildContentBundleSummaryFromEventRecord(item *postgres.AdminEventRecord) *ContentBundleSummaryView {
|
||||
if item == nil || item.CurrentContentBundleID == nil {
|
||||
return nil
|
||||
}
|
||||
return &ContentBundleSummaryView{
|
||||
ContentBundleID: *item.CurrentContentBundleID,
|
||||
Name: item.CurrentContentBundleName,
|
||||
EntryURL: item.CurrentContentEntryURL,
|
||||
AssetRootURL: item.CurrentContentAssetRootURL,
|
||||
}
|
||||
}
|
||||
|
||||
func buildRuntimeSummaryFromAdminEventRecord(item *postgres.AdminEventRecord) *RuntimeSummaryView {
|
||||
if item == nil ||
|
||||
item.CurrentRuntimeBindingID == nil ||
|
||||
item.CurrentPlaceID == nil ||
|
||||
item.CurrentMapAssetID == nil ||
|
||||
item.CurrentTileReleaseID == nil ||
|
||||
item.CurrentCourseSetID == nil ||
|
||||
item.CurrentCourseVariantID == nil {
|
||||
return nil
|
||||
}
|
||||
return &RuntimeSummaryView{
|
||||
RuntimeBindingID: *item.CurrentRuntimeBindingID,
|
||||
PlaceID: *item.CurrentPlaceID,
|
||||
MapID: *item.CurrentMapAssetID,
|
||||
TileReleaseID: *item.CurrentTileReleaseID,
|
||||
CourseSetID: *item.CurrentCourseSetID,
|
||||
CourseVariantID: *item.CurrentCourseVariantID,
|
||||
CourseVariantName: item.CurrentCourseVariantName,
|
||||
RouteCode: item.CurrentRuntimeRouteCode,
|
||||
}
|
||||
}
|
||||
|
||||
func buildAdminEventPresentationView(item postgres.EventPresentation) (AdminEventPresentationView, error) {
|
||||
schema, err := decodeJSONObject(item.SchemaJSON)
|
||||
if err != nil {
|
||||
return AdminEventPresentationView{}, err
|
||||
}
|
||||
return AdminEventPresentationView{
|
||||
ID: item.PublicID,
|
||||
EventID: item.EventPublicID,
|
||||
Code: item.Code,
|
||||
Name: item.Name,
|
||||
PresentationType: item.PresentationType,
|
||||
Status: item.Status,
|
||||
IsDefault: item.IsDefault,
|
||||
TemplateKey: readStringField(schema, "templateKey"),
|
||||
Version: readStringField(schema, "version"),
|
||||
SourceType: readStringField(schema, "sourceType"),
|
||||
SchemaURL: readStringField(schema, "schemaUrl"),
|
||||
Schema: schema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildAdminContentBundleView(item postgres.ContentBundle) (AdminContentBundleView, error) {
|
||||
metadata, err := decodeJSONObject(item.MetadataJSON)
|
||||
if err != nil {
|
||||
return AdminContentBundleView{}, err
|
||||
}
|
||||
return AdminContentBundleView{
|
||||
ID: item.PublicID,
|
||||
EventID: item.EventPublicID,
|
||||
Code: item.Code,
|
||||
Name: item.Name,
|
||||
Status: item.Status,
|
||||
IsDefault: item.IsDefault,
|
||||
BundleType: readStringField(metadata, "bundleType"),
|
||||
Version: readStringField(metadata, "version"),
|
||||
SourceType: readStringField(metadata, "sourceType"),
|
||||
ManifestURL: readStringField(metadata, "manifestUrl"),
|
||||
AssetManifest: metadata["assetManifest"],
|
||||
EntryURL: item.EntryURL,
|
||||
AssetRootURL: item.AssetRootURL,
|
||||
Metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateImportedBundleCode(title, publicID string) string {
|
||||
var builder strings.Builder
|
||||
for _, r := range strings.ToLower(title) {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
builder.WriteRune(r)
|
||||
case r >= '0' && r <= '9':
|
||||
builder.WriteRune(r)
|
||||
case r == ' ' || r == '-' || r == '_':
|
||||
if builder.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
last := builder.String()[builder.Len()-1]
|
||||
if last != '-' {
|
||||
builder.WriteByte('-')
|
||||
}
|
||||
}
|
||||
}
|
||||
code := strings.Trim(builder.String(), "-")
|
||||
if code == "" {
|
||||
code = "bundle"
|
||||
}
|
||||
suffix := publicID
|
||||
if len(suffix) > 8 {
|
||||
suffix = suffix[len(suffix)-8:]
|
||||
}
|
||||
return code + "-" + suffix
|
||||
}
|
||||
|
||||
func generateImportedPresentationCode(title, publicID string) string {
|
||||
var builder strings.Builder
|
||||
for _, r := range strings.ToLower(title) {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z':
|
||||
builder.WriteRune(r)
|
||||
case r >= '0' && r <= '9':
|
||||
builder.WriteRune(r)
|
||||
case r == ' ' || r == '-' || r == '_':
|
||||
if builder.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
last := builder.String()[builder.Len()-1]
|
||||
if last != '-' {
|
||||
builder.WriteByte('-')
|
||||
}
|
||||
}
|
||||
}
|
||||
code := strings.Trim(builder.String(), "-")
|
||||
if code == "" {
|
||||
code = "presentation"
|
||||
}
|
||||
suffix := publicID
|
||||
if len(suffix) > 8 {
|
||||
suffix = suffix[len(suffix)-8:]
|
||||
}
|
||||
return code + "-" + suffix
|
||||
}
|
||||
|
||||
func buildAdminAssembledSource(source map[string]any) *AdminAssembledSource {
|
||||
result := &AdminAssembledSource{}
|
||||
if refs, ok := source["refs"].(map[string]any); ok {
|
||||
@@ -474,6 +1137,26 @@ func normalizeEventCatalogStatus(value string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizePresentationType(value string) string {
|
||||
switch strings.TrimSpace(value) {
|
||||
case "card":
|
||||
return "card"
|
||||
case "detail":
|
||||
return "detail"
|
||||
case "h5":
|
||||
return "h5"
|
||||
case "result":
|
||||
return "result"
|
||||
default:
|
||||
return "generic"
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarshalJSONObject(value map[string]any) string {
|
||||
raw, _ := json.Marshal(value)
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
func mergeJSONObject(target map[string]any, overrides map[string]any) {
|
||||
for key, value := range overrides {
|
||||
if valueMap, ok := value.(map[string]any); ok {
|
||||
|
||||
Reference in New Issue
Block a user