Add schedule data model and migration

This commit is contained in:
NeilClack
2026-09-11 18:15:57 -05:00
parent 960673bbc6
commit e6b492a335
13 changed files with 5228 additions and 8 deletions
@@ -0,0 +1,53 @@
-- CreateTable
CREATE TABLE "Schedule" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Schedule_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Block" (
"id" TEXT NOT NULL,
"scheduleId" TEXT NOT NULL,
"startMinutes" INTEGER NOT NULL,
"endMinutes" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Block_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BlockProclamations" (
"blockId" TEXT NOT NULL,
"proclamationId" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BlockProclamations_pkey" PRIMARY KEY ("blockId","proclamationId")
);
-- CreateIndex
CREATE UNIQUE INDEX "Schedule_userId_key" ON "Schedule"("userId");
-- CreateIndex
CREATE INDEX "Block_scheduleId_idx" ON "Block"("scheduleId");
-- CreateIndex
CREATE INDEX "BlockProclamations_proclamationId_idx" ON "BlockProclamations"("proclamationId");
-- AddForeignKey
ALTER TABLE "Schedule" ADD CONSTRAINT "Schedule_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Block" ADD CONSTRAINT "Block_scheduleId_fkey" FOREIGN KEY ("scheduleId") REFERENCES "Schedule"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BlockProclamations" ADD CONSTRAINT "BlockProclamations_blockId_fkey" FOREIGN KEY ("blockId") REFERENCES "Block"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BlockProclamations" ADD CONSTRAINT "BlockProclamations_proclamationId_fkey" FOREIGN KEY ("proclamationId") REFERENCES "Proclamation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+48 -1
View File
@@ -28,6 +28,7 @@ model User {
profiles Profile[]
licenseKeys LicenseKey[]
userProclamations UserProclamations[]
schedule Schedule?
@@unique([email])
@@map("user")
@@ -130,6 +131,7 @@ model Proclamation {
groupId Int
group ProclamationGroup @relation(fields: [groupId], references: [id])
userProclamations UserProclamations[]
blockProclamations BlockProclamations[]
// Legacy subscription-level FK. Retained in the database but unmanaged; the
// FK constraint is dropped and the value is left NULL. Hidden from the client.
subscriptionLevelId Int? @ignore
@@ -186,7 +188,7 @@ model ProclamationCategoryLegacy {
// `@@ignore` so Prisma remains aware of the table but it is excluded from the
// generated client and Prisma's own migrations.
model ProclamationCategorySubscriptionLevelLegacy {
categoryId Int
categoryId Int
subscriptionLevelId Int
@@id([categoryId, subscriptionLevelId])
@@ -253,3 +255,48 @@ model AuditLog {
@@index([page])
@@map("AuditLog")
}
// A user's daily listening schedule. Exactly one per user (enforced by the
// unique userId); contains 0-4 time Blocks. Times are wall-clock minutes from
// midnight (0-1440), interpreted by the kiosk in the user's configured
// timezone. The portal never stores a timezone.
model Schedule {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
blocks Block[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// A single non-overlapping time range within a Schedule, plus the ordered set
// of selected Proclamations to play during it. Overnight ranges are disallowed
// in v1, so startMinutes is always less than endMinutes.
model Block {
id String @id @default(uuid())
scheduleId String
schedule Schedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade)
startMinutes Int
endMinutes Int
proclamations BlockProclamations[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([scheduleId])
}
// Join table: the Proclamations selected for a Block. Carries the Proclamation
// content for playback via the API; the portal UI only ever reads
// title/category/group.
model BlockProclamations {
blockId String
block Block @relation(fields: [blockId], references: [id], onDelete: Cascade)
proclamationId String
proclamation Proclamation @relation(fields: [proclamationId], references: [id])
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@id([blockId, proclamationId])
@@index([proclamationId])
}