package service import ( "context" "net/http" "strings" "cmr-backend/internal/apperr" "cmr-backend/internal/store/postgres" ) type EventPlayService struct { store *postgres.Store } type EventPlayInput struct { EventPublicID string UserID string } type EventPlayResult struct { Event struct { ID string `json:"id"` Slug string `json:"slug"` DisplayName string `json:"displayName"` Summary *string `json:"summary,omitempty"` Status string `json:"status"` } `json:"event"` Release *struct { ID string `json:"id"` ConfigLabel string `json:"configLabel"` ManifestURL string `json:"manifestUrl"` ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"` RouteCode *string `json:"routeCode,omitempty"` } `json:"release,omitempty"` ResolvedRelease *ResolvedReleaseView `json:"resolvedRelease,omitempty"` Play struct { CanLaunch bool `json:"canLaunch"` PrimaryAction string `json:"primaryAction"` Reason string `json:"reason"` LaunchSource string `json:"launchSource,omitempty"` OngoingSession *EntrySessionSummary `json:"ongoingSession,omitempty"` RecentSession *EntrySessionSummary `json:"recentSession,omitempty"` } `json:"play"` } func NewEventPlayService(store *postgres.Store) *EventPlayService { return &EventPlayService{store: store} } func (s *EventPlayService) GetEventPlay(ctx context.Context, input EventPlayInput) (*EventPlayResult, error) { input.EventPublicID = strings.TrimSpace(input.EventPublicID) input.UserID = strings.TrimSpace(input.UserID) if input.EventPublicID == "" { return nil, apperr.New(http.StatusBadRequest, "invalid_params", "event id is required") } if input.UserID == "" { return nil, apperr.New(http.StatusUnauthorized, "unauthorized", "user is required") } event, err := s.store.GetEventByPublicID(ctx, input.EventPublicID) if err != nil { return nil, err } if event == nil { return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found") } sessions, err := s.store.ListSessionsByUserAndEvent(ctx, input.UserID, event.ID, 10) if err != nil { return nil, err } result := &EventPlayResult{} result.Event.ID = event.PublicID result.Event.Slug = event.Slug result.Event.DisplayName = event.DisplayName result.Event.Summary = event.Summary result.Event.Status = event.Status if event.CurrentReleasePubID != nil && event.ConfigLabel != nil && event.ManifestURL != nil { result.Release = &struct { ID string `json:"id"` ConfigLabel string `json:"configLabel"` ManifestURL string `json:"manifestUrl"` ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"` RouteCode *string `json:"routeCode,omitempty"` }{ ID: *event.CurrentReleasePubID, ConfigLabel: *event.ConfigLabel, ManifestURL: *event.ManifestURL, ManifestChecksumSha256: event.ManifestChecksum, RouteCode: event.RouteCode, } } result.ResolvedRelease = buildResolvedReleaseFromEvent(event, LaunchSourceEventCurrentRelease) if len(sessions) > 0 { recent := buildEntrySessionSummary(&sessions[0]) result.Play.RecentSession = &recent } for i := range sessions { if isSessionOngoingStatus(sessions[i].Status) { ongoing := buildEntrySessionSummary(&sessions[i]) result.Play.OngoingSession = &ongoing break } } canLaunch := event.Status == "active" && event.CurrentReleaseID != nil && event.ManifestURL != nil result.Play.CanLaunch = canLaunch if canLaunch { result.Play.LaunchSource = LaunchSourceEventCurrentRelease } switch { case result.Play.OngoingSession != nil: result.Play.PrimaryAction = "continue" result.Play.Reason = "user has an ongoing session for this event" case canLaunch: result.Play.PrimaryAction = "start" result.Play.Reason = "event is active and launchable" case result.Play.RecentSession != nil: result.Play.PrimaryAction = "review_last_result" result.Play.Reason = "event is not launchable, but user has previous session history" default: result.Play.PrimaryAction = "unavailable" result.Play.Reason = "event is not launchable" } return result, nil }