2026-05-22 23:15:49 +00:00
|
|
|
/**
|
|
|
|
|
* PostgreSQL-specific migrations.
|
|
|
|
|
*
|
|
|
|
|
* Two sets:
|
|
|
|
|
* 1. PUBLIC schema — tenant registry, global admin, runs ONCE.
|
|
|
|
|
* 2. PER-TENANT schema — full BetterFrame table set, runs for each tenant.
|
|
|
|
|
*
|
|
|
|
|
* Key differences from SQLite migrations:
|
|
|
|
|
* - SERIAL / BIGSERIAL instead of AUTOINCREMENT
|
|
|
|
|
* - No STRICT keyword
|
|
|
|
|
* - now() instead of strftime
|
|
|
|
|
* - TEXT works the same (PG TEXT = unbounded)
|
|
|
|
|
* - JSON stored as JSONB for indexing
|
|
|
|
|
* - Boolean is native BOOLEAN, not INTEGER
|
|
|
|
|
*
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
* IMPORTANT: These create the FINAL schema (matching SQLite after all
|
|
|
|
|
* migrations have run). Do not include legacy columns that were dropped.
|
|
|
|
|
*
|
2026-05-22 23:15:49 +00:00
|
|
|
* Migration tracking: schema_migrations table in each schema records
|
|
|
|
|
* which version has been applied. Same PRAGMA user_version concept
|
|
|
|
|
* but in a table since PG has no per-schema PRAGMA.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** Public schema: tenant registry. */
|
|
|
|
|
export const PUBLIC_MIGRATIONS: readonly string[] = [
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS tenants (
|
|
|
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
slug TEXT NOT NULL UNIQUE,
|
|
|
|
|
schema_name TEXT NOT NULL UNIQUE,
|
|
|
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
max_kiosks INTEGER,
|
|
|
|
|
max_cameras INTEGER,
|
|
|
|
|
max_users INTEGER,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS global_admins (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
username TEXT NOT NULL UNIQUE,
|
|
|
|
|
password_hash TEXT NOT NULL,
|
|
|
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
|
|
|
schema_name TEXT NOT NULL,
|
|
|
|
|
version INTEGER NOT NULL,
|
|
|
|
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
PRIMARY KEY (schema_name, version)
|
|
|
|
|
)`,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Per-tenant schema: full BetterFrame table set.
|
|
|
|
|
* These run inside SET search_path = tenant_<id>.
|
|
|
|
|
*
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
* Mirrors the FINAL SQLite schema (after all migrations) with PG-native types.
|
2026-05-22 23:15:49 +00:00
|
|
|
*/
|
|
|
|
|
export const TENANT_MIGRATIONS: readonly string[] = [
|
|
|
|
|
// ---- users ---------------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS users (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
username TEXT NOT NULL UNIQUE,
|
|
|
|
|
password_hash TEXT NOT NULL,
|
|
|
|
|
role TEXT NOT NULL DEFAULT 'operator' CHECK(role IN ('admin', 'operator')),
|
|
|
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
totp_enabled BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
totp_secret_encrypted TEXT,
|
|
|
|
|
recovery_codes_hashed JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
must_change_password BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
failed_login_count INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
locked_until TIMESTAMPTZ,
|
|
|
|
|
last_login_at TIMESTAMPTZ,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
|
|
|
|
|
// ---- sessions ------------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS sessions (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
csrf_token TEXT NOT NULL,
|
|
|
|
|
totp_pending BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
user_agent TEXT,
|
|
|
|
|
ip_address TEXT,
|
|
|
|
|
issued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
revoked_at TIMESTAMPTZ
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_sessions_active ON sessions(expires_at) WHERE revoked_at IS NULL`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- api_keys ------------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS api_keys (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
key_hash TEXT NOT NULL,
|
|
|
|
|
key_prefix TEXT NOT NULL,
|
|
|
|
|
scopes JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
expires_at TIMESTAMPTZ,
|
|
|
|
|
last_used_at TIMESTAMPTZ,
|
|
|
|
|
last_used_ip TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
revoked_at TIMESTAMPTZ
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(key_prefix)`,
|
|
|
|
|
|
|
|
|
|
// ---- setup_state ---------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS setup_state (
|
|
|
|
|
id INTEGER PRIMARY KEY CHECK(id = 1),
|
|
|
|
|
is_complete BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
cluster_key_provisioned BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
nodered_flows_deployed BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
completed_at TIMESTAMPTZ,
|
|
|
|
|
extras JSONB NOT NULL DEFAULT '{}'
|
|
|
|
|
)`,
|
|
|
|
|
`INSERT INTO setup_state (id) VALUES (1) ON CONFLICT DO NOTHING`,
|
|
|
|
|
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
// ---- displays (final schema — no UNIQUE on index, has kiosk_id) ----------
|
2026-05-22 23:15:49 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS displays (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
name TEXT NOT NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
"index" INTEGER NOT NULL,
|
2026-05-22 23:15:49 +00:00
|
|
|
is_primary BOOLEAN NOT NULL DEFAULT false,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
kiosk_id TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
width_px INTEGER NOT NULL DEFAULT 1920,
|
|
|
|
|
height_px INTEGER NOT NULL DEFAULT 1080,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
default_layout_id TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
idle_timeout_seconds INTEGER NOT NULL DEFAULT 600,
|
|
|
|
|
sleep_timeout_seconds INTEGER NOT NULL DEFAULT 1800,
|
|
|
|
|
cec_enabled BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
cec_device_path TEXT,
|
|
|
|
|
cec_logical_address INTEGER,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
desired_power_state TEXT NOT NULL DEFAULT 'follow_layout'
|
|
|
|
|
CHECK(desired_power_state IN ('follow_layout', 'on', 'standby')),
|
|
|
|
|
actual_power_state TEXT NOT NULL DEFAULT 'unknown'
|
|
|
|
|
CHECK(actual_power_state IN ('awake', 'standby', 'unknown')),
|
2026-05-22 23:15:49 +00:00
|
|
|
actual_power_state_at TIMESTAMPTZ,
|
|
|
|
|
state_check_enabled BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
state_check_interval_seconds INTEGER NOT NULL DEFAULT 60,
|
|
|
|
|
is_enabled BOOLEAN NOT NULL DEFAULT true,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
active_layout_id TEXT
|
2026-05-22 23:15:49 +00:00
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_displays_kiosk ON displays(kiosk_id)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_displays_kiosk_index ON displays(kiosk_id, "index")`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- cameras -------------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS cameras (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
name TEXT NOT NULL UNIQUE,
|
2026-05-23 09:36:49 +00:00
|
|
|
type TEXT NOT NULL CHECK(type IN ('rtsp', 'onvif', 'cloud')),
|
2026-05-22 23:15:49 +00:00
|
|
|
rtsp_url TEXT,
|
|
|
|
|
onvif_host TEXT,
|
|
|
|
|
onvif_port INTEGER,
|
|
|
|
|
onvif_username TEXT,
|
|
|
|
|
onvif_password TEXT,
|
|
|
|
|
capabilities JSONB NOT NULL DEFAULT '[]',
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
stream_policy TEXT NOT NULL DEFAULT 'auto'
|
|
|
|
|
CHECK(stream_policy IN ('auto', 'always_main', 'always_sub')),
|
2026-05-22 23:15:49 +00:00
|
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
last_seen_at TIMESTAMPTZ,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
event_source TEXT NOT NULL DEFAULT 'auto',
|
|
|
|
|
event_sink TEXT NOT NULL DEFAULT 'auto',
|
2026-05-23 09:36:49 +00:00
|
|
|
supported_event_topics JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
cloud_account_id TEXT,
|
|
|
|
|
cloud_vendor_camera_id TEXT,
|
|
|
|
|
cloud_stream_url TEXT,
|
|
|
|
|
cloud_stream_type TEXT
|
2026-05-22 23:15:49 +00:00
|
|
|
)`,
|
2026-05-23 09:36:49 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_cameras_cloud_account ON cameras(cloud_account_id)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS camera_streams (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
camera_id TEXT NOT NULL REFERENCES cameras(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
role TEXT NOT NULL CHECK(role IN ('main', 'sub', 'other')),
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
profile_token TEXT,
|
|
|
|
|
rtsp_uri TEXT NOT NULL,
|
2026-05-26 04:51:33 +00:00
|
|
|
rtsp_host TEXT,
|
|
|
|
|
rtsp_port INTEGER DEFAULT 554,
|
|
|
|
|
rtsp_path TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
width INTEGER,
|
|
|
|
|
height INTEGER,
|
|
|
|
|
encoding TEXT,
|
|
|
|
|
framerate REAL,
|
|
|
|
|
bitrate_kbps INTEGER,
|
|
|
|
|
is_discovered BOOLEAN NOT NULL DEFAULT false
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_camera_streams_camera ON camera_streams(camera_id)`,
|
|
|
|
|
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
// ---- kiosks (final schema — all telemetry + update columns) --------------
|
2026-05-22 23:15:49 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS kiosks (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
name TEXT NOT NULL UNIQUE,
|
|
|
|
|
description TEXT,
|
|
|
|
|
key_hash TEXT NOT NULL,
|
|
|
|
|
key_prefix TEXT NOT NULL,
|
|
|
|
|
capabilities JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
hardware_model TEXT,
|
|
|
|
|
os_version TEXT,
|
|
|
|
|
kiosk_app_version TEXT,
|
|
|
|
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
paired_at TIMESTAMPTZ,
|
|
|
|
|
last_seen_at TIMESTAMPTZ,
|
|
|
|
|
last_bundle_version TEXT,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
display_id TEXT REFERENCES displays(id) ON DELETE SET NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
encrypt_key_encrypted TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
cpu_temp_c REAL,
|
|
|
|
|
cpu_load_percent REAL,
|
|
|
|
|
fan_rpm INTEGER,
|
|
|
|
|
fan_pwm INTEGER,
|
|
|
|
|
memory_total_mb INTEGER,
|
|
|
|
|
memory_used_mb INTEGER,
|
|
|
|
|
disk_total_mb INTEGER,
|
|
|
|
|
disk_free_mb INTEGER,
|
|
|
|
|
disk_used_percent REAL,
|
|
|
|
|
firmware_channel TEXT NOT NULL DEFAULT 'stable',
|
|
|
|
|
firmware_target_version TEXT,
|
|
|
|
|
firmware_last_attempt_at TIMESTAMPTZ,
|
|
|
|
|
firmware_last_attempt_version TEXT,
|
|
|
|
|
firmware_last_error TEXT,
|
|
|
|
|
local_key TEXT,
|
|
|
|
|
local_port INTEGER,
|
|
|
|
|
local_last_ip TEXT,
|
|
|
|
|
reported_hostname TEXT,
|
|
|
|
|
network_interfaces_json JSONB,
|
2026-05-26 06:09:20 +00:00
|
|
|
partitions_json JSONB,
|
2026-05-22 23:15:49 +00:00
|
|
|
managed_image BOOLEAN NOT NULL DEFAULT false,
|
|
|
|
|
managed_config_json JSONB,
|
|
|
|
|
managed_config_version INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
managed_config_applied_version INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
managed_config_applied_at TIMESTAMPTZ,
|
|
|
|
|
managed_config_error TEXT,
|
|
|
|
|
os_update_channel TEXT NOT NULL DEFAULT 'stable',
|
|
|
|
|
os_update_target_version TEXT,
|
|
|
|
|
os_update_last_attempt_at TIMESTAMPTZ,
|
|
|
|
|
os_update_last_attempt_version TEXT,
|
|
|
|
|
os_update_last_error TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_kiosks_prefix ON kiosks(key_prefix)`,
|
|
|
|
|
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
// ---- layouts (final schema — no template_id, no display_id) --------------
|
2026-05-22 23:15:49 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS layouts (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
name TEXT NOT NULL UNIQUE,
|
|
|
|
|
description TEXT,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
priority TEXT NOT NULL DEFAULT 'normal' CHECK(priority IN ('hot', 'normal', 'cold')),
|
2026-05-22 23:15:49 +00:00
|
|
|
cooling_timeout_seconds INTEGER,
|
|
|
|
|
preload_camera_ids JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
resets_idle_timer BOOLEAN NOT NULL DEFAULT true
|
|
|
|
|
)`,
|
|
|
|
|
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
// ---- display_layouts (join table) ----------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS display_layouts (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
display_id TEXT NOT NULL REFERENCES displays(id) ON DELETE CASCADE,
|
|
|
|
|
layout_id TEXT NOT NULL REFERENCES layouts(id) ON DELETE CASCADE,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
PRIMARY KEY (display_id, layout_id)
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_display_layouts_layout ON display_layouts(layout_id)`,
|
|
|
|
|
|
|
|
|
|
// ---- layout_cells (final schema — no region_name) ------------------------
|
2026-05-22 23:15:49 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS layout_cells (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
layout_id TEXT NOT NULL REFERENCES layouts(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
"row" INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
col INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
row_span INTEGER NOT NULL DEFAULT 1,
|
|
|
|
|
col_span INTEGER NOT NULL DEFAULT 1,
|
|
|
|
|
content_type TEXT NOT NULL CHECK(content_type IN ('none', 'camera', 'web', 'html')),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
camera_id TEXT REFERENCES cameras(id) ON DELETE SET NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
stream_selector TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
web_url TEXT,
|
|
|
|
|
html_content TEXT,
|
|
|
|
|
cooling_timeout_seconds INTEGER,
|
|
|
|
|
options JSONB NOT NULL DEFAULT '{}',
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
entity_id TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
fit TEXT NOT NULL DEFAULT 'cover'
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_layout_cells_layout ON layout_cells(layout_id)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_layout_cells_entity ON layout_cells(entity_id)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- labels --------------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS labels (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
name TEXT NOT NULL UNIQUE,
|
|
|
|
|
description TEXT,
|
|
|
|
|
color TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS kiosk_labels (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
kiosk_id TEXT NOT NULL REFERENCES kiosks(id) ON DELETE CASCADE,
|
|
|
|
|
label_id TEXT NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
role TEXT NOT NULL CHECK(role IN ('consume', 'operate')),
|
|
|
|
|
PRIMARY KEY (kiosk_id, label_id, role)
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS camera_labels (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
camera_id TEXT NOT NULL REFERENCES cameras(id) ON DELETE CASCADE,
|
|
|
|
|
label_id TEXT NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
PRIMARY KEY (camera_id, label_id)
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS layout_labels (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
layout_id TEXT NOT NULL REFERENCES layouts(id) ON DELETE CASCADE,
|
|
|
|
|
label_id TEXT NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
PRIMARY KEY (layout_id, label_id)
|
|
|
|
|
)`,
|
|
|
|
|
|
|
|
|
|
// ---- pairing codes -------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS pairing_codes (
|
|
|
|
|
code TEXT PRIMARY KEY,
|
|
|
|
|
kiosk_proposed_name TEXT,
|
|
|
|
|
kiosk_hardware_model TEXT,
|
|
|
|
|
kiosk_capabilities JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
issued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
consumed_at TIMESTAMPTZ,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
consumed_by_kiosk_id TEXT REFERENCES kiosks(id) ON DELETE SET NULL,
|
2026-05-22 23:15:49 +00:00
|
|
|
extras JSONB NOT NULL DEFAULT '{}'
|
|
|
|
|
)`,
|
|
|
|
|
|
|
|
|
|
// ---- event_log -----------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS event_log (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
source_kiosk_id TEXT REFERENCES kiosks(id) ON DELETE SET NULL,
|
|
|
|
|
source_camera_id TEXT REFERENCES cameras(id) ON DELETE SET NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
source_type TEXT NOT NULL CHECK(source_type IN ('onvif', 'gpio', 'synthetic', 'system')),
|
2026-05-22 23:15:49 +00:00
|
|
|
topic TEXT NOT NULL,
|
|
|
|
|
property_op TEXT,
|
|
|
|
|
payload JSONB NOT NULL DEFAULT '{}',
|
|
|
|
|
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
forwarded_to_nodered BOOLEAN NOT NULL DEFAULT false
|
|
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_event_log_received ON event_log(received_at DESC)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_event_log_topic ON event_log(topic, received_at DESC)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
|
|
|
|
|
// ---- entities ------------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS entities (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
name TEXT NOT NULL UNIQUE,
|
|
|
|
|
type TEXT NOT NULL CHECK(type IN ('camera', 'html', 'web', 'dashboard')),
|
|
|
|
|
description TEXT,
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
camera_id TEXT REFERENCES cameras(id) ON DELETE CASCADE,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
html_content TEXT,
|
|
|
|
|
web_url TEXT,
|
|
|
|
|
dashboard_id TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_entities_camera ON entities(camera_id)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- firmware releases + rollouts ----------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS firmware_releases (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
version TEXT NOT NULL,
|
|
|
|
|
channel TEXT NOT NULL CHECK(channel IN ('stable', 'beta', 'dev')),
|
|
|
|
|
arch TEXT NOT NULL,
|
|
|
|
|
artifact_path TEXT NOT NULL,
|
|
|
|
|
size_bytes BIGINT NOT NULL,
|
|
|
|
|
sha256 TEXT NOT NULL,
|
|
|
|
|
signature TEXT NOT NULL,
|
|
|
|
|
release_notes TEXT,
|
|
|
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
uploaded_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
yanked_at TIMESTAMPTZ,
|
|
|
|
|
UNIQUE(version, arch)
|
2026-05-22 23:15:49 +00:00
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_firmware_releases_channel ON firmware_releases(channel, arch, uploaded_at DESC)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS firmware_rollouts (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
release_id TEXT NOT NULL REFERENCES firmware_releases(id) ON DELETE CASCADE,
|
|
|
|
|
target_kiosk_ids JSONB NOT NULL DEFAULT '[]',
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
state TEXT NOT NULL DEFAULT 'queued' CHECK(state IN ('queued', 'active', 'paused', 'complete')),
|
|
|
|
|
percentage INTEGER NOT NULL DEFAULT 100 CHECK(percentage BETWEEN 1 AND 100),
|
2026-05-22 23:15:49 +00:00
|
|
|
started_at TIMESTAMPTZ,
|
|
|
|
|
finished_at TIMESTAMPTZ,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL
|
2026-05-22 23:15:49 +00:00
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_firmware_rollouts_state ON firmware_rollouts(state)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- OS update releases + rollouts --------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS os_update_releases (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
version TEXT NOT NULL,
|
|
|
|
|
channel TEXT NOT NULL CHECK(channel IN ('stable', 'beta', 'dev')),
|
|
|
|
|
compatibility TEXT NOT NULL,
|
|
|
|
|
artifact_path TEXT NOT NULL,
|
|
|
|
|
size_bytes BIGINT NOT NULL,
|
|
|
|
|
sha256 TEXT NOT NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
bundle_format TEXT NOT NULL DEFAULT 'raucb' CHECK(bundle_format = 'raucb'),
|
2026-05-22 23:15:49 +00:00
|
|
|
release_notes TEXT,
|
|
|
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
uploaded_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
2026-05-22 23:15:49 +00:00
|
|
|
yanked_at TIMESTAMPTZ,
|
|
|
|
|
UNIQUE(version, compatibility)
|
|
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_os_update_releases_channel ON os_update_releases(channel, compatibility, uploaded_at DESC)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS os_update_rollouts (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
release_id TEXT NOT NULL REFERENCES os_update_releases(id) ON DELETE CASCADE,
|
|
|
|
|
target_kiosk_ids JSONB NOT NULL DEFAULT '[]',
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
state TEXT NOT NULL DEFAULT 'queued' CHECK(state IN ('queued', 'active', 'paused', 'complete')),
|
|
|
|
|
percentage INTEGER NOT NULL DEFAULT 100 CHECK(percentage BETWEEN 1 AND 100),
|
2026-05-22 23:15:49 +00:00
|
|
|
started_at TIMESTAMPTZ,
|
|
|
|
|
finished_at TIMESTAMPTZ,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
created_by TEXT REFERENCES users(id) ON DELETE SET NULL
|
2026-05-22 23:15:49 +00:00
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_os_update_rollouts_state ON os_update_rollouts(state)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- audit_log -----------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS audit_log (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
2026-05-22 23:15:49 +00:00
|
|
|
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
actor_type TEXT NOT NULL CHECK(actor_type IN ('user', 'api_key', 'system', 'kiosk')),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
actor_id TEXT,
|
2026-05-22 23:15:49 +00:00
|
|
|
actor_label TEXT,
|
|
|
|
|
action TEXT NOT NULL,
|
|
|
|
|
resource_type TEXT,
|
|
|
|
|
resource_id TEXT,
|
|
|
|
|
ip TEXT,
|
|
|
|
|
metadata JSONB NOT NULL DEFAULT '{}',
|
|
|
|
|
result TEXT NOT NULL DEFAULT 'ok' CHECK(result IN ('ok', 'failed'))
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_log_ts ON audit_log(ts DESC)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_log_action ON audit_log(action, ts DESC)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_log_actor ON audit_log(actor_type, actor_id, ts DESC)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
// ---- kiosk GPIO bindings -------------------------------------------------
|
2026-05-22 23:15:49 +00:00
|
|
|
`CREATE TABLE IF NOT EXISTS kiosk_gpio_bindings (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
kiosk_id TEXT NOT NULL REFERENCES kiosks(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
chip TEXT NOT NULL DEFAULT 'gpiochip4',
|
|
|
|
|
pin INTEGER NOT NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
direction TEXT NOT NULL DEFAULT 'in' CHECK(direction IN ('in', 'out')),
|
|
|
|
|
pull TEXT CHECK(pull IN ('up', 'down', 'none')),
|
|
|
|
|
edge TEXT CHECK(edge IN ('rising', 'falling', 'both')),
|
2026-05-22 23:15:49 +00:00
|
|
|
topic TEXT NOT NULL,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
2026-05-22 23:15:49 +00:00
|
|
|
UNIQUE(kiosk_id, chip, pin)
|
|
|
|
|
)`,
|
fix(db): rewrite PG migrations to match final SQLite schema
PG migrations still had the original table structure (layouts with
template_id/display_id, layout_cells with region_name) that SQLite
dropped in v0.5. PG deploy would fail because repo code expects the
final schema.
Fixes: layouts table (removed template_id/display_id/is_default),
layout_cells (removed region_name), added display_layouts join table,
kiosks.encrypt_key_encrypted, entities.name UNIQUE, all missing
indexes (sessions active, event_log received, audit_log actor,
firmware version/arch unique), foreign keys on pairing_codes/
event_log/firmware/rollouts, kiosk_gpio_bindings.created_at +
CHECK constraints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 01:03:44 +00:00
|
|
|
`CREATE INDEX IF NOT EXISTS idx_kiosk_gpio_bindings_kiosk ON kiosk_gpio_bindings(kiosk_id)`,
|
2026-05-22 23:15:49 +00:00
|
|
|
|
|
|
|
|
// ---- kiosk_logs ----------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS kiosk_logs (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
kiosk_id TEXT NOT NULL REFERENCES kiosks(id) ON DELETE CASCADE,
|
2026-05-22 23:15:49 +00:00
|
|
|
level TEXT NOT NULL CHECK(level IN ('debug', 'info', 'warn', 'error')),
|
|
|
|
|
message TEXT NOT NULL,
|
|
|
|
|
context JSONB NOT NULL DEFAULT '{}',
|
|
|
|
|
logged_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_kiosk_logs_kiosk ON kiosk_logs(kiosk_id, received_at DESC)`,
|
2026-05-23 00:59:27 +00:00
|
|
|
|
|
|
|
|
// ---- cloud_accounts -------------------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS cloud_accounts (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
vendor TEXT NOT NULL,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
credentials_encrypted TEXT NOT NULL,
|
|
|
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
|
last_sync_at TIMESTAMPTZ,
|
|
|
|
|
last_sync_error TEXT,
|
|
|
|
|
camera_count INTEGER NOT NULL DEFAULT 0,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_cloud_accounts_vendor ON cloud_accounts(vendor)`,
|
2026-05-26 00:38:43 +00:00
|
|
|
|
|
|
|
|
// ---- camera_event_subscriptions ---------------------------------------------
|
|
|
|
|
`CREATE TABLE IF NOT EXISTS camera_event_subscriptions (
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
id TEXT NOT NULL PRIMARY KEY,
|
|
|
|
|
camera_id TEXT NOT NULL REFERENCES cameras(id) ON DELETE CASCADE,
|
2026-05-26 00:38:43 +00:00
|
|
|
topic TEXT NOT NULL,
|
|
|
|
|
status TEXT NOT NULL DEFAULT 'inactive' CHECK(status IN ('inactive', 'pending', 'active', 'failed')),
|
refactor: migrate all auto-increment PKs to UUIDv7 text IDs
Replace SERIAL/AUTOINCREMENT integer primary keys with UUIDv7 text
IDs across all 15 entity tables (users, api_keys, displays, cameras,
camera_streams, layouts, layout_cells, entities, kiosks, labels,
kiosk_gpio_bindings, event_log, kiosk_logs, audit_log,
camera_event_subscriptions). SetupState keeps id=1 INTEGER singleton.
Changes:
- types.ts: all id fields number->string, all FK fields number->string
- mappers.ts: n(r["id"])->s(r["id"]) for PKs, nn()->sn() for nullable FKs
- repository.ts: import uuidv7, generate IDs before INSERT, remove
RETURNING id, change all method signatures from number to string
- migrations-pg.ts: SERIAL->TEXT NOT NULL PRIMARY KEY, INTEGER FK->TEXT FK
- bundle.ts: all bundle interface IDs number->string
- pairing.ts, auth.ts: kioskId/userId types number->string
- coordinator-registry.ts: kioskId number->string
- audit.ts: actor_id number->string
- mqtt-bridge.ts: kioskId number->string in publish/subscribe
- All route handlers: Number(getRouterParam)->getRouterParam ?? ""
- admin-pages.tsx: template function params and Map types number->string
- kiosk/src/bundle.rs: flexible serde deserializer that accepts both
u32 (old) and String (new) IDs for backward compatibility
Fresh PG database -- no data migration needed, just schema changes.
SQLite migrations unchanged (dev-only, recreate DB for UUIDv7).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 05:11:45 +00:00
|
|
|
subscribed_by_kiosk_id TEXT REFERENCES kiosks(id) ON DELETE SET NULL,
|
2026-05-26 00:38:43 +00:00
|
|
|
last_event_at TIMESTAMPTZ,
|
|
|
|
|
error_message TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
UNIQUE(camera_id, topic)
|
|
|
|
|
)`,
|
|
|
|
|
`CREATE INDEX IF NOT EXISTS idx_camera_event_subs_camera ON camera_event_subscriptions(camera_id)`,
|
2026-05-26 06:09:20 +00:00
|
|
|
|
|
|
|
|
`ALTER TABLE kiosks ADD COLUMN IF NOT EXISTS partitions_json JSONB`,
|
2026-05-26 11:22:29 +00:00
|
|
|
|
|
|
|
|
// ---- UUIDv7 PK migration for existing databases ----
|
|
|
|
|
// Databases created before UUIDv7 migration have INTEGER PKs.
|
|
|
|
|
// This migration converts them to TEXT in-place. Safe to run on
|
|
|
|
|
// databases that already have TEXT PKs (DO NOTHING on conflict).
|
|
|
|
|
// gen_random_uuid() generates UUIDv4 — close enough for backfill.
|
|
|
|
|
// New rows already use app-generated UUIDv7 from repository.ts.
|
2026-05-26 11:32:18 +00:00
|
|
|
`CREATE OR REPLACE FUNCTION _bf_add_fk(
|
|
|
|
|
src_table text, src_col text, ref_table text, ref_col text, on_del text
|
|
|
|
|
) RETURNS void LANGUAGE plpgsql AS $fn$
|
|
|
|
|
DECLARE
|
|
|
|
|
col_exists boolean;
|
|
|
|
|
ref_exists boolean;
|
|
|
|
|
cname text;
|
|
|
|
|
BEGIN
|
|
|
|
|
SELECT EXISTS(
|
|
|
|
|
SELECT 1 FROM information_schema.columns
|
|
|
|
|
WHERE table_schema = current_schema() AND table_name = src_table AND column_name = src_col
|
|
|
|
|
) INTO col_exists;
|
|
|
|
|
SELECT EXISTS(
|
|
|
|
|
SELECT 1 FROM information_schema.columns
|
|
|
|
|
WHERE table_schema = current_schema() AND table_name = ref_table AND column_name = ref_col
|
|
|
|
|
) INTO ref_exists;
|
|
|
|
|
IF NOT col_exists OR NOT ref_exists THEN RETURN; END IF;
|
|
|
|
|
cname := src_table || '_' || src_col || '_fkey';
|
|
|
|
|
EXECUTE format(
|
|
|
|
|
'ALTER TABLE %I ADD CONSTRAINT %I FOREIGN KEY (%I) REFERENCES %I(%I) ON DELETE %s',
|
|
|
|
|
src_table, cname, src_col, ref_table, ref_col, on_del
|
|
|
|
|
);
|
|
|
|
|
END $fn$`,
|
|
|
|
|
|
2026-05-26 11:22:29 +00:00
|
|
|
`DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
col_type text;
|
2026-05-26 11:26:46 +00:00
|
|
|
r record;
|
2026-05-26 11:22:29 +00:00
|
|
|
BEGIN
|
|
|
|
|
SELECT data_type INTO col_type
|
|
|
|
|
FROM information_schema.columns
|
|
|
|
|
WHERE table_schema = current_schema()
|
|
|
|
|
AND table_name = 'users'
|
|
|
|
|
AND column_name = 'id';
|
|
|
|
|
IF col_type IS NULL OR col_type = 'text' THEN
|
|
|
|
|
RAISE NOTICE 'UUIDv7 migration: already TEXT or table missing, skipping';
|
|
|
|
|
RETURN;
|
|
|
|
|
END IF;
|
|
|
|
|
|
|
|
|
|
RAISE NOTICE 'UUIDv7 migration: converting INTEGER PKs to TEXT...';
|
|
|
|
|
|
2026-05-26 11:26:46 +00:00
|
|
|
-- 1. Drop ALL foreign key constraints in current schema dynamically.
|
|
|
|
|
FOR r IN
|
|
|
|
|
SELECT tc.constraint_name, tc.table_name
|
|
|
|
|
FROM information_schema.table_constraints tc
|
|
|
|
|
WHERE tc.table_schema = current_schema()
|
|
|
|
|
AND tc.constraint_type = 'FOREIGN KEY'
|
|
|
|
|
LOOP
|
|
|
|
|
EXECUTE format('ALTER TABLE %I DROP CONSTRAINT IF EXISTS %I', r.table_name, r.constraint_name);
|
|
|
|
|
END LOOP;
|
|
|
|
|
|
|
|
|
|
-- 2. Convert every integer/bigint column that is a PK or FK to TEXT.
|
|
|
|
|
FOR r IN
|
|
|
|
|
SELECT c.table_name, c.column_name
|
|
|
|
|
FROM information_schema.columns c
|
|
|
|
|
WHERE c.table_schema = current_schema()
|
|
|
|
|
AND c.data_type IN ('integer', 'bigint')
|
|
|
|
|
AND (
|
|
|
|
|
c.column_name = 'id'
|
|
|
|
|
OR c.column_name LIKE '%_id'
|
2026-05-26 11:34:27 +00:00
|
|
|
OR c.column_name LIKE '%_by'
|
2026-05-26 11:26:46 +00:00
|
|
|
)
|
2026-05-26 11:28:53 +00:00
|
|
|
AND c.table_name NOT IN ('schema_migrations', 'setup_state')
|
2026-05-26 11:26:46 +00:00
|
|
|
LOOP
|
|
|
|
|
EXECUTE format('ALTER TABLE %I ALTER COLUMN %I TYPE TEXT USING %I::TEXT',
|
|
|
|
|
r.table_name, r.column_name, r.column_name);
|
|
|
|
|
-- Drop any leftover default (sequences from old SERIAL columns).
|
|
|
|
|
EXECUTE format('ALTER TABLE %I ALTER COLUMN %I DROP DEFAULT', r.table_name, r.column_name);
|
|
|
|
|
END LOOP;
|
|
|
|
|
|
|
|
|
|
-- 3. Drop orphan sequences (leftover from SERIAL columns).
|
|
|
|
|
FOR r IN
|
|
|
|
|
SELECT sequence_name
|
|
|
|
|
FROM information_schema.sequences
|
|
|
|
|
WHERE sequence_schema = current_schema()
|
|
|
|
|
AND sequence_name LIKE '%_id_seq'
|
|
|
|
|
LOOP
|
|
|
|
|
EXECUTE format('DROP SEQUENCE IF EXISTS %I CASCADE', r.sequence_name);
|
|
|
|
|
END LOOP;
|
2026-05-26 11:22:29 +00:00
|
|
|
|
2026-05-26 11:32:18 +00:00
|
|
|
-- 4. Re-add FK constraints (only if both table and column exist).
|
|
|
|
|
PERFORM _bf_add_fk('sessions', 'user_id', 'users', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('api_keys', 'user_id', 'users', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('camera_streams', 'camera_id', 'cameras', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('display_layouts','display_id', 'displays', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('display_layouts','layout_id', 'layouts', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('layout_cells', 'layout_id', 'layouts', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('layout_cells', 'camera_id', 'cameras', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('kiosks', 'display_id', 'displays', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('kiosk_labels', 'kiosk_id', 'kiosks', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('kiosk_labels', 'label_id', 'labels', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('camera_labels', 'camera_id', 'cameras', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('camera_labels', 'label_id', 'labels', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('layout_labels', 'layout_id', 'layouts', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('layout_labels', 'label_id', 'labels', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('event_log', 'source_kiosk_id', 'kiosks', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('event_log', 'source_camera_id', 'cameras', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('kiosk_gpio_bindings','kiosk_id', 'kiosks', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('kiosk_logs', 'kiosk_id', 'kiosks', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('camera_event_subscriptions','camera_id', 'cameras', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('camera_event_subscriptions','subscribed_by_kiosk_id','kiosks', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('displays', 'default_layout_id', 'layouts', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('pairing_codes', 'consumed_by_kiosk_id', 'kiosks', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('firmware_releases','uploaded_by', 'users', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('firmware_rollouts','release_id', 'firmware_releases', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('firmware_rollouts','created_by', 'users', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('os_update_releases','uploaded_by', 'users', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('os_update_rollouts','release_id', 'os_update_releases', 'id', 'CASCADE');
|
|
|
|
|
PERFORM _bf_add_fk('os_update_rollouts','created_by', 'users', 'id', 'SET NULL');
|
|
|
|
|
PERFORM _bf_add_fk('entities', 'camera_id', 'cameras', 'id', 'CASCADE');
|
2026-05-26 11:22:29 +00:00
|
|
|
|
|
|
|
|
RAISE NOTICE 'UUIDv7 migration: complete — all PKs and FKs are now TEXT';
|
|
|
|
|
END $$`,
|
2026-05-26 11:38:55 +00:00
|
|
|
|
|
|
|
|
// ---- Backfill: replace bare-integer IDs with real UUIDv7 ----
|
|
|
|
|
// Existing rows have IDs like "1", "2" from the type conversion.
|
|
|
|
|
// This replaces them with proper UUIDv7-shaped UUIDs while updating
|
|
|
|
|
// all FK references so nothing breaks.
|
|
|
|
|
`DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
r record;
|
|
|
|
|
old_id text;
|
|
|
|
|
new_id text;
|
|
|
|
|
fk record;
|
|
|
|
|
BEGIN
|
|
|
|
|
-- Process each table that has a TEXT PK column named 'id'
|
|
|
|
|
-- where any value looks like a bare integer (no hyphens/letters).
|
|
|
|
|
FOR r IN
|
|
|
|
|
SELECT t.table_name
|
|
|
|
|
FROM information_schema.columns t
|
|
|
|
|
WHERE t.table_schema = current_schema()
|
|
|
|
|
AND t.column_name = 'id'
|
|
|
|
|
AND t.data_type = 'text'
|
|
|
|
|
AND t.table_name NOT IN ('schema_migrations', 'setup_state', 'pairing_codes', 'sessions')
|
|
|
|
|
ORDER BY t.table_name
|
|
|
|
|
LOOP
|
|
|
|
|
-- For each row with an integer-looking id, replace it.
|
|
|
|
|
FOR old_id IN
|
|
|
|
|
EXECUTE format('SELECT id FROM %I WHERE id ~ $1', r.table_name)
|
|
|
|
|
USING '^[0-9]+$'
|
|
|
|
|
LOOP
|
|
|
|
|
new_id := gen_random_uuid()::text;
|
|
|
|
|
|
|
|
|
|
-- Update all FK columns in other tables that reference this id.
|
|
|
|
|
FOR fk IN
|
|
|
|
|
SELECT
|
|
|
|
|
ccu.table_name AS ref_table,
|
|
|
|
|
kcu.table_name AS fk_table,
|
|
|
|
|
kcu.column_name AS fk_column
|
|
|
|
|
FROM information_schema.table_constraints tc
|
|
|
|
|
JOIN information_schema.key_column_usage kcu
|
|
|
|
|
ON tc.constraint_name = kcu.constraint_name
|
|
|
|
|
AND tc.table_schema = kcu.table_schema
|
|
|
|
|
JOIN information_schema.constraint_column_usage ccu
|
|
|
|
|
ON tc.constraint_name = ccu.constraint_name
|
|
|
|
|
AND tc.table_schema = ccu.table_schema
|
|
|
|
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
|
|
|
AND tc.table_schema = current_schema()
|
|
|
|
|
AND ccu.table_name = r.table_name
|
|
|
|
|
AND ccu.column_name = 'id'
|
|
|
|
|
LOOP
|
|
|
|
|
EXECUTE format('UPDATE %I SET %I = $1 WHERE %I = $2',
|
|
|
|
|
fk.fk_table, fk.fk_column, fk.fk_column)
|
|
|
|
|
USING new_id, old_id;
|
|
|
|
|
END LOOP;
|
|
|
|
|
|
|
|
|
|
-- Update the PK itself.
|
|
|
|
|
EXECUTE format('UPDATE %I SET id = $1 WHERE id = $2', r.table_name)
|
|
|
|
|
USING new_id, old_id;
|
|
|
|
|
END LOOP;
|
|
|
|
|
END LOOP;
|
|
|
|
|
|
|
|
|
|
RAISE NOTICE 'UUIDv7 backfill: all integer-looking IDs replaced with UUIDs';
|
|
|
|
|
END $$`,
|
2026-05-22 23:15:49 +00:00
|
|
|
];
|