admin_event_store.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package postgres
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "github.com/jackc/pgx/v5"
  7. )
  8. type Tenant struct {
  9. ID string
  10. TenantCode string
  11. Name string
  12. Status string
  13. }
  14. type AdminEventRecord struct {
  15. ID string
  16. PublicID string
  17. TenantID *string
  18. TenantCode *string
  19. TenantName *string
  20. Slug string
  21. DisplayName string
  22. Summary *string
  23. Status string
  24. CurrentReleaseID *string
  25. CurrentReleasePubID *string
  26. ConfigLabel *string
  27. ManifestURL *string
  28. ManifestChecksum *string
  29. RouteCode *string
  30. PresentationID *string
  31. PresentationName *string
  32. PresentationType *string
  33. ContentBundleID *string
  34. ContentBundleName *string
  35. ContentEntryURL *string
  36. ContentAssetRootURL *string
  37. CurrentPresentationID *string
  38. CurrentPresentationName *string
  39. CurrentPresentationType *string
  40. CurrentContentBundleID *string
  41. CurrentContentBundleName *string
  42. CurrentContentEntryURL *string
  43. CurrentContentAssetRootURL *string
  44. CurrentRuntimeBindingID *string
  45. CurrentPlaceID *string
  46. CurrentMapAssetID *string
  47. CurrentTileReleaseID *string
  48. CurrentCourseSetID *string
  49. CurrentCourseVariantID *string
  50. CurrentCourseVariantName *string
  51. CurrentRuntimeRouteCode *string
  52. }
  53. type CreateAdminEventParams struct {
  54. PublicID string
  55. TenantID *string
  56. Slug string
  57. DisplayName string
  58. Summary *string
  59. Status string
  60. }
  61. type UpdateAdminEventParams struct {
  62. EventID string
  63. TenantID *string
  64. Slug string
  65. DisplayName string
  66. Summary *string
  67. Status string
  68. ClearTenant bool
  69. }
  70. func (s *Store) GetTenantByCode(ctx context.Context, tenantCode string) (*Tenant, error) {
  71. row := s.pool.QueryRow(ctx, `
  72. SELECT id, tenant_code, name, status
  73. FROM tenants
  74. WHERE tenant_code = $1
  75. LIMIT 1
  76. `, tenantCode)
  77. var item Tenant
  78. err := row.Scan(&item.ID, &item.TenantCode, &item.Name, &item.Status)
  79. if errors.Is(err, pgx.ErrNoRows) {
  80. return nil, nil
  81. }
  82. if err != nil {
  83. return nil, fmt.Errorf("get tenant by code: %w", err)
  84. }
  85. return &item, nil
  86. }
  87. func (s *Store) ListAdminEvents(ctx context.Context, limit int) ([]AdminEventRecord, error) {
  88. if limit <= 0 || limit > 200 {
  89. limit = 50
  90. }
  91. rows, err := s.pool.Query(ctx, `
  92. SELECT
  93. e.id,
  94. e.event_public_id,
  95. e.tenant_id,
  96. t.tenant_code,
  97. t.name,
  98. e.slug,
  99. e.display_name,
  100. e.summary,
  101. e.status,
  102. e.current_release_id,
  103. er.release_public_id,
  104. er.config_label,
  105. er.manifest_url,
  106. er.manifest_checksum_sha256,
  107. er.route_code,
  108. ep.presentation_public_id,
  109. ep.name,
  110. ep.presentation_type,
  111. cb.content_bundle_public_id,
  112. cb.name,
  113. cb.entry_url,
  114. cb.asset_root_url,
  115. epc.presentation_public_id,
  116. epc.name,
  117. epc.presentation_type,
  118. cbc.content_bundle_public_id,
  119. cbc.name,
  120. cbc.entry_url,
  121. cbc.asset_root_url,
  122. mrb.runtime_binding_public_id,
  123. p.place_public_id,
  124. ma.map_asset_public_id,
  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. FROM events e
  131. LEFT JOIN tenants t ON t.id = e.tenant_id
  132. LEFT JOIN event_releases er ON er.id = e.current_release_id
  133. LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
  134. LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
  135. LEFT JOIN event_presentations epc ON epc.id = e.current_presentation_id
  136. LEFT JOIN content_bundles cbc ON cbc.id = e.current_content_bundle_id
  137. LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
  138. LEFT JOIN places p ON p.id = mrb.place_id
  139. LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
  140. LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
  141. LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
  142. LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
  143. ORDER BY e.created_at DESC
  144. LIMIT $1
  145. `, limit)
  146. if err != nil {
  147. return nil, fmt.Errorf("list admin events: %w", err)
  148. }
  149. defer rows.Close()
  150. items := []AdminEventRecord{}
  151. for rows.Next() {
  152. item, err := scanAdminEventFromRows(rows)
  153. if err != nil {
  154. return nil, err
  155. }
  156. items = append(items, *item)
  157. }
  158. if err := rows.Err(); err != nil {
  159. return nil, fmt.Errorf("iterate admin events: %w", err)
  160. }
  161. return items, nil
  162. }
  163. func (s *Store) GetAdminEventByPublicID(ctx context.Context, eventPublicID string) (*AdminEventRecord, error) {
  164. row := s.pool.QueryRow(ctx, `
  165. SELECT
  166. e.id,
  167. e.event_public_id,
  168. e.tenant_id,
  169. t.tenant_code,
  170. t.name,
  171. e.slug,
  172. e.display_name,
  173. e.summary,
  174. e.status,
  175. e.current_release_id,
  176. er.release_public_id,
  177. er.config_label,
  178. er.manifest_url,
  179. er.manifest_checksum_sha256,
  180. er.route_code,
  181. ep.presentation_public_id,
  182. ep.name,
  183. ep.presentation_type,
  184. cb.content_bundle_public_id,
  185. cb.name,
  186. cb.entry_url,
  187. cb.asset_root_url,
  188. epc.presentation_public_id,
  189. epc.name,
  190. epc.presentation_type,
  191. cbc.content_bundle_public_id,
  192. cbc.name,
  193. cbc.entry_url,
  194. cbc.asset_root_url,
  195. mrb.runtime_binding_public_id,
  196. p.place_public_id,
  197. ma.map_asset_public_id,
  198. tr.tile_release_public_id,
  199. cset.course_set_public_id,
  200. cv.course_variant_public_id,
  201. cv.name,
  202. cv.route_code
  203. FROM events e
  204. LEFT JOIN tenants t ON t.id = e.tenant_id
  205. LEFT JOIN event_releases er ON er.id = e.current_release_id
  206. LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
  207. LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
  208. LEFT JOIN event_presentations epc ON epc.id = e.current_presentation_id
  209. LEFT JOIN content_bundles cbc ON cbc.id = e.current_content_bundle_id
  210. LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
  211. LEFT JOIN places p ON p.id = mrb.place_id
  212. LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
  213. LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
  214. LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
  215. LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
  216. WHERE e.event_public_id = $1
  217. LIMIT 1
  218. `, eventPublicID)
  219. return scanAdminEvent(row)
  220. }
  221. func (s *Store) CreateAdminEvent(ctx context.Context, tx Tx, params CreateAdminEventParams) (*AdminEventRecord, error) {
  222. row := tx.QueryRow(ctx, `
  223. INSERT INTO events (tenant_id, event_public_id, slug, display_name, summary, status)
  224. VALUES ($1, $2, $3, $4, $5, $6)
  225. RETURNING id, event_public_id, tenant_id, slug, display_name, summary, status, current_release_id
  226. `, params.TenantID, params.PublicID, params.Slug, params.DisplayName, params.Summary, params.Status)
  227. var item AdminEventRecord
  228. if err := row.Scan(
  229. &item.ID,
  230. &item.PublicID,
  231. &item.TenantID,
  232. &item.Slug,
  233. &item.DisplayName,
  234. &item.Summary,
  235. &item.Status,
  236. &item.CurrentReleaseID,
  237. ); err != nil {
  238. return nil, fmt.Errorf("create admin event: %w", err)
  239. }
  240. return &item, nil
  241. }
  242. func (s *Store) UpdateAdminEvent(ctx context.Context, tx Tx, params UpdateAdminEventParams) (*AdminEventRecord, error) {
  243. row := tx.QueryRow(ctx, `
  244. UPDATE events
  245. SET tenant_id = CASE WHEN $7 THEN NULL ELSE $2 END,
  246. slug = $3,
  247. display_name = $4,
  248. summary = $5,
  249. status = $6
  250. WHERE id = $1
  251. RETURNING id, event_public_id, tenant_id, slug, display_name, summary, status, current_release_id
  252. `, params.EventID, params.TenantID, params.Slug, params.DisplayName, params.Summary, params.Status, params.ClearTenant)
  253. var item AdminEventRecord
  254. if err := row.Scan(
  255. &item.ID,
  256. &item.PublicID,
  257. &item.TenantID,
  258. &item.Slug,
  259. &item.DisplayName,
  260. &item.Summary,
  261. &item.Status,
  262. &item.CurrentReleaseID,
  263. ); err != nil {
  264. return nil, fmt.Errorf("update admin event: %w", err)
  265. }
  266. return &item, nil
  267. }
  268. func scanAdminEvent(row pgx.Row) (*AdminEventRecord, error) {
  269. var item AdminEventRecord
  270. err := row.Scan(
  271. &item.ID,
  272. &item.PublicID,
  273. &item.TenantID,
  274. &item.TenantCode,
  275. &item.TenantName,
  276. &item.Slug,
  277. &item.DisplayName,
  278. &item.Summary,
  279. &item.Status,
  280. &item.CurrentReleaseID,
  281. &item.CurrentReleasePubID,
  282. &item.ConfigLabel,
  283. &item.ManifestURL,
  284. &item.ManifestChecksum,
  285. &item.RouteCode,
  286. &item.PresentationID,
  287. &item.PresentationName,
  288. &item.PresentationType,
  289. &item.ContentBundleID,
  290. &item.ContentBundleName,
  291. &item.ContentEntryURL,
  292. &item.ContentAssetRootURL,
  293. &item.CurrentPresentationID,
  294. &item.CurrentPresentationName,
  295. &item.CurrentPresentationType,
  296. &item.CurrentContentBundleID,
  297. &item.CurrentContentBundleName,
  298. &item.CurrentContentEntryURL,
  299. &item.CurrentContentAssetRootURL,
  300. &item.CurrentRuntimeBindingID,
  301. &item.CurrentPlaceID,
  302. &item.CurrentMapAssetID,
  303. &item.CurrentTileReleaseID,
  304. &item.CurrentCourseSetID,
  305. &item.CurrentCourseVariantID,
  306. &item.CurrentCourseVariantName,
  307. &item.CurrentRuntimeRouteCode,
  308. )
  309. if errors.Is(err, pgx.ErrNoRows) {
  310. return nil, nil
  311. }
  312. if err != nil {
  313. return nil, fmt.Errorf("scan admin event: %w", err)
  314. }
  315. return &item, nil
  316. }
  317. func scanAdminEventFromRows(rows pgx.Rows) (*AdminEventRecord, error) {
  318. var item AdminEventRecord
  319. err := rows.Scan(
  320. &item.ID,
  321. &item.PublicID,
  322. &item.TenantID,
  323. &item.TenantCode,
  324. &item.TenantName,
  325. &item.Slug,
  326. &item.DisplayName,
  327. &item.Summary,
  328. &item.Status,
  329. &item.CurrentReleaseID,
  330. &item.CurrentReleasePubID,
  331. &item.ConfigLabel,
  332. &item.ManifestURL,
  333. &item.ManifestChecksum,
  334. &item.RouteCode,
  335. &item.PresentationID,
  336. &item.PresentationName,
  337. &item.PresentationType,
  338. &item.ContentBundleID,
  339. &item.ContentBundleName,
  340. &item.ContentEntryURL,
  341. &item.ContentAssetRootURL,
  342. &item.CurrentPresentationID,
  343. &item.CurrentPresentationName,
  344. &item.CurrentPresentationType,
  345. &item.CurrentContentBundleID,
  346. &item.CurrentContentBundleName,
  347. &item.CurrentContentEntryURL,
  348. &item.CurrentContentAssetRootURL,
  349. &item.CurrentRuntimeBindingID,
  350. &item.CurrentPlaceID,
  351. &item.CurrentMapAssetID,
  352. &item.CurrentTileReleaseID,
  353. &item.CurrentCourseSetID,
  354. &item.CurrentCourseVariantID,
  355. &item.CurrentCourseVariantName,
  356. &item.CurrentRuntimeRouteCode,
  357. )
  358. if err != nil {
  359. return nil, fmt.Errorf("scan admin event row: %w", err)
  360. }
  361. return &item, nil
  362. }