-- Phase 7: redesign Notification/NotificationPreference around the -- Notification (message) / NotificationDelivery (one attempt per channel) -- split — see prisma/schema.prisma for the full rationale. Both tables -- were unused scaffolding from Phase 2 (confirmed via a repo-wide grep for -- `prisma.notification`/`prisma.notificationPreference` before writing -- this migration — zero call sites), so these are safe destructive -- ALTERs, not a real data migration. -- DropForeignKey ALTER TABLE `Notification` DROP FOREIGN KEY `Notification_userId_fkey`; -- DropForeignKey ALTER TABLE `NotificationPreference` DROP FOREIGN KEY `NotificationPreference_userId_fkey`; -- DropIndex DROP INDEX `Notification_userId_idx` ON `Notification`; -- DropIndex DROP INDEX `NotificationPreference_userId_category_channel_key` ON `NotificationPreference`; -- AlterTable ALTER TABLE `Notification` DROP COLUMN `body`, DROP COLUMN `category`, DROP COLUMN `channel`, DROP COLUMN `failureReason`, DROP COLUMN `sentAt`, DROP COLUMN `status`, DROP COLUMN `userId`, ADD COLUMN `bodyText` TEXT NOT NULL, ADD COLUMN `businessId` VARCHAR(191) NOT NULL, ADD COLUMN `deepLinkPath` VARCHAR(191) NULL, ADD COLUMN `idempotencyKey` VARCHAR(191) NOT NULL, ADD COLUMN `loyaltyRewardId` VARCHAR(191) NULL, ADD COLUMN `readAt` DATETIME(3) NULL, ADD COLUMN `recipientCustomerId` VARCHAR(191) NULL, ADD COLUMN `recipientUserId` VARCHAR(191) NULL, ADD COLUMN `type` ENUM('BOOKING_CONFIRMED', 'BOOKING_PENDING_APPROVAL', 'BOOKING_APPROVED', 'BOOKING_REMINDER', 'BOOKING_RESCHEDULED', 'BOOKING_CANCELLED', 'BOOKING_BARBER_CHANGED', 'BOOKING_COMPLETED', 'LOYALTY_STAMP_ADDED', 'LOYALTY_REWARD_EARNED', 'LOYALTY_REWARD_REDEEMED', 'LOYALTY_REWARD_EXPIRING', 'STAFF_NEW_APPOINTMENT', 'STAFF_APPOINTMENT_RESCHEDULED', 'STAFF_APPOINTMENT_CANCELLED', 'STAFF_APPOINTMENT_CHECKED_IN', 'STAFF_APPOINTMENT_REMINDER', 'STAFF_SCHEDULE_CHANGED', 'OWNER_NEW_BOOKING', 'OWNER_BOOKING_CANCELLED', 'OWNER_NO_SHOW', 'OWNER_STAFF_INVITATION_ACCEPTED', 'OWNER_REPEATED_DELIVERY_FAILURE') NOT NULL; -- AlterTable ALTER TABLE `NotificationPreference` DROP COLUMN `category`, ADD COLUMN `type` ENUM('BOOKING_CONFIRMED', 'BOOKING_PENDING_APPROVAL', 'BOOKING_APPROVED', 'BOOKING_REMINDER', 'BOOKING_RESCHEDULED', 'BOOKING_CANCELLED', 'BOOKING_BARBER_CHANGED', 'BOOKING_COMPLETED', 'LOYALTY_STAMP_ADDED', 'LOYALTY_REWARD_EARNED', 'LOYALTY_REWARD_REDEEMED', 'LOYALTY_REWARD_EXPIRING', 'STAFF_NEW_APPOINTMENT', 'STAFF_APPOINTMENT_RESCHEDULED', 'STAFF_APPOINTMENT_CANCELLED', 'STAFF_APPOINTMENT_CHECKED_IN', 'STAFF_APPOINTMENT_REMINDER', 'STAFF_SCHEDULE_CHANGED', 'OWNER_NEW_BOOKING', 'OWNER_BOOKING_CANCELLED', 'OWNER_NO_SHOW', 'OWNER_STAFF_INVITATION_ACCEPTED', 'OWNER_REPEATED_DELIVERY_FAILURE') NOT NULL, MODIFY `channel` ENUM('EMAIL', 'PUSH', 'IN_APP') NOT NULL; -- CreateTable CREATE TABLE `NotificationDelivery` ( `id` VARCHAR(191) NOT NULL, `notificationId` VARCHAR(191) NOT NULL, `channel` ENUM('EMAIL', 'PUSH', 'IN_APP') NOT NULL, `status` ENUM('PENDING', 'SCHEDULED', 'PROCESSING', 'SENT', 'FAILED', 'CANCELLED', 'SKIPPED') NOT NULL DEFAULT 'PENDING', `scheduledFor` DATETIME(3) NULL, `attemptCount` INTEGER NOT NULL DEFAULT 0, `lastAttemptedAt` DATETIME(3) NULL, `providerName` VARCHAR(191) NULL, `providerMessageId` VARCHAR(191) NULL, `failureReason` VARCHAR(191) NULL, `sentAt` DATETIME(3) NULL, `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), INDEX `NotificationDelivery_status_scheduledFor_idx`(`status`, `scheduledFor`), UNIQUE INDEX `NotificationDelivery_notificationId_channel_key`(`notificationId`, `channel`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; -- CreateIndex CREATE UNIQUE INDEX `Notification_idempotencyKey_key` ON `Notification`(`idempotencyKey`); -- CreateIndex CREATE INDEX `Notification_businessId_createdAt_idx` ON `Notification`(`businessId`, `createdAt`); -- CreateIndex CREATE INDEX `Notification_recipientUserId_readAt_idx` ON `Notification`(`recipientUserId`, `readAt`); -- CreateIndex CREATE INDEX `Notification_recipientCustomerId_idx` ON `Notification`(`recipientCustomerId`); -- CreateIndex CREATE UNIQUE INDEX `NotificationPreference_userId_type_channel_key` ON `NotificationPreference`(`userId`, `type`, `channel`); -- AddForeignKey ALTER TABLE `Notification` ADD CONSTRAINT `Notification_businessId_fkey` FOREIGN KEY (`businessId`) REFERENCES `Business`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `Notification` ADD CONSTRAINT `Notification_recipientUserId_fkey` FOREIGN KEY (`recipientUserId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `Notification` ADD CONSTRAINT `Notification_recipientCustomerId_fkey` FOREIGN KEY (`recipientCustomerId`) REFERENCES `CustomerProfile`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `Notification` ADD CONSTRAINT `Notification_loyaltyRewardId_fkey` FOREIGN KEY (`loyaltyRewardId`) REFERENCES `LoyaltyReward`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `NotificationDelivery` ADD CONSTRAINT `NotificationDelivery_notificationId_fkey` FOREIGN KEY (`notificationId`) REFERENCES `Notification`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -- Re-adds the FK this migration dropped above (to accommodate the -- category->type column rename) but never restored — caught while writing -- a later migration and diffing against the live dev DB, which showed -- NotificationPreference.userId with no FK at all. Fixed here, in the -- original migration, rather than a separate patch: this migration had -- only ever been applied to this local dev database, never shared. ALTER TABLE `NotificationPreference` ADD CONSTRAINT `NotificationPreference_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;