event_play_service.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package service
  2. import (
  3. "context"
  4. "net/http"
  5. "strings"
  6. "cmr-backend/internal/apperr"
  7. "cmr-backend/internal/store/postgres"
  8. )
  9. type EventPlayService struct {
  10. store *postgres.Store
  11. }
  12. type EventPlayInput struct {
  13. EventPublicID string
  14. UserID string
  15. }
  16. type EventPlayResult struct {
  17. Event struct {
  18. ID string `json:"id"`
  19. Slug string `json:"slug"`
  20. DisplayName string `json:"displayName"`
  21. Summary *string `json:"summary,omitempty"`
  22. Status string `json:"status"`
  23. } `json:"event"`
  24. Release *struct {
  25. ID string `json:"id"`
  26. ConfigLabel string `json:"configLabel"`
  27. ManifestURL string `json:"manifestUrl"`
  28. ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
  29. RouteCode *string `json:"routeCode,omitempty"`
  30. } `json:"release,omitempty"`
  31. ResolvedRelease *ResolvedReleaseView `json:"resolvedRelease,omitempty"`
  32. Runtime *RuntimeSummaryView `json:"runtime,omitempty"`
  33. CurrentPresentation *PresentationSummaryView `json:"currentPresentation,omitempty"`
  34. CurrentContentBundle *ContentBundleSummaryView `json:"currentContentBundle,omitempty"`
  35. Play struct {
  36. AssignmentMode *string `json:"assignmentMode,omitempty"`
  37. CourseVariants []CourseVariantView `json:"courseVariants,omitempty"`
  38. CanLaunch bool `json:"canLaunch"`
  39. PrimaryAction string `json:"primaryAction"`
  40. Reason string `json:"reason"`
  41. LaunchSource string `json:"launchSource,omitempty"`
  42. OngoingSession *EntrySessionSummary `json:"ongoingSession,omitempty"`
  43. RecentSession *EntrySessionSummary `json:"recentSession,omitempty"`
  44. } `json:"play"`
  45. }
  46. func NewEventPlayService(store *postgres.Store) *EventPlayService {
  47. return &EventPlayService{store: store}
  48. }
  49. func (s *EventPlayService) GetEventPlay(ctx context.Context, input EventPlayInput) (*EventPlayResult, error) {
  50. input.EventPublicID = strings.TrimSpace(input.EventPublicID)
  51. input.UserID = strings.TrimSpace(input.UserID)
  52. if input.EventPublicID == "" {
  53. return nil, apperr.New(http.StatusBadRequest, "invalid_params", "event id is required")
  54. }
  55. if input.UserID == "" {
  56. return nil, apperr.New(http.StatusUnauthorized, "unauthorized", "user is required")
  57. }
  58. event, err := s.store.GetEventByPublicID(ctx, input.EventPublicID)
  59. if err != nil {
  60. return nil, err
  61. }
  62. if event == nil {
  63. return nil, apperr.New(http.StatusNotFound, "event_not_found", "event not found")
  64. }
  65. sessions, err := s.store.ListSessionsByUserAndEvent(ctx, input.UserID, event.ID, 10)
  66. if err != nil {
  67. return nil, err
  68. }
  69. result := &EventPlayResult{}
  70. result.Event.ID = event.PublicID
  71. result.Event.Slug = event.Slug
  72. result.Event.DisplayName = event.DisplayName
  73. result.Event.Summary = event.Summary
  74. result.Event.Status = event.Status
  75. variantPlan := resolveVariantPlan(event.ReleasePayloadJSON)
  76. result.Play.AssignmentMode = variantPlan.AssignmentMode
  77. if len(variantPlan.CourseVariants) > 0 {
  78. result.Play.CourseVariants = variantPlan.CourseVariants
  79. }
  80. if event.CurrentReleasePubID != nil && event.ConfigLabel != nil && event.ManifestURL != nil {
  81. result.Release = &struct {
  82. ID string `json:"id"`
  83. ConfigLabel string `json:"configLabel"`
  84. ManifestURL string `json:"manifestUrl"`
  85. ManifestChecksumSha256 *string `json:"manifestChecksumSha256,omitempty"`
  86. RouteCode *string `json:"routeCode,omitempty"`
  87. }{
  88. ID: *event.CurrentReleasePubID,
  89. ConfigLabel: *event.ConfigLabel,
  90. ManifestURL: *event.ManifestURL,
  91. ManifestChecksumSha256: event.ManifestChecksum,
  92. RouteCode: event.RouteCode,
  93. }
  94. }
  95. result.ResolvedRelease = buildResolvedReleaseFromEvent(event, LaunchSourceEventCurrentRelease)
  96. result.Runtime = buildRuntimeSummaryFromEvent(event)
  97. result.CurrentPresentation = buildPresentationSummaryFromEvent(event)
  98. if enrichedPresentation, err := loadPresentationSummaryByPublicID(ctx, s.store, event.PresentationID); err != nil {
  99. return nil, err
  100. } else if enrichedPresentation != nil {
  101. result.CurrentPresentation = enrichedPresentation
  102. }
  103. result.CurrentContentBundle = buildContentBundleSummaryFromEvent(event)
  104. if enrichedBundle, err := loadContentBundleSummaryByPublicID(ctx, s.store, event.ContentBundleID); err != nil {
  105. return nil, err
  106. } else if enrichedBundle != nil {
  107. result.CurrentContentBundle = enrichedBundle
  108. }
  109. if len(sessions) > 0 {
  110. recent := buildEntrySessionSummary(&sessions[0])
  111. result.Play.RecentSession = &recent
  112. }
  113. for i := range sessions {
  114. if isSessionOngoingStatus(sessions[i].Status) {
  115. ongoing := buildEntrySessionSummary(&sessions[i])
  116. result.Play.OngoingSession = &ongoing
  117. break
  118. }
  119. }
  120. canLaunch, launchReason := evaluateEventLaunchReadiness(event)
  121. result.Play.CanLaunch = canLaunch
  122. if canLaunch {
  123. result.Play.LaunchSource = LaunchSourceEventCurrentRelease
  124. }
  125. switch {
  126. case result.Play.OngoingSession != nil:
  127. result.Play.PrimaryAction = "continue"
  128. result.Play.Reason = "user has an ongoing session for this event"
  129. case canLaunch:
  130. result.Play.PrimaryAction = "start"
  131. result.Play.Reason = launchReason
  132. case result.Play.RecentSession != nil:
  133. result.Play.PrimaryAction = "review_last_result"
  134. result.Play.Reason = launchReason + ", but user has previous session history"
  135. default:
  136. result.Play.PrimaryAction = "unavailable"
  137. result.Play.Reason = launchReason
  138. }
  139. return result, nil
  140. }