完善活动运营域与联调标准化

This commit is contained in:
2026-04-03 13:11:41 +08:00
parent 0e28f70bad
commit 129ea935db
56 changed files with 11004 additions and 196 deletions

View File

@@ -16,21 +16,43 @@ type Tenant struct {
}
type AdminEventRecord struct {
ID string
PublicID string
TenantID *string
TenantCode *string
TenantName *string
Slug string
DisplayName string
Summary *string
Status string
CurrentReleaseID *string
CurrentReleasePubID *string
ConfigLabel *string
ManifestURL *string
ManifestChecksum *string
RouteCode *string
ID string
PublicID string
TenantID *string
TenantCode *string
TenantName *string
Slug string
DisplayName string
Summary *string
Status string
CurrentReleaseID *string
CurrentReleasePubID *string
ConfigLabel *string
ManifestURL *string
ManifestChecksum *string
RouteCode *string
PresentationID *string
PresentationName *string
PresentationType *string
ContentBundleID *string
ContentBundleName *string
ContentEntryURL *string
ContentAssetRootURL *string
CurrentPresentationID *string
CurrentPresentationName *string
CurrentPresentationType *string
CurrentContentBundleID *string
CurrentContentBundleName *string
CurrentContentEntryURL *string
CurrentContentAssetRootURL *string
CurrentRuntimeBindingID *string
CurrentPlaceID *string
CurrentMapAssetID *string
CurrentTileReleaseID *string
CurrentCourseSetID *string
CurrentCourseVariantID *string
CurrentCourseVariantName *string
CurrentRuntimeRouteCode *string
}
type CreateAdminEventParams struct {
@@ -90,10 +112,42 @@ func (s *Store) ListAdminEvents(ctx context.Context, limit int) ([]AdminEventRec
er.config_label,
er.manifest_url,
er.manifest_checksum_sha256,
er.route_code
er.route_code,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url,
epc.presentation_public_id,
epc.name,
epc.presentation_type,
cbc.content_bundle_public_id,
cbc.name,
cbc.entry_url,
cbc.asset_root_url,
mrb.runtime_binding_public_id,
p.place_public_id,
ma.map_asset_public_id,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code
FROM events e
LEFT JOIN tenants t ON t.id = e.tenant_id
LEFT JOIN event_releases er ON er.id = e.current_release_id
LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
LEFT JOIN event_presentations epc ON epc.id = e.current_presentation_id
LEFT JOIN content_bundles cbc ON cbc.id = e.current_content_bundle_id
LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
ORDER BY e.created_at DESC
LIMIT $1
`, limit)
@@ -133,10 +187,42 @@ func (s *Store) GetAdminEventByPublicID(ctx context.Context, eventPublicID strin
er.config_label,
er.manifest_url,
er.manifest_checksum_sha256,
er.route_code
er.route_code,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url,
epc.presentation_public_id,
epc.name,
epc.presentation_type,
cbc.content_bundle_public_id,
cbc.name,
cbc.entry_url,
cbc.asset_root_url,
mrb.runtime_binding_public_id,
p.place_public_id,
ma.map_asset_public_id,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code
FROM events e
LEFT JOIN tenants t ON t.id = e.tenant_id
LEFT JOIN event_releases er ON er.id = e.current_release_id
LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
LEFT JOIN event_presentations epc ON epc.id = e.current_presentation_id
LEFT JOIN content_bundles cbc ON cbc.id = e.current_content_bundle_id
LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
WHERE e.event_public_id = $1
LIMIT 1
`, eventPublicID)
@@ -212,6 +298,28 @@ func scanAdminEvent(row pgx.Row) (*AdminEventRecord, error) {
&item.ManifestURL,
&item.ManifestChecksum,
&item.RouteCode,
&item.PresentationID,
&item.PresentationName,
&item.PresentationType,
&item.ContentBundleID,
&item.ContentBundleName,
&item.ContentEntryURL,
&item.ContentAssetRootURL,
&item.CurrentPresentationID,
&item.CurrentPresentationName,
&item.CurrentPresentationType,
&item.CurrentContentBundleID,
&item.CurrentContentBundleName,
&item.CurrentContentEntryURL,
&item.CurrentContentAssetRootURL,
&item.CurrentRuntimeBindingID,
&item.CurrentPlaceID,
&item.CurrentMapAssetID,
&item.CurrentTileReleaseID,
&item.CurrentCourseSetID,
&item.CurrentCourseVariantID,
&item.CurrentCourseVariantName,
&item.CurrentRuntimeRouteCode,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
@@ -240,6 +348,28 @@ func scanAdminEventFromRows(rows pgx.Rows) (*AdminEventRecord, error) {
&item.ManifestURL,
&item.ManifestChecksum,
&item.RouteCode,
&item.PresentationID,
&item.PresentationName,
&item.PresentationType,
&item.ContentBundleID,
&item.ContentBundleName,
&item.ContentEntryURL,
&item.ContentAssetRootURL,
&item.CurrentPresentationID,
&item.CurrentPresentationName,
&item.CurrentPresentationType,
&item.CurrentContentBundleID,
&item.CurrentContentBundleName,
&item.CurrentContentEntryURL,
&item.CurrentContentAssetRootURL,
&item.CurrentRuntimeBindingID,
&item.CurrentPlaceID,
&item.CurrentMapAssetID,
&item.CurrentTileReleaseID,
&item.CurrentCourseSetID,
&item.CurrentCourseVariantID,
&item.CurrentCourseVariantName,
&item.CurrentRuntimeRouteCode,
)
if err != nil {
return nil, fmt.Errorf("scan admin event row: %w", err)

View File

@@ -13,6 +13,13 @@ type DemoBootstrapSummary struct {
SourceID string `json:"sourceId"`
BuildID string `json:"buildId"`
CardID string `json:"cardId"`
PlaceID string `json:"placeId"`
MapAssetID string `json:"mapAssetId"`
TileReleaseID string `json:"tileReleaseId"`
CourseSourceID string `json:"courseSourceId"`
CourseSetID string `json:"courseSetId"`
CourseVariantID string `json:"courseVariantId"`
RuntimeBindingID string `json:"runtimeBindingId"`
VariantManualEventID string `json:"variantManualEventId"`
VariantManualRelease string `json:"variantManualReleaseId"`
VariantManualCardID string `json:"variantManualCardId"`
@@ -311,6 +318,156 @@ func (s *Store) EnsureDemoData(ctx context.Context) (*DemoBootstrapSummary, erro
return nil, fmt.Errorf("ensure demo card: %w", err)
}
var placeID, placePublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO places (
place_public_id, code, name, region, status
)
VALUES (
'place_demo_001', 'place-demo-001', 'Demo Park', 'Shanghai', 'active'
)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
region = EXCLUDED.region,
status = EXCLUDED.status
RETURNING id, place_public_id
`).Scan(&placeID, &placePublicID); err != nil {
return nil, fmt.Errorf("ensure demo place: %w", err)
}
var mapAssetID, mapAssetPublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO map_assets (
map_asset_public_id, place_id, code, name, map_type, status
)
VALUES (
'mapasset_demo_001', $1, 'mapasset-demo-001', 'Demo Asset Map', 'standard', 'active'
)
ON CONFLICT (code) DO UPDATE SET
place_id = EXCLUDED.place_id,
name = EXCLUDED.name,
map_type = EXCLUDED.map_type,
status = EXCLUDED.status
RETURNING id, map_asset_public_id
`, placeID).Scan(&mapAssetID, &mapAssetPublicID); err != nil {
return nil, fmt.Errorf("ensure demo map asset: %w", err)
}
var tileReleaseID, tileReleasePublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO tile_releases (
tile_release_public_id, map_asset_id, version_code, status, tile_base_url, meta_url, published_at
)
VALUES (
'tile_demo_001', $1, 'v2026-04-03', 'published',
'https://example.com/tiles/demo/', 'https://example.com/tiles/demo/meta.json', NOW()
)
ON CONFLICT (map_asset_id, version_code) DO UPDATE SET
status = EXCLUDED.status,
tile_base_url = EXCLUDED.tile_base_url,
meta_url = EXCLUDED.meta_url,
published_at = EXCLUDED.published_at
RETURNING id, tile_release_public_id
`, mapAssetID).Scan(&tileReleaseID, &tileReleasePublicID); err != nil {
return nil, fmt.Errorf("ensure demo tile release: %w", err)
}
if _, err := tx.Exec(ctx, `
UPDATE map_assets
SET current_tile_release_id = $2
WHERE id = $1
`, mapAssetID, tileReleaseID); err != nil {
return nil, fmt.Errorf("attach demo tile release: %w", err)
}
var courseSourceID, courseSourcePublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO course_sources (
course_source_public_id, source_type, file_url, import_status
)
VALUES (
'csource_demo_001', 'kml', 'https://example.com/course/demo.kml', 'imported'
)
ON CONFLICT (course_source_public_id) DO UPDATE SET
source_type = EXCLUDED.source_type,
file_url = EXCLUDED.file_url,
import_status = EXCLUDED.import_status
RETURNING id, course_source_public_id
`).Scan(&courseSourceID, &courseSourcePublicID); err != nil {
return nil, fmt.Errorf("ensure demo course source: %w", err)
}
var courseSetID, courseSetPublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO course_sets (
course_set_public_id, place_id, map_asset_id, code, mode, name, status
)
VALUES (
'cset_demo_001', $1, $2, 'cset-demo-001', 'classic-sequential', 'Demo Course Set', 'active'
)
ON CONFLICT (code) DO UPDATE SET
place_id = EXCLUDED.place_id,
map_asset_id = EXCLUDED.map_asset_id,
mode = EXCLUDED.mode,
name = EXCLUDED.name,
status = EXCLUDED.status
RETURNING id, course_set_public_id
`, placeID, mapAssetID).Scan(&courseSetID, &courseSetPublicID); err != nil {
return nil, fmt.Errorf("ensure demo course set: %w", err)
}
var courseVariantID, courseVariantPublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO course_variants (
course_variant_public_id, course_set_id, source_id, name, route_code, mode, control_count, status, is_default
)
VALUES (
'cvariant_demo_001', $1, $2, 'Demo Variant A', 'route-demo-a', 'classic-sequential', 8, 'active', true
)
ON CONFLICT (course_variant_public_id) DO UPDATE SET
course_set_id = EXCLUDED.course_set_id,
source_id = EXCLUDED.source_id,
name = EXCLUDED.name,
route_code = EXCLUDED.route_code,
mode = EXCLUDED.mode,
control_count = EXCLUDED.control_count,
status = EXCLUDED.status,
is_default = EXCLUDED.is_default
RETURNING id, course_variant_public_id
`, courseSetID, courseSourceID).Scan(&courseVariantID, &courseVariantPublicID); err != nil {
return nil, fmt.Errorf("ensure demo course variant: %w", err)
}
if _, err := tx.Exec(ctx, `
UPDATE course_sets
SET current_variant_id = $2
WHERE id = $1
`, courseSetID, courseVariantID); err != nil {
return nil, fmt.Errorf("attach demo course variant: %w", err)
}
var runtimeBindingID, runtimeBindingPublicID string
if err := tx.QueryRow(ctx, `
INSERT INTO map_runtime_bindings (
runtime_binding_public_id, event_id, place_id, map_asset_id, tile_release_id, course_set_id, course_variant_id, status, notes
)
VALUES (
'runtime_demo_001', $1, $2, $3, $4, $5, $6, 'active', 'demo runtime binding'
)
ON CONFLICT (runtime_binding_public_id) DO UPDATE SET
event_id = EXCLUDED.event_id,
place_id = EXCLUDED.place_id,
map_asset_id = EXCLUDED.map_asset_id,
tile_release_id = EXCLUDED.tile_release_id,
course_set_id = EXCLUDED.course_set_id,
course_variant_id = EXCLUDED.course_variant_id,
status = EXCLUDED.status,
notes = EXCLUDED.notes
RETURNING id, runtime_binding_public_id
`, eventID, placeID, mapAssetID, tileReleaseID, courseSetID, courseVariantID).Scan(&runtimeBindingID, &runtimeBindingPublicID); err != nil {
return nil, fmt.Errorf("ensure demo runtime binding: %w", err)
}
var manualEventID string
if err := tx.QueryRow(ctx, `
INSERT INTO events (
@@ -452,6 +609,13 @@ func (s *Store) EnsureDemoData(ctx context.Context) (*DemoBootstrapSummary, erro
SourceID: source.ID,
BuildID: build.ID,
CardID: cardPublicID,
PlaceID: placePublicID,
MapAssetID: mapAssetPublicID,
TileReleaseID: tileReleasePublicID,
CourseSourceID: courseSourcePublicID,
CourseSetID: courseSetPublicID,
CourseVariantID: courseVariantPublicID,
RuntimeBindingID: runtimeBindingPublicID,
VariantManualEventID: "evt_demo_variant_manual_001",
VariantManualRelease: manualReleaseRow.PublicID,
VariantManualCardID: manualCardPublicID,

View File

@@ -0,0 +1,560 @@
package postgres
import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
)
type EventPresentation struct {
ID string
PublicID string
EventID string
EventPublicID string
Code string
Name string
PresentationType string
Status string
IsDefault bool
SchemaJSON string
CreatedAt string
UpdatedAt string
}
type ContentBundle struct {
ID string
PublicID string
EventID string
EventPublicID string
Code string
Name string
Status string
IsDefault bool
EntryURL *string
AssetRootURL *string
MetadataJSON string
CreatedAt string
UpdatedAt string
}
type CreateEventPresentationParams struct {
PublicID string
EventID string
Code string
Name string
PresentationType string
Status string
IsDefault bool
SchemaJSON string
}
type CreateContentBundleParams struct {
PublicID string
EventID string
Code string
Name string
Status string
IsDefault bool
EntryURL *string
AssetRootURL *string
MetadataJSON string
}
type EventDefaultBindings struct {
EventID string
EventPublicID string
PresentationID *string
PresentationPublicID *string
PresentationName *string
PresentationType *string
ContentBundleID *string
ContentBundlePublicID *string
ContentBundleName *string
ContentEntryURL *string
ContentAssetRootURL *string
RuntimeBindingID *string
RuntimeBindingPublicID *string
PlacePublicID *string
PlaceName *string
MapAssetPublicID *string
MapAssetName *string
TileReleasePublicID *string
CourseSetPublicID *string
CourseVariantPublicID *string
CourseVariantName *string
RuntimeRouteCode *string
}
type SetEventDefaultBindingsParams struct {
EventID string
PresentationID *string
ContentBundleID *string
RuntimeBindingID *string
UpdatePresentation bool
UpdateContent bool
UpdateRuntime bool
}
func (s *Store) ListEventPresentationsByEventID(ctx context.Context, eventID string, limit int) ([]EventPresentation, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.pool.Query(ctx, `
SELECT
ep.id,
ep.presentation_public_id,
ep.event_id,
e.event_public_id,
ep.code,
ep.name,
ep.presentation_type,
ep.status,
ep.is_default,
ep.schema_jsonb::text,
ep.created_at::text,
ep.updated_at::text
FROM event_presentations ep
JOIN events e ON e.id = ep.event_id
WHERE ep.event_id = $1
ORDER BY ep.is_default DESC, ep.created_at DESC
LIMIT $2
`, eventID, limit)
if err != nil {
return nil, fmt.Errorf("list event presentations: %w", err)
}
defer rows.Close()
items := []EventPresentation{}
for rows.Next() {
item, err := scanEventPresentationFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate event presentations: %w", err)
}
return items, nil
}
func (s *Store) GetEventPresentationByPublicID(ctx context.Context, publicID string) (*EventPresentation, error) {
row := s.pool.QueryRow(ctx, `
SELECT
ep.id,
ep.presentation_public_id,
ep.event_id,
e.event_public_id,
ep.code,
ep.name,
ep.presentation_type,
ep.status,
ep.is_default,
ep.schema_jsonb::text,
ep.created_at::text,
ep.updated_at::text
FROM event_presentations ep
JOIN events e ON e.id = ep.event_id
WHERE ep.presentation_public_id = $1
LIMIT 1
`, publicID)
return scanEventPresentation(row)
}
func (s *Store) GetDefaultEventPresentationByEventID(ctx context.Context, eventID string) (*EventPresentation, error) {
row := s.pool.QueryRow(ctx, `
SELECT
ep.id,
ep.presentation_public_id,
ep.event_id,
e.event_public_id,
ep.code,
ep.name,
ep.presentation_type,
ep.status,
ep.is_default,
ep.schema_jsonb::text,
ep.created_at::text,
ep.updated_at::text
FROM event_presentations ep
JOIN events e ON e.id = ep.event_id
WHERE ep.event_id = $1
AND ep.status = 'active'
ORDER BY ep.is_default DESC, ep.updated_at DESC, ep.created_at DESC
LIMIT 1
`, eventID)
return scanEventPresentation(row)
}
func (s *Store) CreateEventPresentation(ctx context.Context, tx Tx, params CreateEventPresentationParams) (*EventPresentation, error) {
row := tx.QueryRow(ctx, `
INSERT INTO event_presentations (
presentation_public_id,
event_id,
code,
name,
presentation_type,
status,
is_default,
schema_jsonb
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)
RETURNING
id,
presentation_public_id,
event_id,
code,
name,
presentation_type,
status,
is_default,
schema_jsonb::text,
created_at::text,
updated_at::text
`, params.PublicID, params.EventID, params.Code, params.Name, params.PresentationType, params.Status, params.IsDefault, params.SchemaJSON)
var item EventPresentation
if err := row.Scan(
&item.ID,
&item.PublicID,
&item.EventID,
&item.Code,
&item.Name,
&item.PresentationType,
&item.Status,
&item.IsDefault,
&item.SchemaJSON,
&item.CreatedAt,
&item.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("create event presentation: %w", err)
}
return &item, nil
}
func (s *Store) GetEventDefaultBindingsByEventID(ctx context.Context, eventID string) (*EventDefaultBindings, error) {
row := s.pool.QueryRow(ctx, `
SELECT
e.id,
e.event_public_id,
e.current_presentation_id,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
e.current_content_bundle_id,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url,
e.current_runtime_binding_id,
mrb.runtime_binding_public_id,
p.place_public_id,
p.name,
ma.map_asset_public_id,
ma.name,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code
FROM events e
LEFT JOIN event_presentations ep ON ep.id = e.current_presentation_id
LEFT JOIN content_bundles cb ON cb.id = e.current_content_bundle_id
LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
WHERE e.id = $1
LIMIT 1
`, eventID)
return scanEventDefaultBindings(row)
}
func (s *Store) SetEventDefaultBindings(ctx context.Context, tx Tx, params SetEventDefaultBindingsParams) error {
if _, err := tx.Exec(ctx, `
UPDATE events
SET current_presentation_id = CASE WHEN $5 THEN $2 ELSE current_presentation_id END,
current_content_bundle_id = CASE WHEN $6 THEN $3 ELSE current_content_bundle_id END,
current_runtime_binding_id = CASE WHEN $7 THEN $4 ELSE current_runtime_binding_id END
WHERE id = $1
`, params.EventID, params.PresentationID, params.ContentBundleID, params.RuntimeBindingID, params.UpdatePresentation, params.UpdateContent, params.UpdateRuntime); err != nil {
return fmt.Errorf("set event default bindings: %w", err)
}
return nil
}
func (s *Store) ListContentBundlesByEventID(ctx context.Context, eventID string, limit int) ([]ContentBundle, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.pool.Query(ctx, `
SELECT
cb.id,
cb.content_bundle_public_id,
cb.event_id,
e.event_public_id,
cb.code,
cb.name,
cb.status,
cb.is_default,
cb.entry_url,
cb.asset_root_url,
cb.metadata_jsonb::text,
cb.created_at::text,
cb.updated_at::text
FROM content_bundles cb
JOIN events e ON e.id = cb.event_id
WHERE cb.event_id = $1
ORDER BY cb.is_default DESC, cb.created_at DESC
LIMIT $2
`, eventID, limit)
if err != nil {
return nil, fmt.Errorf("list content bundles: %w", err)
}
defer rows.Close()
items := []ContentBundle{}
for rows.Next() {
item, err := scanContentBundleFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate content bundles: %w", err)
}
return items, nil
}
func (s *Store) GetContentBundleByPublicID(ctx context.Context, publicID string) (*ContentBundle, error) {
row := s.pool.QueryRow(ctx, `
SELECT
cb.id,
cb.content_bundle_public_id,
cb.event_id,
e.event_public_id,
cb.code,
cb.name,
cb.status,
cb.is_default,
cb.entry_url,
cb.asset_root_url,
cb.metadata_jsonb::text,
cb.created_at::text,
cb.updated_at::text
FROM content_bundles cb
JOIN events e ON e.id = cb.event_id
WHERE cb.content_bundle_public_id = $1
LIMIT 1
`, publicID)
return scanContentBundle(row)
}
func (s *Store) GetDefaultContentBundleByEventID(ctx context.Context, eventID string) (*ContentBundle, error) {
row := s.pool.QueryRow(ctx, `
SELECT
cb.id,
cb.content_bundle_public_id,
cb.event_id,
e.event_public_id,
cb.code,
cb.name,
cb.status,
cb.is_default,
cb.entry_url,
cb.asset_root_url,
cb.metadata_jsonb::text,
cb.created_at::text,
cb.updated_at::text
FROM content_bundles cb
JOIN events e ON e.id = cb.event_id
WHERE cb.event_id = $1
AND cb.status = 'active'
ORDER BY cb.is_default DESC, cb.updated_at DESC, cb.created_at DESC
LIMIT 1
`, eventID)
return scanContentBundle(row)
}
func (s *Store) CreateContentBundle(ctx context.Context, tx Tx, params CreateContentBundleParams) (*ContentBundle, error) {
row := tx.QueryRow(ctx, `
INSERT INTO content_bundles (
content_bundle_public_id,
event_id,
code,
name,
status,
is_default,
entry_url,
asset_root_url,
metadata_jsonb
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb)
RETURNING
id,
content_bundle_public_id,
event_id,
code,
name,
status,
is_default,
entry_url,
asset_root_url,
metadata_jsonb::text,
created_at::text,
updated_at::text
`, params.PublicID, params.EventID, params.Code, params.Name, params.Status, params.IsDefault, params.EntryURL, params.AssetRootURL, params.MetadataJSON)
var item ContentBundle
if err := row.Scan(
&item.ID,
&item.PublicID,
&item.EventID,
&item.Code,
&item.Name,
&item.Status,
&item.IsDefault,
&item.EntryURL,
&item.AssetRootURL,
&item.MetadataJSON,
&item.CreatedAt,
&item.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("create content bundle: %w", err)
}
return &item, nil
}
func scanEventPresentation(row pgx.Row) (*EventPresentation, error) {
var item EventPresentation
err := row.Scan(
&item.ID,
&item.PublicID,
&item.EventID,
&item.EventPublicID,
&item.Code,
&item.Name,
&item.PresentationType,
&item.Status,
&item.IsDefault,
&item.SchemaJSON,
&item.CreatedAt,
&item.UpdatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan event presentation: %w", err)
}
return &item, nil
}
func scanEventPresentationFromRows(rows pgx.Rows) (*EventPresentation, error) {
var item EventPresentation
if err := rows.Scan(
&item.ID,
&item.PublicID,
&item.EventID,
&item.EventPublicID,
&item.Code,
&item.Name,
&item.PresentationType,
&item.Status,
&item.IsDefault,
&item.SchemaJSON,
&item.CreatedAt,
&item.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan event presentation row: %w", err)
}
return &item, nil
}
func scanContentBundle(row pgx.Row) (*ContentBundle, error) {
var item ContentBundle
err := row.Scan(
&item.ID,
&item.PublicID,
&item.EventID,
&item.EventPublicID,
&item.Code,
&item.Name,
&item.Status,
&item.IsDefault,
&item.EntryURL,
&item.AssetRootURL,
&item.MetadataJSON,
&item.CreatedAt,
&item.UpdatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan content bundle: %w", err)
}
return &item, nil
}
func scanContentBundleFromRows(rows pgx.Rows) (*ContentBundle, error) {
var item ContentBundle
if err := rows.Scan(
&item.ID,
&item.PublicID,
&item.EventID,
&item.EventPublicID,
&item.Code,
&item.Name,
&item.Status,
&item.IsDefault,
&item.EntryURL,
&item.AssetRootURL,
&item.MetadataJSON,
&item.CreatedAt,
&item.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan content bundle row: %w", err)
}
return &item, nil
}
func scanEventDefaultBindings(row pgx.Row) (*EventDefaultBindings, error) {
var item EventDefaultBindings
err := row.Scan(
&item.EventID,
&item.EventPublicID,
&item.PresentationID,
&item.PresentationPublicID,
&item.PresentationName,
&item.PresentationType,
&item.ContentBundleID,
&item.ContentBundlePublicID,
&item.ContentBundleName,
&item.ContentEntryURL,
&item.ContentAssetRootURL,
&item.RuntimeBindingID,
&item.RuntimeBindingPublicID,
&item.PlacePublicID,
&item.PlaceName,
&item.MapAssetPublicID,
&item.MapAssetName,
&item.TileReleasePublicID,
&item.CourseSetPublicID,
&item.CourseVariantPublicID,
&item.CourseVariantName,
&item.RuntimeRouteCode,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan event default bindings: %w", err)
}
return &item, nil
}

View File

@@ -23,20 +23,54 @@ type Event struct {
ManifestChecksum *string
RouteCode *string
ReleasePayloadJSON *string
RuntimeBindingID *string
PlacePublicID *string
PlaceName *string
MapAssetPublicID *string
MapAssetName *string
TileReleasePublicID *string
CourseSetPublicID *string
CourseVariantID *string
CourseVariantName *string
RuntimeRouteCode *string
PresentationID *string
PresentationName *string
PresentationType *string
ContentBundleID *string
ContentBundleName *string
ContentEntryURL *string
ContentAssetRootURL *string
}
type EventRelease struct {
ID string
PublicID string
EventID string
ReleaseNo int
ConfigLabel string
ManifestURL string
ManifestChecksum *string
RouteCode *string
BuildID *string
Status string
PublishedAt time.Time
ID string
PublicID string
EventID string
ReleaseNo int
ConfigLabel string
ManifestURL string
ManifestChecksum *string
RouteCode *string
BuildID *string
Status string
PublishedAt time.Time
RuntimeBindingID *string
PlacePublicID *string
PlaceName *string
MapAssetPublicID *string
MapAssetName *string
TileReleaseID *string
CourseSetID *string
CourseVariantID *string
CourseVariantName *string
RuntimeRouteCode *string
PresentationID *string
PresentationName *string
PresentationType *string
ContentBundleID *string
ContentBundleName *string
ContentEntryURL *string
ContentAssetURL *string
}
type CreateGameSessionParams struct {
@@ -85,9 +119,34 @@ func (s *Store) GetEventByPublicID(ctx context.Context, eventPublicID string) (*
er.manifest_url,
er.manifest_checksum_sha256,
er.route_code,
er.payload_jsonb::text
er.payload_jsonb::text,
mrb.runtime_binding_public_id,
p.place_public_id,
p.name,
ma.map_asset_public_id,
ma.name,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url
FROM events e
LEFT JOIN event_releases er ON er.id = e.current_release_id
LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
WHERE e.event_public_id = $1
LIMIT 1
`, eventPublicID)
@@ -107,6 +166,23 @@ func (s *Store) GetEventByPublicID(ctx context.Context, eventPublicID string) (*
&event.ManifestChecksum,
&event.RouteCode,
&event.ReleasePayloadJSON,
&event.RuntimeBindingID,
&event.PlacePublicID,
&event.PlaceName,
&event.MapAssetPublicID,
&event.MapAssetName,
&event.TileReleasePublicID,
&event.CourseSetPublicID,
&event.CourseVariantID,
&event.CourseVariantName,
&event.RuntimeRouteCode,
&event.PresentationID,
&event.PresentationName,
&event.PresentationType,
&event.ContentBundleID,
&event.ContentBundleName,
&event.ContentEntryURL,
&event.ContentAssetRootURL,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
@@ -132,9 +208,34 @@ func (s *Store) GetEventByID(ctx context.Context, eventID string) (*Event, error
er.manifest_url,
er.manifest_checksum_sha256,
er.route_code,
er.payload_jsonb::text
er.payload_jsonb::text,
mrb.runtime_binding_public_id,
p.place_public_id,
p.name,
ma.map_asset_public_id,
ma.name,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url
FROM events e
LEFT JOIN event_releases er ON er.id = e.current_release_id
LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
WHERE e.id = $1
LIMIT 1
`, eventID)
@@ -154,6 +255,23 @@ func (s *Store) GetEventByID(ctx context.Context, eventID string) (*Event, error
&event.ManifestChecksum,
&event.RouteCode,
&event.ReleasePayloadJSON,
&event.RuntimeBindingID,
&event.PlacePublicID,
&event.PlaceName,
&event.MapAssetPublicID,
&event.MapAssetName,
&event.TileReleasePublicID,
&event.CourseSetPublicID,
&event.CourseVariantID,
&event.CourseVariantName,
&event.RuntimeRouteCode,
&event.PresentationID,
&event.PresentationName,
&event.PresentationType,
&event.ContentBundleID,
&event.ContentBundleName,
&event.ContentEntryURL,
&event.ContentAssetRootURL,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
@@ -168,7 +286,7 @@ func (s *Store) NextEventReleaseNo(ctx context.Context, eventID string) (int, er
var next int
if err := s.pool.QueryRow(ctx, `
SELECT COALESCE(MAX(release_no), 0) + 1
FROM event_releases
FROM event_releases er
WHERE event_id = $1
`, eventID).Scan(&next); err != nil {
return 0, fmt.Errorf("next event release no: %w", err)
@@ -185,6 +303,9 @@ type CreateEventReleaseParams struct {
ManifestChecksum *string
RouteCode *string
BuildID *string
RuntimeBindingID *string
PresentationID *string
ContentBundleID *string
Status string
PayloadJSON string
}
@@ -200,12 +321,15 @@ func (s *Store) CreateEventRelease(ctx context.Context, tx Tx, params CreateEven
manifest_checksum_sha256,
route_code,
build_id,
runtime_binding_id,
presentation_id,
content_bundle_id,
status,
payload_jsonb
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13::jsonb)
RETURNING id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at
`, params.PublicID, params.EventID, params.ReleaseNo, params.ConfigLabel, params.ManifestURL, params.ManifestChecksum, params.RouteCode, params.BuildID, params.Status, params.PayloadJSON)
`, 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)
var item EventRelease
if err := row.Scan(
@@ -284,10 +408,46 @@ func (s *Store) ListEventReleasesByEventID(ctx context.Context, eventID string,
limit = 20
}
rows, err := s.pool.Query(ctx, `
SELECT id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at
FROM event_releases
WHERE event_id = $1
ORDER BY release_no DESC
SELECT
er.id,
er.release_public_id,
er.event_id,
er.release_no,
er.config_label,
er.manifest_url,
er.manifest_checksum_sha256,
er.route_code,
er.build_id,
er.status,
er.published_at,
mrb.runtime_binding_public_id,
p.place_public_id,
p.name,
ma.map_asset_public_id,
ma.name,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url
FROM event_releases er
LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
WHERE er.event_id = $1
ORDER BY er.release_no DESC
LIMIT $2
`, eventID, limit)
if err != nil {
@@ -311,9 +471,45 @@ func (s *Store) ListEventReleasesByEventID(ctx context.Context, eventID string,
func (s *Store) GetEventReleaseByPublicID(ctx context.Context, releasePublicID string) (*EventRelease, error) {
row := s.pool.QueryRow(ctx, `
SELECT id, release_public_id, event_id, release_no, config_label, manifest_url, manifest_checksum_sha256, route_code, build_id, status, published_at
FROM event_releases
WHERE release_public_id = $1
SELECT
er.id,
er.release_public_id,
er.event_id,
er.release_no,
er.config_label,
er.manifest_url,
er.manifest_checksum_sha256,
er.route_code,
er.build_id,
er.status,
er.published_at,
mrb.runtime_binding_public_id,
p.place_public_id,
p.name,
ma.map_asset_public_id,
ma.name,
tr.tile_release_public_id,
cset.course_set_public_id,
cv.course_variant_public_id,
cv.name,
cv.route_code,
ep.presentation_public_id,
ep.name,
ep.presentation_type,
cb.content_bundle_public_id,
cb.name,
cb.entry_url,
cb.asset_root_url
FROM event_releases er
LEFT JOIN map_runtime_bindings mrb ON mrb.id = er.runtime_binding_id
LEFT JOIN places p ON p.id = mrb.place_id
LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
WHERE er.release_public_id = $1
LIMIT 1
`, releasePublicID)
@@ -330,6 +526,23 @@ func (s *Store) GetEventReleaseByPublicID(ctx context.Context, releasePublicID s
&item.BuildID,
&item.Status,
&item.PublishedAt,
&item.RuntimeBindingID,
&item.PlacePublicID,
&item.PlaceName,
&item.MapAssetPublicID,
&item.MapAssetName,
&item.TileReleaseID,
&item.CourseSetID,
&item.CourseVariantID,
&item.CourseVariantName,
&item.RuntimeRouteCode,
&item.PresentationID,
&item.PresentationName,
&item.PresentationType,
&item.ContentBundleID,
&item.ContentBundleName,
&item.ContentEntryURL,
&item.ContentAssetURL,
)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
@@ -354,9 +567,37 @@ func scanEventReleaseFromRows(rows pgx.Rows) (*EventRelease, error) {
&item.BuildID,
&item.Status,
&item.PublishedAt,
&item.RuntimeBindingID,
&item.PlacePublicID,
&item.PlaceName,
&item.MapAssetPublicID,
&item.MapAssetName,
&item.TileReleaseID,
&item.CourseSetID,
&item.CourseVariantID,
&item.CourseVariantName,
&item.RuntimeRouteCode,
&item.PresentationID,
&item.PresentationName,
&item.PresentationType,
&item.ContentBundleID,
&item.ContentBundleName,
&item.ContentEntryURL,
&item.ContentAssetURL,
)
if err != nil {
return nil, fmt.Errorf("scan event release row: %w", err)
}
return &item, nil
}
func (s *Store) SetEventReleaseRuntimeBinding(ctx context.Context, tx Tx, releaseID string, runtimeBindingID *string) error {
if _, err := tx.Exec(ctx, `
UPDATE event_releases
SET runtime_binding_id = $2
WHERE id = $1
`, releaseID, runtimeBindingID); err != nil {
return fmt.Errorf("set event release runtime binding: %w", err)
}
return nil
}

View File

@@ -0,0 +1,822 @@
package postgres
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5"
)
type Place struct {
ID string
PublicID string
Code string
Name string
Region *string
CoverURL *string
Description *string
CenterPoint json.RawMessage
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
type MapAsset struct {
ID string
PublicID string
PlaceID string
LegacyMapID *string
LegacyMapPublicID *string
Code string
Name string
MapType string
CoverURL *string
Description *string
Status string
CurrentTileReleaseID *string
CreatedAt time.Time
UpdatedAt time.Time
}
type TileRelease struct {
ID string
PublicID string
MapAssetID string
LegacyMapVersionID *string
LegacyMapVersionPub *string
VersionCode string
Status string
TileBaseURL string
MetaURL string
PublishedAssetRoot *string
MetadataJSON json.RawMessage
PublishedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type CourseSource struct {
ID string
PublicID string
LegacyPlayfieldVersionID *string
LegacyPlayfieldVersionPub *string
SourceType string
FileURL string
Checksum *string
ParserVersion *string
ImportStatus string
MetadataJSON json.RawMessage
ImportedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type CourseSet struct {
ID string
PublicID string
PlaceID string
MapAssetID string
Code string
Mode string
Name string
Description *string
Status string
CurrentVariantID *string
CreatedAt time.Time
UpdatedAt time.Time
}
type CourseVariant struct {
ID string
PublicID string
CourseSetID string
SourceID *string
SourcePublicID *string
Name string
RouteCode *string
Mode string
ControlCount *int
Difficulty *string
Status string
IsDefault bool
ConfigPatch json.RawMessage
MetadataJSON json.RawMessage
CreatedAt time.Time
UpdatedAt time.Time
}
type MapRuntimeBinding struct {
ID string
PublicID string
EventID string
EventPublicID string
PlaceID string
PlacePublicID string
MapAssetID string
MapAssetPublicID string
TileReleaseID string
TileReleasePublicID string
CourseSetID string
CourseSetPublicID string
CourseVariantID string
CourseVariantPublicID string
Status string
Notes *string
CreatedAt time.Time
UpdatedAt time.Time
}
type CreatePlaceParams struct {
PublicID string
Code string
Name string
Region *string
CoverURL *string
Description *string
CenterPoint map[string]any
Status string
}
type CreateMapAssetParams struct {
PublicID string
PlaceID string
LegacyMapID *string
Code string
Name string
MapType string
CoverURL *string
Description *string
Status string
}
type CreateTileReleaseParams struct {
PublicID string
MapAssetID string
LegacyMapVersionID *string
VersionCode string
Status string
TileBaseURL string
MetaURL string
PublishedAssetRoot *string
MetadataJSON map[string]any
PublishedAt *time.Time
}
type CreateCourseSourceParams struct {
PublicID string
LegacyPlayfieldVersionID *string
SourceType string
FileURL string
Checksum *string
ParserVersion *string
ImportStatus string
MetadataJSON map[string]any
ImportedAt *time.Time
}
type CreateCourseSetParams struct {
PublicID string
PlaceID string
MapAssetID string
Code string
Mode string
Name string
Description *string
Status string
}
type CreateCourseVariantParams struct {
PublicID string
CourseSetID string
SourceID *string
Name string
RouteCode *string
Mode string
ControlCount *int
Difficulty *string
Status string
IsDefault bool
ConfigPatch map[string]any
MetadataJSON map[string]any
}
type CreateMapRuntimeBindingParams struct {
PublicID string
EventID string
PlaceID string
MapAssetID string
TileReleaseID string
CourseSetID string
CourseVariantID string
Status string
Notes *string
}
func (s *Store) ListPlaces(ctx context.Context, limit int) ([]Place, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.pool.Query(ctx, `
SELECT id, place_public_id, code, name, region, cover_url, description, center_point_jsonb::text, status, created_at, updated_at
FROM places
ORDER BY created_at DESC
LIMIT $1
`, limit)
if err != nil {
return nil, fmt.Errorf("list places: %w", err)
}
defer rows.Close()
items := []Place{}
for rows.Next() {
item, err := scanPlaceFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate places: %w", err)
}
return items, nil
}
func (s *Store) GetPlaceByPublicID(ctx context.Context, publicID string) (*Place, error) {
row := s.pool.QueryRow(ctx, `
SELECT id, place_public_id, code, name, region, cover_url, description, center_point_jsonb::text, status, created_at, updated_at
FROM places
WHERE place_public_id = $1
LIMIT 1
`, publicID)
return scanPlace(row)
}
func (s *Store) CreatePlace(ctx context.Context, tx Tx, params CreatePlaceParams) (*Place, error) {
centerPointJSON, err := marshalJSONMap(params.CenterPoint)
if err != nil {
return nil, fmt.Errorf("marshal place center point: %w", err)
}
row := tx.QueryRow(ctx, `
INSERT INTO places (place_public_id, code, name, region, cover_url, description, center_point_jsonb, status)
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8)
RETURNING id, place_public_id, code, name, region, cover_url, description, center_point_jsonb::text, status, created_at, updated_at
`, params.PublicID, params.Code, params.Name, params.Region, params.CoverURL, params.Description, centerPointJSON, params.Status)
return scanPlace(row)
}
func (s *Store) ListMapAssetsByPlaceID(ctx context.Context, placeID string) ([]MapAsset, error) {
rows, err := s.pool.Query(ctx, `
SELECT ma.id, ma.map_asset_public_id, ma.place_id, ma.legacy_map_id, lm.map_public_id, ma.code, ma.name, ma.map_type,
ma.cover_url, ma.description, ma.status, ma.current_tile_release_id, ma.created_at, ma.updated_at
FROM map_assets ma
LEFT JOIN maps lm ON lm.id = ma.legacy_map_id
WHERE ma.place_id = $1
ORDER BY ma.created_at DESC
`, placeID)
if err != nil {
return nil, fmt.Errorf("list map assets: %w", err)
}
defer rows.Close()
items := []MapAsset{}
for rows.Next() {
item, err := scanMapAssetFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate map assets: %w", err)
}
return items, nil
}
func (s *Store) GetMapAssetByPublicID(ctx context.Context, publicID string) (*MapAsset, error) {
row := s.pool.QueryRow(ctx, `
SELECT ma.id, ma.map_asset_public_id, ma.place_id, ma.legacy_map_id, lm.map_public_id, ma.code, ma.name, ma.map_type,
ma.cover_url, ma.description, ma.status, ma.current_tile_release_id, ma.created_at, ma.updated_at
FROM map_assets ma
LEFT JOIN maps lm ON lm.id = ma.legacy_map_id
WHERE ma.map_asset_public_id = $1
LIMIT 1
`, publicID)
return scanMapAsset(row)
}
func (s *Store) CreateMapAsset(ctx context.Context, tx Tx, params CreateMapAssetParams) (*MapAsset, error) {
row := tx.QueryRow(ctx, `
INSERT INTO map_assets (map_asset_public_id, place_id, legacy_map_id, code, name, map_type, cover_url, description, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, map_asset_public_id, place_id, legacy_map_id, NULL::text, code, name, map_type, cover_url, description, status, current_tile_release_id, created_at, updated_at
`, params.PublicID, params.PlaceID, params.LegacyMapID, params.Code, params.Name, params.MapType, params.CoverURL, params.Description, params.Status)
return scanMapAsset(row)
}
func (s *Store) ListTileReleasesByMapAssetID(ctx context.Context, mapAssetID string) ([]TileRelease, error) {
rows, err := s.pool.Query(ctx, `
SELECT tr.id, tr.tile_release_public_id, tr.map_asset_id, tr.legacy_map_version_id, mv.version_public_id,
tr.version_code, tr.status, tr.tile_base_url, tr.meta_url, tr.published_asset_root,
tr.metadata_jsonb::text, tr.published_at, tr.created_at, tr.updated_at
FROM tile_releases tr
LEFT JOIN map_versions mv ON mv.id = tr.legacy_map_version_id
WHERE tr.map_asset_id = $1
ORDER BY tr.created_at DESC
`, mapAssetID)
if err != nil {
return nil, fmt.Errorf("list tile releases: %w", err)
}
defer rows.Close()
items := []TileRelease{}
for rows.Next() {
item, err := scanTileReleaseFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate tile releases: %w", err)
}
return items, nil
}
func (s *Store) GetTileReleaseByPublicID(ctx context.Context, publicID string) (*TileRelease, error) {
row := s.pool.QueryRow(ctx, `
SELECT tr.id, tr.tile_release_public_id, tr.map_asset_id, tr.legacy_map_version_id, mv.version_public_id,
tr.version_code, tr.status, tr.tile_base_url, tr.meta_url, tr.published_asset_root,
tr.metadata_jsonb::text, tr.published_at, tr.created_at, tr.updated_at
FROM tile_releases tr
LEFT JOIN map_versions mv ON mv.id = tr.legacy_map_version_id
WHERE tr.tile_release_public_id = $1
LIMIT 1
`, publicID)
return scanTileRelease(row)
}
func (s *Store) CreateTileRelease(ctx context.Context, tx Tx, params CreateTileReleaseParams) (*TileRelease, error) {
metadataJSON, err := marshalJSONMap(params.MetadataJSON)
if err != nil {
return nil, fmt.Errorf("marshal tile release metadata: %w", err)
}
row := tx.QueryRow(ctx, `
INSERT INTO tile_releases (
tile_release_public_id, map_asset_id, legacy_map_version_id, version_code, status,
tile_base_url, meta_url, published_asset_root, metadata_jsonb, published_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10)
RETURNING id, tile_release_public_id, map_asset_id, legacy_map_version_id, NULL::text, version_code, status,
tile_base_url, meta_url, published_asset_root, metadata_jsonb::text, published_at, created_at, updated_at
`, params.PublicID, params.MapAssetID, params.LegacyMapVersionID, params.VersionCode, params.Status, params.TileBaseURL, params.MetaURL, params.PublishedAssetRoot, metadataJSON, params.PublishedAt)
return scanTileRelease(row)
}
func (s *Store) SetMapAssetCurrentTileRelease(ctx context.Context, tx Tx, mapAssetID, tileReleaseID string) error {
_, err := tx.Exec(ctx, `UPDATE map_assets SET current_tile_release_id = $2 WHERE id = $1`, mapAssetID, tileReleaseID)
if err != nil {
return fmt.Errorf("set map asset current tile release: %w", err)
}
return nil
}
func (s *Store) ListCourseSources(ctx context.Context, limit int) ([]CourseSource, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.pool.Query(ctx, `
SELECT cs.id, cs.course_source_public_id, cs.legacy_playfield_version_id, pv.version_public_id, cs.source_type,
cs.file_url, cs.checksum, cs.parser_version, cs.import_status, cs.metadata_jsonb::text, cs.imported_at, cs.created_at, cs.updated_at
FROM course_sources cs
LEFT JOIN playfield_versions pv ON pv.id = cs.legacy_playfield_version_id
ORDER BY cs.created_at DESC
LIMIT $1
`, limit)
if err != nil {
return nil, fmt.Errorf("list course sources: %w", err)
}
defer rows.Close()
items := []CourseSource{}
for rows.Next() {
item, err := scanCourseSourceFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate course sources: %w", err)
}
return items, nil
}
func (s *Store) GetCourseSourceByPublicID(ctx context.Context, publicID string) (*CourseSource, error) {
row := s.pool.QueryRow(ctx, `
SELECT cs.id, cs.course_source_public_id, cs.legacy_playfield_version_id, pv.version_public_id, cs.source_type,
cs.file_url, cs.checksum, cs.parser_version, cs.import_status, cs.metadata_jsonb::text, cs.imported_at, cs.created_at, cs.updated_at
FROM course_sources cs
LEFT JOIN playfield_versions pv ON pv.id = cs.legacy_playfield_version_id
WHERE cs.course_source_public_id = $1
LIMIT 1
`, publicID)
return scanCourseSource(row)
}
func (s *Store) CreateCourseSource(ctx context.Context, tx Tx, params CreateCourseSourceParams) (*CourseSource, error) {
metadataJSON, err := marshalJSONMap(params.MetadataJSON)
if err != nil {
return nil, fmt.Errorf("marshal course source metadata: %w", err)
}
importedAt := time.Now()
if params.ImportedAt != nil {
importedAt = *params.ImportedAt
}
row := tx.QueryRow(ctx, `
INSERT INTO course_sources (
course_source_public_id, legacy_playfield_version_id, source_type, file_url, checksum,
parser_version, import_status, metadata_jsonb, imported_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)
RETURNING id, course_source_public_id, legacy_playfield_version_id, NULL::text, source_type, file_url,
checksum, parser_version, import_status, metadata_jsonb::text, imported_at, created_at, updated_at
`, params.PublicID, params.LegacyPlayfieldVersionID, params.SourceType, params.FileURL, params.Checksum, params.ParserVersion, params.ImportStatus, metadataJSON, importedAt)
return scanCourseSource(row)
}
func (s *Store) ListCourseSets(ctx context.Context, limit int) ([]CourseSet, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.pool.Query(ctx, `
SELECT id, course_set_public_id, place_id, map_asset_id, code, mode, name, description, status, current_variant_id, created_at, updated_at
FROM course_sets
ORDER BY created_at DESC
LIMIT $1
`, limit)
if err != nil {
return nil, fmt.Errorf("list course sets: %w", err)
}
defer rows.Close()
items := []CourseSet{}
for rows.Next() {
item, err := scanCourseSetFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate course sets: %w", err)
}
return items, nil
}
func (s *Store) ListCourseSetsByMapAssetID(ctx context.Context, mapAssetID string) ([]CourseSet, error) {
rows, err := s.pool.Query(ctx, `
SELECT id, course_set_public_id, place_id, map_asset_id, code, mode, name, description, status, current_variant_id, created_at, updated_at
FROM course_sets
WHERE map_asset_id = $1
ORDER BY created_at DESC
`, mapAssetID)
if err != nil {
return nil, fmt.Errorf("list course sets by map asset: %w", err)
}
defer rows.Close()
items := []CourseSet{}
for rows.Next() {
item, err := scanCourseSetFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate course sets by map asset: %w", err)
}
return items, nil
}
func (s *Store) GetCourseSetByPublicID(ctx context.Context, publicID string) (*CourseSet, error) {
row := s.pool.QueryRow(ctx, `
SELECT id, course_set_public_id, place_id, map_asset_id, code, mode, name, description, status, current_variant_id, created_at, updated_at
FROM course_sets
WHERE course_set_public_id = $1
LIMIT 1
`, publicID)
return scanCourseSet(row)
}
func (s *Store) CreateCourseSet(ctx context.Context, tx Tx, params CreateCourseSetParams) (*CourseSet, error) {
row := tx.QueryRow(ctx, `
INSERT INTO course_sets (course_set_public_id, place_id, map_asset_id, code, mode, name, description, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, course_set_public_id, place_id, map_asset_id, code, mode, name, description, status, current_variant_id, created_at, updated_at
`, params.PublicID, params.PlaceID, params.MapAssetID, params.Code, params.Mode, params.Name, params.Description, params.Status)
return scanCourseSet(row)
}
func (s *Store) ListCourseVariantsByCourseSetID(ctx context.Context, courseSetID string) ([]CourseVariant, error) {
rows, err := s.pool.Query(ctx, `
SELECT cv.id, cv.course_variant_public_id, cv.course_set_id, cv.source_id, cs.course_source_public_id, cv.name, cv.route_code,
cv.mode, cv.control_count, cv.difficulty, cv.status, cv.is_default,
cv.config_patch_jsonb::text, cv.metadata_jsonb::text, cv.created_at, cv.updated_at
FROM course_variants cv
LEFT JOIN course_sources cs ON cs.id = cv.source_id
WHERE cv.course_set_id = $1
ORDER BY cv.created_at DESC
`, courseSetID)
if err != nil {
return nil, fmt.Errorf("list course variants: %w", err)
}
defer rows.Close()
items := []CourseVariant{}
for rows.Next() {
item, err := scanCourseVariantFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate course variants: %w", err)
}
return items, nil
}
func (s *Store) GetCourseVariantByPublicID(ctx context.Context, publicID string) (*CourseVariant, error) {
row := s.pool.QueryRow(ctx, `
SELECT cv.id, cv.course_variant_public_id, cv.course_set_id, cv.source_id, cs.course_source_public_id, cv.name, cv.route_code,
cv.mode, cv.control_count, cv.difficulty, cv.status, cv.is_default,
cv.config_patch_jsonb::text, cv.metadata_jsonb::text, cv.created_at, cv.updated_at
FROM course_variants cv
LEFT JOIN course_sources cs ON cs.id = cv.source_id
WHERE cv.course_variant_public_id = $1
LIMIT 1
`, publicID)
return scanCourseVariant(row)
}
func (s *Store) CreateCourseVariant(ctx context.Context, tx Tx, params CreateCourseVariantParams) (*CourseVariant, error) {
configPatchJSON, err := marshalJSONMap(params.ConfigPatch)
if err != nil {
return nil, fmt.Errorf("marshal course variant config patch: %w", err)
}
metadataJSON, err := marshalJSONMap(params.MetadataJSON)
if err != nil {
return nil, fmt.Errorf("marshal course variant metadata: %w", err)
}
row := tx.QueryRow(ctx, `
INSERT INTO course_variants (
course_variant_public_id, course_set_id, source_id, name, route_code, mode, control_count,
difficulty, status, is_default, config_patch_jsonb, metadata_jsonb
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb, $12::jsonb)
RETURNING id, course_variant_public_id, course_set_id, source_id, NULL::text, name, route_code, mode,
control_count, difficulty, status, is_default, config_patch_jsonb::text, metadata_jsonb::text, created_at, updated_at
`, params.PublicID, params.CourseSetID, params.SourceID, params.Name, params.RouteCode, params.Mode, params.ControlCount, params.Difficulty, params.Status, params.IsDefault, configPatchJSON, metadataJSON)
return scanCourseVariant(row)
}
func (s *Store) SetCourseSetCurrentVariant(ctx context.Context, tx Tx, courseSetID, variantID string) error {
_, err := tx.Exec(ctx, `UPDATE course_sets SET current_variant_id = $2 WHERE id = $1`, courseSetID, variantID)
if err != nil {
return fmt.Errorf("set course set current variant: %w", err)
}
return nil
}
func (s *Store) ListMapRuntimeBindings(ctx context.Context, limit int) ([]MapRuntimeBinding, error) {
if limit <= 0 || limit > 200 {
limit = 50
}
rows, err := s.pool.Query(ctx, `
SELECT mrb.id, mrb.runtime_binding_public_id, mrb.event_id, e.event_public_id, mrb.place_id, p.place_public_id,
mrb.map_asset_id, ma.map_asset_public_id, mrb.tile_release_id, tr.tile_release_public_id,
mrb.course_set_id, cset.course_set_public_id, mrb.course_variant_id, cv.course_variant_public_id,
mrb.status, mrb.notes, mrb.created_at, mrb.updated_at
FROM map_runtime_bindings mrb
JOIN events e ON e.id = mrb.event_id
JOIN places p ON p.id = mrb.place_id
JOIN map_assets ma ON ma.id = mrb.map_asset_id
JOIN tile_releases tr ON tr.id = mrb.tile_release_id
JOIN course_sets cset ON cset.id = mrb.course_set_id
JOIN course_variants cv ON cv.id = mrb.course_variant_id
ORDER BY mrb.created_at DESC
LIMIT $1
`, limit)
if err != nil {
return nil, fmt.Errorf("list runtime bindings: %w", err)
}
defer rows.Close()
items := []MapRuntimeBinding{}
for rows.Next() {
item, err := scanMapRuntimeBindingFromRows(rows)
if err != nil {
return nil, err
}
items = append(items, *item)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate runtime bindings: %w", err)
}
return items, nil
}
func (s *Store) GetMapRuntimeBindingByPublicID(ctx context.Context, publicID string) (*MapRuntimeBinding, error) {
row := s.pool.QueryRow(ctx, `
SELECT mrb.id, mrb.runtime_binding_public_id, mrb.event_id, e.event_public_id, mrb.place_id, p.place_public_id,
mrb.map_asset_id, ma.map_asset_public_id, mrb.tile_release_id, tr.tile_release_public_id,
mrb.course_set_id, cset.course_set_public_id, mrb.course_variant_id, cv.course_variant_public_id,
mrb.status, mrb.notes, mrb.created_at, mrb.updated_at
FROM map_runtime_bindings mrb
JOIN events e ON e.id = mrb.event_id
JOIN places p ON p.id = mrb.place_id
JOIN map_assets ma ON ma.id = mrb.map_asset_id
JOIN tile_releases tr ON tr.id = mrb.tile_release_id
JOIN course_sets cset ON cset.id = mrb.course_set_id
JOIN course_variants cv ON cv.id = mrb.course_variant_id
WHERE mrb.runtime_binding_public_id = $1
LIMIT 1
`, publicID)
return scanMapRuntimeBinding(row)
}
func (s *Store) CreateMapRuntimeBinding(ctx context.Context, tx Tx, params CreateMapRuntimeBindingParams) (*MapRuntimeBinding, error) {
row := tx.QueryRow(ctx, `
INSERT INTO map_runtime_bindings (
runtime_binding_public_id, event_id, place_id, map_asset_id, tile_release_id, course_set_id, course_variant_id, status, notes
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, runtime_binding_public_id, event_id, ''::text, place_id, ''::text, map_asset_id, ''::text,
tile_release_id, ''::text, course_set_id, ''::text, course_variant_id, ''::text,
status, notes, created_at, updated_at
`, params.PublicID, params.EventID, params.PlaceID, params.MapAssetID, params.TileReleaseID, params.CourseSetID, params.CourseVariantID, params.Status, params.Notes)
return scanMapRuntimeBinding(row)
}
func scanPlace(row pgx.Row) (*Place, error) {
var item Place
var centerPoint string
err := row.Scan(&item.ID, &item.PublicID, &item.Code, &item.Name, &item.Region, &item.CoverURL, &item.Description, &centerPoint, &item.Status, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan place: %w", err)
}
item.CenterPoint = json.RawMessage(centerPoint)
return &item, nil
}
func scanPlaceFromRows(rows pgx.Rows) (*Place, error) {
var item Place
var centerPoint string
err := rows.Scan(&item.ID, &item.PublicID, &item.Code, &item.Name, &item.Region, &item.CoverURL, &item.Description, &centerPoint, &item.Status, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan place row: %w", err)
}
item.CenterPoint = json.RawMessage(centerPoint)
return &item, nil
}
func scanMapAsset(row pgx.Row) (*MapAsset, error) {
var item MapAsset
err := row.Scan(&item.ID, &item.PublicID, &item.PlaceID, &item.LegacyMapID, &item.LegacyMapPublicID, &item.Code, &item.Name, &item.MapType, &item.CoverURL, &item.Description, &item.Status, &item.CurrentTileReleaseID, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan map asset: %w", err)
}
return &item, nil
}
func scanMapAssetFromRows(rows pgx.Rows) (*MapAsset, error) {
var item MapAsset
err := rows.Scan(&item.ID, &item.PublicID, &item.PlaceID, &item.LegacyMapID, &item.LegacyMapPublicID, &item.Code, &item.Name, &item.MapType, &item.CoverURL, &item.Description, &item.Status, &item.CurrentTileReleaseID, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan map asset row: %w", err)
}
return &item, nil
}
func scanTileRelease(row pgx.Row) (*TileRelease, error) {
var item TileRelease
var metadataJSON string
err := row.Scan(&item.ID, &item.PublicID, &item.MapAssetID, &item.LegacyMapVersionID, &item.LegacyMapVersionPub, &item.VersionCode, &item.Status, &item.TileBaseURL, &item.MetaURL, &item.PublishedAssetRoot, &metadataJSON, &item.PublishedAt, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan tile release: %w", err)
}
item.MetadataJSON = json.RawMessage(metadataJSON)
return &item, nil
}
func scanTileReleaseFromRows(rows pgx.Rows) (*TileRelease, error) {
var item TileRelease
var metadataJSON string
err := rows.Scan(&item.ID, &item.PublicID, &item.MapAssetID, &item.LegacyMapVersionID, &item.LegacyMapVersionPub, &item.VersionCode, &item.Status, &item.TileBaseURL, &item.MetaURL, &item.PublishedAssetRoot, &metadataJSON, &item.PublishedAt, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan tile release row: %w", err)
}
item.MetadataJSON = json.RawMessage(metadataJSON)
return &item, nil
}
func scanCourseSource(row pgx.Row) (*CourseSource, error) {
var item CourseSource
var metadataJSON string
err := row.Scan(&item.ID, &item.PublicID, &item.LegacyPlayfieldVersionID, &item.LegacyPlayfieldVersionPub, &item.SourceType, &item.FileURL, &item.Checksum, &item.ParserVersion, &item.ImportStatus, &metadataJSON, &item.ImportedAt, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan course source: %w", err)
}
item.MetadataJSON = json.RawMessage(metadataJSON)
return &item, nil
}
func scanCourseSourceFromRows(rows pgx.Rows) (*CourseSource, error) {
var item CourseSource
var metadataJSON string
err := rows.Scan(&item.ID, &item.PublicID, &item.LegacyPlayfieldVersionID, &item.LegacyPlayfieldVersionPub, &item.SourceType, &item.FileURL, &item.Checksum, &item.ParserVersion, &item.ImportStatus, &metadataJSON, &item.ImportedAt, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan course source row: %w", err)
}
item.MetadataJSON = json.RawMessage(metadataJSON)
return &item, nil
}
func scanCourseSet(row pgx.Row) (*CourseSet, error) {
var item CourseSet
err := row.Scan(&item.ID, &item.PublicID, &item.PlaceID, &item.MapAssetID, &item.Code, &item.Mode, &item.Name, &item.Description, &item.Status, &item.CurrentVariantID, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan course set: %w", err)
}
return &item, nil
}
func scanCourseSetFromRows(rows pgx.Rows) (*CourseSet, error) {
var item CourseSet
err := rows.Scan(&item.ID, &item.PublicID, &item.PlaceID, &item.MapAssetID, &item.Code, &item.Mode, &item.Name, &item.Description, &item.Status, &item.CurrentVariantID, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan course set row: %w", err)
}
return &item, nil
}
func scanCourseVariant(row pgx.Row) (*CourseVariant, error) {
var item CourseVariant
var configPatch string
var metadataJSON string
err := row.Scan(&item.ID, &item.PublicID, &item.CourseSetID, &item.SourceID, &item.SourcePublicID, &item.Name, &item.RouteCode, &item.Mode, &item.ControlCount, &item.Difficulty, &item.Status, &item.IsDefault, &configPatch, &metadataJSON, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan course variant: %w", err)
}
item.ConfigPatch = json.RawMessage(configPatch)
item.MetadataJSON = json.RawMessage(metadataJSON)
return &item, nil
}
func scanCourseVariantFromRows(rows pgx.Rows) (*CourseVariant, error) {
var item CourseVariant
var configPatch string
var metadataJSON string
err := rows.Scan(&item.ID, &item.PublicID, &item.CourseSetID, &item.SourceID, &item.SourcePublicID, &item.Name, &item.RouteCode, &item.Mode, &item.ControlCount, &item.Difficulty, &item.Status, &item.IsDefault, &configPatch, &metadataJSON, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan course variant row: %w", err)
}
item.ConfigPatch = json.RawMessage(configPatch)
item.MetadataJSON = json.RawMessage(metadataJSON)
return &item, nil
}
func scanMapRuntimeBinding(row pgx.Row) (*MapRuntimeBinding, error) {
var item MapRuntimeBinding
err := row.Scan(&item.ID, &item.PublicID, &item.EventID, &item.EventPublicID, &item.PlaceID, &item.PlacePublicID, &item.MapAssetID, &item.MapAssetPublicID, &item.TileReleaseID, &item.TileReleasePublicID, &item.CourseSetID, &item.CourseSetPublicID, &item.CourseVariantID, &item.CourseVariantPublicID, &item.Status, &item.Notes, &item.CreatedAt, &item.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("scan runtime binding: %w", err)
}
return &item, nil
}
func scanMapRuntimeBindingFromRows(rows pgx.Rows) (*MapRuntimeBinding, error) {
var item MapRuntimeBinding
err := rows.Scan(&item.ID, &item.PublicID, &item.EventID, &item.EventPublicID, &item.PlaceID, &item.PlacePublicID, &item.MapAssetID, &item.MapAssetPublicID, &item.TileReleaseID, &item.TileReleasePublicID, &item.CourseSetID, &item.CourseSetPublicID, &item.CourseVariantID, &item.CourseVariantPublicID, &item.Status, &item.Notes, &item.CreatedAt, &item.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("scan runtime binding row: %w", err)
}
return &item, nil
}