Skip to content

fix: coordinated schema migration for audit findings 18-21, 37-39#199

Open
jakebromberg wants to merge 7 commits intomainfrom
fix/schema-migration-batch
Open

fix: coordinated schema migration for audit findings 18-21, 37-39#199
jakebromberg wants to merge 7 commits intomainfrom
fix/schema-migration-batch

Conversation

@jakebromberg
Copy link
Member

Summary

Combines all 7 schema audit fixes into a single coordinated migration (0026_schema_audit_fixes.sql) to avoid ordering conflicts between the individual schema PRs.

Changes in schema.ts

Finding Change
F18 shift_covers.schedule_id: serialinteger (FK should not auto-increment)
F19 flowsheet.play_order: serialinteger (manually managed by reorder ops)
F20 artist_library_crossreference: add .notNull() to both FK columns
F21 show_djs: add uniqueIndex on (show_id, dj_id)
F37 anonymous_devices.deviceId: remove redundant .unique() (keep explicit uniqueIndex)
F38 15 FK relationships: add onDelete: 'cascade' or 'set null'
F39 14 timestamp columns: add { withTimezone: true }

Migration (0026_schema_audit_fixes.sql)

The migration is hand-crafted (not auto-generated) for safety:

  • F18/F19: Drops DEFAULT nextval() and associated sequences
  • F20: ALTER COLUMN SET NOT NULL (verify no NULL rows exist first)
  • F21: Deduplicates any existing rows, then creates unique index
  • F37: Drops the redundant constraint by name
  • F38: Drops and recreates each FK with the correct ON DELETE rule
  • F39: ALTER COLUMN TYPE timestamptz USING col AT TIME ZONE 'America/New_York'

Relationship to individual PRs

The individual schema PRs (#192-#198) contain the schema.ts changes and unit tests but deliberately omit migrations. This PR provides the single migration that covers all of them. The individual PRs should be merged first (for their tests), then this PR merged last.

Pre-deployment checklist

  • Verify no NULL rows in artist_library_crossreference: SELECT count(*) FROM wxyc_schema.artist_library_crossreference WHERE artist_id IS NULL OR library_id IS NULL;
  • Verify no duplicate rows in show_djs: SELECT show_id, dj_id, count(*) FROM wxyc_schema.show_djs GROUP BY show_id, dj_id HAVING count(*) > 1;
  • Run migration against staging first
  • Verify application behavior after timestamp → timestamptz conversion

Test plan

  • schema.ts typechecks cleanly
  • Migration SQL is syntactically valid
  • Run migration against staging database
  • Verify all FK cascade behaviors work as expected
  • Verify timestamp values are preserved correctly after conversion

Made with Cursor

Combines seven schema fixes into a single migration (0026) to avoid
ordering conflicts between individual PRs:

- F18: shift_covers.schedule_id serial → integer (drop auto-increment on FK)
- F19: flowsheet.play_order serial → integer (manually managed, not auto-inc)
- F20: artist_library_crossreference add NOT NULL on both FK columns
- F21: show_djs add unique constraint on (show_id, dj_id)
- F37: anonymous_devices remove redundant .unique() (keep explicit uniqueIndex)
- F38: add ON DELETE cascade/set-null rules to 15 FK relationships
- F39: add withTimezone to all 14 wxyc_schema timestamp columns

Timestamp conversions use AT TIME ZONE 'America/New_York' to preserve
the intended wall-clock times from the station's local timezone.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jakebromberg jakebromberg force-pushed the fix/schema-migration-batch branch from c20558f to 8e069f9 Compare February 27, 2026 06:13
Jake Bromberg added 6 commits February 27, 2026 10:22
play_order changed from serial to integer, requiring explicit values.
Add getNextPlayOrder() helper that computes max(play_order)+1 per show.
Use it in addTrack, startShow, endShow, and DJ join/leave notifications.
The 0024_anonymous_devices.sql migration file existed but had no
corresponding journal entry, so drizzle-kit never executed it. The
0026_schema_audit_fixes migration then failed with "relation
anonymous_devices does not exist" when trying to drop a constraint
on the missing table.

Add journal entry at idx 27 for 0024_anonymous_devices (before the
schema audit fixes at idx 28) so the table is created first.
Use --abort-on-container-exit with docker compose up ci-db-init so
that migration failures are caught immediately instead of silently
continuing with an uninitialized database.
getNextPlayOrder() was scoped per-show (WHERE show_id = X), making
play_order values non-globally-unique. Queries use ORDER BY play_order
DESC globally, so the sequence must be monotonically increasing across
all shows to preserve correct ordering.
… changes

PostgreSQL cannot alter a column's type when a view depends on it.
The migration now drops library_artist_view before converting
library.add_date and library.last_modified to timestamptz, then
recreates the view afterward.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant