event_store.go 16 KB

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