event_store.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. package postgres
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "time"
  7. "github.com/jackc/pgx/v5"
  8. )
  9. type Event struct {
  10. ID string
  11. PublicID string
  12. Slug string
  13. DisplayName string
  14. Summary *string
  15. Status string
  16. IsDefaultExperience bool
  17. ShowInEventList bool
  18. CurrentReleaseID *string
  19. CurrentReleasePubID *string
  20. ConfigLabel *string
  21. ManifestURL *string
  22. ManifestChecksum *string
  23. RouteCode *string
  24. ReleasePayloadJSON *string
  25. RuntimeBindingID *string
  26. PlacePublicID *string
  27. PlaceName *string
  28. MapAssetPublicID *string
  29. MapAssetName *string
  30. TileReleasePublicID *string
  31. CourseSetPublicID *string
  32. CourseVariantID *string
  33. CourseVariantName *string
  34. RuntimeRouteCode *string
  35. PresentationID *string
  36. PresentationName *string
  37. PresentationType *string
  38. ContentBundleID *string
  39. ContentBundleName *string
  40. ContentEntryURL *string
  41. ContentAssetRootURL *string
  42. }
  43. type EventRelease struct {
  44. ID string
  45. PublicID string
  46. EventID string
  47. ReleaseNo int
  48. ConfigLabel string
  49. ManifestURL string
  50. ManifestChecksum *string
  51. RouteCode *string
  52. BuildID *string
  53. Status string
  54. PublishedAt time.Time
  55. RuntimeBindingID *string
  56. PlacePublicID *string
  57. PlaceName *string
  58. MapAssetPublicID *string
  59. MapAssetName *string
  60. TileReleaseID *string
  61. CourseSetID *string
  62. CourseVariantID *string
  63. CourseVariantName *string
  64. RuntimeRouteCode *string
  65. PresentationID *string
  66. PresentationName *string
  67. PresentationType *string
  68. ContentBundleID *string
  69. ContentBundleName *string
  70. ContentEntryURL *string
  71. ContentAssetURL *string
  72. }
  73. type CreateGameSessionParams struct {
  74. SessionPublicID string
  75. UserID string
  76. EventID string
  77. EventReleaseID string
  78. DeviceKey string
  79. ClientType string
  80. AssignmentMode *string
  81. VariantID *string
  82. VariantName *string
  83. RouteCode *string
  84. SessionTokenHash string
  85. SessionTokenExpiresAt time.Time
  86. }
  87. type GameSession struct {
  88. ID string
  89. SessionPublicID string
  90. UserID string
  91. EventID string
  92. EventReleaseID string
  93. DeviceKey string
  94. ClientType string
  95. AssignmentMode *string
  96. VariantID *string
  97. VariantName *string
  98. RouteCode *string
  99. Status string
  100. SessionTokenExpiresAt time.Time
  101. }
  102. func (s *Store) GetEventByPublicID(ctx context.Context, eventPublicID string) (*Event, error) {
  103. row := s.pool.QueryRow(ctx, `
  104. SELECT
  105. e.id,
  106. e.event_public_id,
  107. e.slug,
  108. e.display_name,
  109. e.summary,
  110. e.status,
  111. e.is_default_experience,
  112. e.show_in_event_list,
  113. e.current_release_id,
  114. er.release_public_id,
  115. er.config_label,
  116. er.manifest_url,
  117. er.manifest_checksum_sha256,
  118. er.route_code,
  119. er.payload_jsonb::text,
  120. mrb.runtime_binding_public_id,
  121. p.place_public_id,
  122. p.name,
  123. ma.map_asset_public_id,
  124. ma.name,
  125. tr.tile_release_public_id,
  126. cset.course_set_public_id,
  127. cv.course_variant_public_id,
  128. cv.name,
  129. cv.route_code,
  130. ep.presentation_public_id,
  131. ep.name,
  132. ep.presentation_type,
  133. cb.content_bundle_public_id,
  134. cb.name,
  135. cb.entry_url,
  136. cb.asset_root_url
  137. FROM events e
  138. LEFT JOIN event_releases er ON er.id = e.current_release_id
  139. LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
  140. LEFT JOIN places p ON p.id = mrb.place_id
  141. LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
  142. LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
  143. LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
  144. LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
  145. LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
  146. LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
  147. WHERE e.event_public_id = $1
  148. LIMIT 1
  149. `, eventPublicID)
  150. var event Event
  151. err := row.Scan(
  152. &event.ID,
  153. &event.PublicID,
  154. &event.Slug,
  155. &event.DisplayName,
  156. &event.Summary,
  157. &event.Status,
  158. &event.IsDefaultExperience,
  159. &event.ShowInEventList,
  160. &event.CurrentReleaseID,
  161. &event.CurrentReleasePubID,
  162. &event.ConfigLabel,
  163. &event.ManifestURL,
  164. &event.ManifestChecksum,
  165. &event.RouteCode,
  166. &event.ReleasePayloadJSON,
  167. &event.RuntimeBindingID,
  168. &event.PlacePublicID,
  169. &event.PlaceName,
  170. &event.MapAssetPublicID,
  171. &event.MapAssetName,
  172. &event.TileReleasePublicID,
  173. &event.CourseSetPublicID,
  174. &event.CourseVariantID,
  175. &event.CourseVariantName,
  176. &event.RuntimeRouteCode,
  177. &event.PresentationID,
  178. &event.PresentationName,
  179. &event.PresentationType,
  180. &event.ContentBundleID,
  181. &event.ContentBundleName,
  182. &event.ContentEntryURL,
  183. &event.ContentAssetRootURL,
  184. )
  185. if errors.Is(err, pgx.ErrNoRows) {
  186. return nil, nil
  187. }
  188. if err != nil {
  189. return nil, fmt.Errorf("get event by public id: %w", err)
  190. }
  191. return &event, nil
  192. }
  193. func (s *Store) GetEventByID(ctx context.Context, eventID string) (*Event, error) {
  194. row := s.pool.QueryRow(ctx, `
  195. SELECT
  196. e.id,
  197. e.event_public_id,
  198. e.slug,
  199. e.display_name,
  200. e.summary,
  201. e.status,
  202. e.is_default_experience,
  203. e.show_in_event_list,
  204. e.current_release_id,
  205. er.release_public_id,
  206. er.config_label,
  207. er.manifest_url,
  208. er.manifest_checksum_sha256,
  209. er.route_code,
  210. er.payload_jsonb::text,
  211. mrb.runtime_binding_public_id,
  212. p.place_public_id,
  213. p.name,
  214. ma.map_asset_public_id,
  215. ma.name,
  216. tr.tile_release_public_id,
  217. cset.course_set_public_id,
  218. cv.course_variant_public_id,
  219. cv.name,
  220. cv.route_code,
  221. ep.presentation_public_id,
  222. ep.name,
  223. ep.presentation_type,
  224. cb.content_bundle_public_id,
  225. cb.name,
  226. cb.entry_url,
  227. cb.asset_root_url
  228. FROM events e
  229. LEFT JOIN event_releases er ON er.id = e.current_release_id
  230. LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
  231. LEFT JOIN places p ON p.id = mrb.place_id
  232. LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
  233. LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
  234. LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
  235. LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
  236. LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
  237. LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
  238. WHERE e.id = $1
  239. LIMIT 1
  240. `, eventID)
  241. var event Event
  242. err := row.Scan(
  243. &event.ID,
  244. &event.PublicID,
  245. &event.Slug,
  246. &event.DisplayName,
  247. &event.Summary,
  248. &event.Status,
  249. &event.IsDefaultExperience,
  250. &event.ShowInEventList,
  251. &event.CurrentReleaseID,
  252. &event.CurrentReleasePubID,
  253. &event.ConfigLabel,
  254. &event.ManifestURL,
  255. &event.ManifestChecksum,
  256. &event.RouteCode,
  257. &event.ReleasePayloadJSON,
  258. &event.RuntimeBindingID,
  259. &event.PlacePublicID,
  260. &event.PlaceName,
  261. &event.MapAssetPublicID,
  262. &event.MapAssetName,
  263. &event.TileReleasePublicID,
  264. &event.CourseSetPublicID,
  265. &event.CourseVariantID,
  266. &event.CourseVariantName,
  267. &event.RuntimeRouteCode,
  268. &event.PresentationID,
  269. &event.PresentationName,
  270. &event.PresentationType,
  271. &event.ContentBundleID,
  272. &event.ContentBundleName,
  273. &event.ContentEntryURL,
  274. &event.ContentAssetRootURL,
  275. )
  276. if errors.Is(err, pgx.ErrNoRows) {
  277. return nil, nil
  278. }
  279. if err != nil {
  280. return nil, fmt.Errorf("get event by id: %w", err)
  281. }
  282. return &event, nil
  283. }
  284. func (s *Store) NextEventReleaseNo(ctx context.Context, eventID string) (int, error) {
  285. var next int
  286. if err := s.pool.QueryRow(ctx, `
  287. SELECT COALESCE(MAX(release_no), 0) + 1
  288. FROM event_releases er
  289. WHERE event_id = $1
  290. `, eventID).Scan(&next); err != nil {
  291. return 0, fmt.Errorf("next event release no: %w", err)
  292. }
  293. return next, nil
  294. }
  295. type CreateEventReleaseParams struct {
  296. PublicID string
  297. EventID string
  298. ReleaseNo int
  299. ConfigLabel string
  300. ManifestURL string
  301. ManifestChecksum *string
  302. RouteCode *string
  303. BuildID *string
  304. RuntimeBindingID *string
  305. PresentationID *string
  306. ContentBundleID *string
  307. Status string
  308. PayloadJSON string
  309. }
  310. func (s *Store) CreateEventRelease(ctx context.Context, tx Tx, params CreateEventReleaseParams) (*EventRelease, error) {
  311. row := tx.QueryRow(ctx, `
  312. INSERT INTO event_releases (
  313. release_public_id,
  314. event_id,
  315. release_no,
  316. config_label,
  317. manifest_url,
  318. manifest_checksum_sha256,
  319. route_code,
  320. build_id,
  321. runtime_binding_id,
  322. presentation_id,
  323. content_bundle_id,
  324. status,
  325. payload_jsonb
  326. )
  327. VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13::jsonb)
  328. RETURNING id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at
  329. `, params.PublicID, params.EventID, params.ReleaseNo, params.ConfigLabel, params.ManifestURL, params.ManifestChecksum, params.RouteCode, params.BuildID, params.RuntimeBindingID, params.PresentationID, params.ContentBundleID, params.Status, params.PayloadJSON)
  330. var item EventRelease
  331. if err := row.Scan(
  332. &item.ID,
  333. &item.PublicID,
  334. &item.EventID,
  335. &item.ReleaseNo,
  336. &item.ConfigLabel,
  337. &item.ManifestURL,
  338. &item.ManifestChecksum,
  339. &item.RouteCode,
  340. &item.BuildID,
  341. &item.Status,
  342. &item.PublishedAt,
  343. ); err != nil {
  344. return nil, fmt.Errorf("create event release: %w", err)
  345. }
  346. return &item, nil
  347. }
  348. func (s *Store) SetCurrentEventRelease(ctx context.Context, tx Tx, eventID, releaseID string) error {
  349. if _, err := tx.Exec(ctx, `
  350. UPDATE events
  351. SET current_release_id = $2
  352. WHERE id = $1
  353. `, eventID, releaseID); err != nil {
  354. return fmt.Errorf("set current event release: %w", err)
  355. }
  356. return nil
  357. }
  358. func (s *Store) CreateGameSession(ctx context.Context, tx Tx, params CreateGameSessionParams) (*GameSession, error) {
  359. row := tx.QueryRow(ctx, `
  360. INSERT INTO game_sessions (
  361. session_public_id,
  362. user_id,
  363. event_id,
  364. event_release_id,
  365. device_key,
  366. client_type,
  367. assignment_mode,
  368. variant_id,
  369. variant_name,
  370. route_code,
  371. session_token_hash,
  372. session_token_expires_at
  373. )
  374. VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
  375. RETURNING id, session_public_id, user_id, event_id, event_release_id, device_key, client_type, assignment_mode, variant_id, variant_name, route_code, status, session_token_expires_at
  376. `, params.SessionPublicID, params.UserID, params.EventID, params.EventReleaseID, params.DeviceKey, params.ClientType, params.AssignmentMode, params.VariantID, params.VariantName, params.RouteCode, params.SessionTokenHash, params.SessionTokenExpiresAt)
  377. var session GameSession
  378. err := row.Scan(
  379. &session.ID,
  380. &session.SessionPublicID,
  381. &session.UserID,
  382. &session.EventID,
  383. &session.EventReleaseID,
  384. &session.DeviceKey,
  385. &session.ClientType,
  386. &session.AssignmentMode,
  387. &session.VariantID,
  388. &session.VariantName,
  389. &session.RouteCode,
  390. &session.Status,
  391. &session.SessionTokenExpiresAt,
  392. )
  393. if err != nil {
  394. return nil, fmt.Errorf("create game session: %w", err)
  395. }
  396. return &session, nil
  397. }
  398. func (s *Store) ListEventReleasesByEventID(ctx context.Context, eventID string, limit int) ([]EventRelease, error) {
  399. if limit <= 0 || limit > 100 {
  400. limit = 20
  401. }
  402. rows, err := s.pool.Query(ctx, `
  403. SELECT
  404. er.id,
  405. er.release_public_id,
  406. er.event_id,
  407. er.release_no,
  408. er.config_label,
  409. er.manifest_url,
  410. er.manifest_checksum_sha256,
  411. er.route_code,
  412. er.build_id,
  413. er.status,
  414. er.published_at,
  415. mrb.runtime_binding_public_id,
  416. p.place_public_id,
  417. p.name,
  418. ma.map_asset_public_id,
  419. ma.name,
  420. tr.tile_release_public_id,
  421. cset.course_set_public_id,
  422. cv.course_variant_public_id,
  423. cv.name,
  424. cv.route_code,
  425. ep.presentation_public_id,
  426. ep.name,
  427. ep.presentation_type,
  428. cb.content_bundle_public_id,
  429. cb.name,
  430. cb.entry_url,
  431. cb.asset_root_url
  432. FROM event_releases er
  433. LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
  434. LEFT JOIN places p ON p.id = mrb.place_id
  435. LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
  436. LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
  437. LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
  438. LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
  439. LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
  440. LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
  441. WHERE er.event_id = $1
  442. ORDER BY er.release_no DESC
  443. LIMIT $2
  444. `, eventID, limit)
  445. if err != nil {
  446. return nil, fmt.Errorf("list event releases by event id: %w", err)
  447. }
  448. defer rows.Close()
  449. items := []EventRelease{}
  450. for rows.Next() {
  451. item, err := scanEventReleaseFromRows(rows)
  452. if err != nil {
  453. return nil, err
  454. }
  455. items = append(items, *item)
  456. }
  457. if err := rows.Err(); err != nil {
  458. return nil, fmt.Errorf("iterate event releases by event id: %w", err)
  459. }
  460. return items, nil
  461. }
  462. func (s *Store) GetEventReleaseByPublicID(ctx context.Context, releasePublicID string) (*EventRelease, error) {
  463. row := s.pool.QueryRow(ctx, `
  464. SELECT
  465. er.id,
  466. er.release_public_id,
  467. er.event_id,
  468. er.release_no,
  469. er.config_label,
  470. er.manifest_url,
  471. er.manifest_checksum_sha256,
  472. er.route_code,
  473. er.build_id,
  474. er.status,
  475. er.published_at,
  476. mrb.runtime_binding_public_id,
  477. p.place_public_id,
  478. p.name,
  479. ma.map_asset_public_id,
  480. ma.name,
  481. tr.tile_release_public_id,
  482. cset.course_set_public_id,
  483. cv.course_variant_public_id,
  484. cv.name,
  485. cv.route_code,
  486. ep.presentation_public_id,
  487. ep.name,
  488. ep.presentation_type,
  489. cb.content_bundle_public_id,
  490. cb.name,
  491. cb.entry_url,
  492. cb.asset_root_url
  493. FROM event_releases er
  494. LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
  495. LEFT JOIN places p ON p.id = mrb.place_id
  496. LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
  497. LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
  498. LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
  499. LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
  500. LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
  501. LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
  502. WHERE er.release_public_id = $1
  503. LIMIT 1
  504. `, releasePublicID)
  505. var item EventRelease
  506. err := row.Scan(
  507. &item.ID,
  508. &item.PublicID,
  509. &item.EventID,
  510. &item.ReleaseNo,
  511. &item.ConfigLabel,
  512. &item.ManifestURL,
  513. &item.ManifestChecksum,
  514. &item.RouteCode,
  515. &item.BuildID,
  516. &item.Status,
  517. &item.PublishedAt,
  518. &item.RuntimeBindingID,
  519. &item.PlacePublicID,
  520. &item.PlaceName,
  521. &item.MapAssetPublicID,
  522. &item.MapAssetName,
  523. &item.TileReleaseID,
  524. &item.CourseSetID,
  525. &item.CourseVariantID,
  526. &item.CourseVariantName,
  527. &item.RuntimeRouteCode,
  528. &item.PresentationID,
  529. &item.PresentationName,
  530. &item.PresentationType,
  531. &item.ContentBundleID,
  532. &item.ContentBundleName,
  533. &item.ContentEntryURL,
  534. &item.ContentAssetURL,
  535. )
  536. if errors.Is(err, pgx.ErrNoRows) {
  537. return nil, nil
  538. }
  539. if err != nil {
  540. return nil, fmt.Errorf("get event release by public id: %w", err)
  541. }
  542. return &item, nil
  543. }
  544. func scanEventReleaseFromRows(rows pgx.Rows) (*EventRelease, error) {
  545. var item EventRelease
  546. err := rows.Scan(
  547. &item.ID,
  548. &item.PublicID,
  549. &item.EventID,
  550. &item.ReleaseNo,
  551. &item.ConfigLabel,
  552. &item.ManifestURL,
  553. &item.ManifestChecksum,
  554. &item.RouteCode,
  555. &item.BuildID,
  556. &item.Status,
  557. &item.PublishedAt,
  558. &item.RuntimeBindingID,
  559. &item.PlacePublicID,
  560. &item.PlaceName,
  561. &item.MapAssetPublicID,
  562. &item.MapAssetName,
  563. &item.TileReleaseID,
  564. &item.CourseSetID,
  565. &item.CourseVariantID,
  566. &item.CourseVariantName,
  567. &item.RuntimeRouteCode,
  568. &item.PresentationID,
  569. &item.PresentationName,
  570. &item.PresentationType,
  571. &item.ContentBundleID,
  572. &item.ContentBundleName,
  573. &item.ContentEntryURL,
  574. &item.ContentAssetURL,
  575. )
  576. if err != nil {
  577. return nil, fmt.Errorf("scan event release row: %w", err)
  578. }
  579. return &item, nil
  580. }
  581. func (s *Store) SetEventReleaseRuntimeBinding(ctx context.Context, tx Tx, releaseID string, runtimeBindingID *string) error {
  582. if _, err := tx.Exec(ctx, `
  583. UPDATE event_releases
  584. SET runtime_binding_id = $2
  585. WHERE id = $1
  586. `, releaseID, runtimeBindingID); err != nil {
  587. return fmt.Errorf("set event release runtime binding: %w", err)
  588. }
  589. return nil
  590. }
  591. func (s *Store) SetEventReleaseBindings(ctx context.Context, tx Tx, releaseID string, runtimeBindingID, presentationID, contentBundleID *string) error {
  592. if _, err := tx.Exec(ctx, `
  593. UPDATE event_releases
  594. SET runtime_binding_id = $2,
  595. presentation_id = $3,
  596. content_bundle_id = $4
  597. WHERE id = $1
  598. `, releaseID, runtimeBindingID, presentationID, contentBundleID); err != nil {
  599. return fmt.Errorf("set event release bindings: %w", err)
  600. }
  601. return nil
  602. }