From ad38363365745ee6765b8d3fae83f7ec24f6f050 Mon Sep 17 00:00:00 2001 From: Janarthanan Soundhararajan Date: Tue, 8 Sep 2026 23:58:11 +0530 Subject: [PATCH 1/3] docs: establish M2 migration foundation --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index 66cd9c9..f3475bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,6 +39,7 @@ Statements about current behavior should be verifiable in code. Proposed behavio | [Deployment architecture](./deployment-architecture.md) | Environments, deployable units, database, releases, secrets, observability, backups, and recovery | Proposed deployment design | | [Roadmap](./roadmap.md) | Vertical delivery milestones, dependencies, decision gates, and completion evidence | Proposed delivery plan | | [M0 baseline reconciliation](./baseline-m0.md) | Validation evidence, environment contracts, migration gaps, data-quality queries, and Server Action conventions | Current baseline | +| [M2 poll-to-meeting migration proposal](./m2-migration-proposal.md) | Staged schema, backfill, integrity, concurrency, rollback, and verification plan for the M2 vertical slice | Proposed for review | | [Content opportunities](./content-opportunities.md) | Reusable learning and publishing ideas discovered during development | Active backlog | ## Articles From 87c929ad676830ef38995e0150b5858b3c827212 Mon Sep 17 00:00:00 2001 From: Janarthanan Soundhararajan Date: Thu, 10 Sep 2026 21:16:39 +0530 Subject: [PATCH 2/3] chore(db): establish Prisma migration baseline --- docs/m2-migration-proposal.md | 373 ++++++++++++++++++ .../20260908000000_baseline/migration.sql | 237 +++++++++++ .../db/prisma/migrations/migration_lock.toml | 3 + 3 files changed, 613 insertions(+) create mode 100644 docs/m2-migration-proposal.md create mode 100644 packages/db/prisma/migrations/20260908000000_baseline/migration.sql create mode 100644 packages/db/prisma/migrations/migration_lock.toml diff --git a/docs/m2-migration-proposal.md b/docs/m2-migration-proposal.md new file mode 100644 index 0000000..76be174 --- /dev/null +++ b/docs/m2-migration-proposal.md @@ -0,0 +1,373 @@ +# M2 poll-to-meeting migration proposal + +**Status:** Approved direction; baseline migration established before M2 schema changes + +**Milestone:** M2 — Poll to Meeting + +## Objective + +Deliver one complete group-scheduling path: + +```text +Create a multi-date poll + -> collect poll-scoped responses + -> select one candidate + -> finalize once + -> create one confirmed meeting + -> reject further voting +``` + +This proposal narrows the broader target model to the smallest migration that satisfies M2 while leaving direct-booking migration, durable notifications, team workspace UX, and calendar providers for later milestones. + +## Current integrity gaps + +The current schema cannot provide the M2 guarantees: + +1. `Availability` repeats participant name and email for every candidate preference. +2. `Availability.pollId` and `Availability.timeSlotId` are individually valid foreign keys, but the database does not prove that the slot belongs to the same poll. +3. Anonymous identity is inferred from a mutable display name. +4. `Poll` has no lifecycle, access policy, duration, organizer timezone, deadline, or concurrency version. +5. `TimeSlot` has no uniqueness constraint for duplicate intervals and no database check that end follows start. +6. No shared `Meeting`, participant snapshot, lifecycle event, or poll-finalization record exists. +7. Two concurrent finalization requests could create two independent outcomes unless the database and transaction claim the poll exactly once. + +## M2 scope decisions + +### Included + +- Personal workspace records as an internal ownership boundary +- Poll lifecycle and open-link access policy +- Multiple candidate dates, explicit duration, and organizer timezone +- Poll-scoped registered and accountless participants +- Required and optional participant classification +- Explicit unanswered semantics: no preference row means unanswered +- Poll management and response progress +- Atomic finalization into one meeting +- Meeting participant snapshots and initial lifecycle event +- Database-enforced cross-poll integrity and one-time finalization + +### Deferred + +- Team workspace creation and workspace switching UI +- Invitation-only token implementation and reminders +- Direct-booking migration to `Meeting` (M3) +- Cancellation and rescheduling lifecycle (M4) +- Durable notification/outbox processing (M5) +- Calendar and conferencing provider integration (M6) +- Fairness and timezone-inconvenience ranking (M7) +- Immersive/VR-specific product behavior + +The schema may reserve stable enum values for later policies, but M2 code must not present deferred behavior as available. + +## Proposed M2 data model + +### Workspace foundation + +`Workspace` provides the tenant boundary without exposing organization UI: + +```text +Workspace +├── id +├── name +├── type = PERSONAL +├── personalOwnerUserId (unique) +├── createdAt +└── updatedAt + +WorkspaceMembership +├── workspaceId +├── userId +├── role = OWNER +├── createdAt +└── unique(workspaceId, userId) +``` + +Every existing and newly authenticated user receives exactly one personal workspace and one owner membership. Provisioning must be idempotent. M2 moves poll ownership to the personal workspace while retaining `hostId` as the acting organizer. Other user-owned resources migrate in their scheduled milestones. + +### Poll and candidates + +```text +Poll +├── id +├── workspaceId +├── hostId +├── title +├── description +├── slug (unique) +├── status: DRAFT | OPEN | FINALIZED | EXPIRED | CANCELLED +├── access: OPEN_LINK | INVITATION_ONLY +├── durationMinutes +├── organizerTimeZone +├── deadlineAt (nullable) +├── version +├── createdAt +└── updatedAt + +PollCandidate +├── id +├── pollId +├── startAt +├── endAt +├── createdAt +├── unique(pollId, startAt, endAt) +└── unique(id, pollId) +``` + +M2 initially creates polls as `OPEN` with `OPEN_LINK` access to preserve the current sharing flow. The lifecycle still models draft and restricted states so later behavior does not require another enum migration. New candidate construction uses the organizer's validated IANA timezone and stores absolute instants. + +### Participants and preferences + +```text +PollParticipant +├── id +├── pollId +├── userId (nullable) +├── displayName +├── email (nullable) +├── role: REQUIRED | OPTIONAL +├── responseStatus: PENDING | RESPONDED +├── respondedAt (nullable) +├── createdAt +├── updatedAt +└── unique(id, pollId) + +PollPreference +├── id +├── pollId +├── participantId +├── candidateId +├── status: YES | IF_NEEDED | NO +├── createdAt +├── updatedAt +├── unique(participantId, candidateId) +├── FK(participantId, pollId) -> PollParticipant(id, pollId) +└── FK(candidateId, pollId) -> PollCandidate(id, pollId) +``` + +Including `pollId` in both composite foreign keys is intentional. It prevents a preference for a participant from poll A and a candidate from poll B even when both IDs exist. Display name remains presentation data rather than identity. + +For open-link accountless responses, M2 issues an opaque edit token after the first response and stores only its hash. A returning anonymous participant must present that token to replace the same response. A matching display name alone must not replace another person's preferences. + +### Meeting and finalization + +```text +Meeting +├── id +├── workspaceId +├── origin = POLL +├── status = CONFIRMED +├── titleSnapshot +├── descriptionSnapshot +├── startAt +├── endAt +├── displayTimeZone +├── locationType (nullable) +├── locationValue (nullable) +├── version +├── createdAt +└── updatedAt + +MeetingParticipant +├── id +├── meetingId +├── pollParticipantId (nullable) +├── userId (nullable) +├── role: HOST | REQUIRED | OPTIONAL +├── displayNameSnapshot +├── emailSnapshot (nullable) +└── unique(meetingId, pollParticipantId) when sourced from a poll + +MeetingEvent +├── id +├── meetingId +├── type = SCHEDULED +├── actorUserId +├── occurredAt +└── metadata (nullable JSON) + +PollFinalization +├── pollId (primary key) +├── candidateId +├── meetingId (unique) +├── finalizedByUserId +├── finalizedAt +├── recommendationSnapshot (nullable JSON) +└── FK(candidateId, pollId) -> PollCandidate(id, pollId) +``` + +Snapshots preserve what was accepted at finalization even if a user profile or poll title changes later. Provider-specific video, calendar, and immersive fields are not added in M2; the nullable general location fields are sufficient until integration design is implemented. + +## Finalization transaction + +The application service, not the Server Action, owns finalization: + +```text +Polling.finalizePoll(command) + -> authenticate and authorize organizer in workspace + -> load OPEN poll and candidate scoped to that poll + -> atomically claim poll with status + version update + -> create Meeting from candidate and poll snapshots + -> create host and participant snapshots + -> create PollFinalization + -> append MeetingEvent(SCHEDULED) + -> commit +``` + +The claim uses an update condition equivalent to: + +```text +poll.id = requestedPollId +AND poll.status = OPEN +AND poll.version = expectedVersion +``` + +Exactly one concurrent request can update that row. A request that loses the race reads `PollFinalization` and returns the already-created meeting as an idempotent success. `PollFinalization.pollId` as the primary key and unique `meetingId` are the final database safeguards. + +No external email, calendar, or conferencing call occurs inside this transaction. + +## Migration and release sequence + +### Gate 0: establish migration authority + +The repository currently has no committed Prisma migration history. Before `prisma migrate dev` or `prisma migrate deploy` is used: + +1. Identify the authoritative development/staging database. +2. Decide whether existing data must be retained or is disposable. +3. If retained, create and review a baseline migration using `prisma migrate diff` rather than pretending the database is empty. +4. Check migration status and schema drift. +5. Back up any database chosen for a representative populated migration test. + +Decision recorded 2026-09-08: the current database contains disposable test data. SlotSyncro will establish a clean migration history from the M1 schema and reset only the explicitly selected development database before applying M2 migrations. No production-data backfill is required for this milestone. The backfill design remains documented because future environments may contain retained data. + +`prisma db push` is not the M2 production migration strategy because it does not provide the reviewed, repeatable history required for backfills and custom constraints. + +### Migration A: additive foundations + +- Add workspace and membership tables. +- Add nullable `Poll.workspaceId` plus lifecycle, access, duration, timezone, deadline, and version fields. +- Add new participant, preference, meeting, meeting-participant, meeting-event, and finalization tables. +- Preserve `TimeSlot` and `Availability` unchanged. +- Deploy code capable of reading the legacy flow while the new fields remain nullable. + +### Backfill A: personal workspaces and polls + +- Create one personal workspace and owner membership per user. +- Set each existing poll's workspace from its host's personal workspace. +- Initialize legacy polls as `OPEN` and `OPEN_LINK`. +- Set legacy duration from `endTime - startTime`; report polls with inconsistent candidate durations. +- Set organizer timezone only through an explicit policy. Do not infer it from the machine running the backfill. + +### Migration B: candidates and normalized responses + +- Copy each legacy `TimeSlot` to `PollCandidate`, preserving IDs where practical. +- Create participants conservatively: + - Group authenticated rows by `(pollId, userId)`. + - Otherwise prefer `(pollId, normalizedEmail)` when a consistent non-empty email exists. + - Use `(pollId, normalizedDisplayName)` only for legacy rows without a durable identifier, matching current replacement behavior. +- Emit an ambiguity report for conflicting names, emails, or user IDs instead of silently merging them. +- Copy one preference per participant and candidate. +- Fail verification if any legacy response points to a candidate from another poll. + +### Application cutover + +- Write new polls, candidates, participants, and preferences only through polling application services. +- Read heatmaps and response progress from normalized preferences. +- Stop name-only anonymous replacement; use the hashed edit token. +- Enforce `OPEN` state and deadline on every response mutation. +- Release poll management and finalization after integration tests pass. + +### Migration C: constraints and cleanup + +- Make backfilled ownership and required lifecycle fields non-null. +- Add composite foreign keys, unique constraints, interval checks, and indexes through reviewed SQL. +- Stop all legacy writes. +- Reconcile row counts and aggregates. +- Remove legacy tables/columns only in a later deployment after rollback no longer depends on them. + +## Backfill verification + +Every backfill records before/after counts and rejects unexplained differences: + +| Check | Expected result | +| --- | --- | +| Existing users vs personal workspaces | One personal workspace per user | +| Existing polls without workspace | Zero after backfill | +| Legacy time slots vs candidates | Equal count unless a documented invalid row is quarantined | +| Legacy vote rows vs preferences | Equal effective preference count after documented duplicate resolution | +| Preferences with mismatched poll relationships | Zero | +| Duplicate participant/candidate preferences | Zero | +| Candidates with `endAt <= startAt` | Zero | +| Finalized polls without finalization/meeting | Zero | +| Poll finalizations with multiple meetings | Zero | + +Backfill code must be rerunnable or protected by stable uniqueness so recovery does not create duplicate workspaces, participants, candidates, or meetings. + +## Rollback and forward-fix strategy + +- Migrations A and B are additive, so rollback means routing application traffic back to legacy reads/writes while retaining unused new tables. +- Do not drop legacy data in the same release that switches application reads. +- If a backfill fails, fix the data or script and rerun it; do not mark an incomplete migration as successfully applied. +- After new-only writes begin, prefer a forward fix. Rolling back application code could lose visibility of newly normalized responses. +- Before destructive cleanup, take a backup and retain reconciliation output and a mapping from legacy IDs to new IDs. +- Production applies committed migrations with `prisma migrate deploy`; it never creates migrations with `migrate dev`. + +## Test evidence required + +### Unit tests + +- IANA timezone validation and local-time-to-instant conversion +- Poll lifecycle transitions +- Candidate duration and duplicate validation +- Required/optional participant snapshot mapping +- Finalization result mapping and stable error codes + +### PostgreSQL integration tests + +- Composite foreign keys reject cross-poll preferences +- Composite finalization relationship rejects a candidate from another poll +- One effective preference exists per participant/candidate +- Concurrent finalization requests produce one meeting and one finalization +- Repeating finalization returns the same meeting +- Poll, meeting, participants, finalization, and event commit or roll back together +- Backfill succeeds on empty and representative populated databases + +### End-to-end tests + +- Organizer creates a multi-date poll and shares it +- Accountless and authenticated participants respond and revise securely +- Organizer sees response progress and finalizes a candidate +- Finalized poll shows the confirmed meeting and rejects further voting +- Double-clicking Finalize produces one visible meeting + +## Review decisions required before schema implementation + +1. **Resolved:** existing non-production data is disposable and may be reset after explicit target verification. +2. Select the authoritative organizer-timezone backfill policy for legacy polls. +3. Confirm that new M2 polls open immediately; draft UI remains deferred. +4. Confirm that invitation-only access is modeled but not exposed until its token and delivery flow is implemented. +5. Confirm anonymous response editing uses an opaque token rather than name/email matching. +6. Confirm direct bookings remain on `Booking` until M3 while poll finalization creates `Meeting` first. + +No Prisma schema or migration should be committed until these decisions and the baseline migration strategy are reviewed. + +## Prisma workflow after approval + +For each migration slice: + +```powershell +pnpm --filter db exec prisma migrate status +pnpm --filter db exec prisma migrate dev --create-only --name +# Review and, where required, edit the generated SQL. +pnpm --filter db exec prisma migrate dev +pnpm --filter db db:generate +``` + +Use `prisma migrate deploy` only in staging/production automation after the same migrations and backfills pass against a representative database. + +## Related documents + +- [Roadmap](./roadmap.md) +- [Business rules](./business-rules.md) +- [Proposed domain decisions](./domain-decisions.md) +- [Proposed domain model](./domain-model-proposed.md) +- [M0 baseline reconciliation](./baseline-m0.md) diff --git a/packages/db/prisma/migrations/20260908000000_baseline/migration.sql b/packages/db/prisma/migrations/20260908000000_baseline/migration.sql new file mode 100644 index 0000000..cbcaff3 --- /dev/null +++ b/packages/db/prisma/migrations/20260908000000_baseline/migration.sql @@ -0,0 +1,237 @@ +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "public"; + +-- CreateEnum +CREATE TYPE "DayOfWeek" AS ENUM ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'); + +-- CreateEnum +CREATE TYPE "BookingStatus" AS ENUM ('PENDING', 'ACCEPTED', 'CANCELLED'); + +-- CreateEnum +CREATE TYPE "AvailabilityStatus" AS ENUM ('YES', 'IF_NEEDED', 'NO'); + +-- CreateEnum +CREATE TYPE "MeetingProvider" AS ENUM ('GOOGLE_MEET', 'ZOOM', 'MICROSOFT_TEAMS', 'PHONE', 'IN_PERSON', 'OTHER'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "name" TEXT, + "email" TEXT, + "emailVerified" TIMESTAMP(3), + "image" TEXT, + "username" TEXT, + "timeZone" TEXT NOT NULL DEFAULT 'UTC', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Account" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "type" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sessionToken" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "VerificationToken" ( + "identifier" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL +); + +-- CreateTable +CREATE TABLE "EventType" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "description" TEXT, + "duration" INTEGER NOT NULL, + "isArchived" BOOLEAN NOT NULL DEFAULT false, + "bufferBefore" INTEGER NOT NULL DEFAULT 0, + "bufferAfter" INTEGER NOT NULL DEFAULT 0, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "EventType_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UserAvailability" ( + "id" TEXT NOT NULL, + "day" "DayOfWeek" NOT NULL, + "isAvailable" BOOLEAN NOT NULL DEFAULT true, + "startTime" TEXT NOT NULL, + "endTime" TEXT NOT NULL, + "slots" JSONB NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "UserAvailability_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Booking" ( + "id" TEXT NOT NULL, + "hostId" TEXT NOT NULL, + "eventTypeId" TEXT NOT NULL, + "guestName" TEXT NOT NULL, + "guestEmail" TEXT NOT NULL, + "guestNotes" TEXT, + "guestTimeZone" TEXT NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "status" "BookingStatus" NOT NULL DEFAULT 'ACCEPTED', + "meetingUrl" TEXT, + "meetingProvider" "MeetingProvider", + "icsUid" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Booking_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Poll" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "slug" TEXT NOT NULL, + "hostId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Poll_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TimeSlot" ( + "id" TEXT NOT NULL, + "pollId" TEXT NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "TimeSlot_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Availability" ( + "id" TEXT NOT NULL, + "pollId" TEXT NOT NULL, + "timeSlotId" TEXT NOT NULL, + "participantName" TEXT NOT NULL, + "participantEmail" TEXT, + "userId" TEXT, + "status" "AvailabilityStatus" NOT NULL DEFAULT 'YES', + + CONSTRAINT "Availability_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); + +-- CreateIndex +CREATE INDEX "User_username_idx" ON "User"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); + +-- CreateIndex +CREATE INDEX "EventType_userId_idx" ON "EventType"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "EventType_userId_slug_key" ON "EventType"("userId", "slug"); + +-- CreateIndex +CREATE INDEX "UserAvailability_userId_idx" ON "UserAvailability"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "UserAvailability_userId_day_key" ON "UserAvailability"("userId", "day"); + +-- CreateIndex +CREATE UNIQUE INDEX "Booking_icsUid_key" ON "Booking"("icsUid"); + +-- CreateIndex +CREATE INDEX "Booking_hostId_idx" ON "Booking"("hostId"); + +-- CreateIndex +CREATE INDEX "Booking_eventTypeId_idx" ON "Booking"("eventTypeId"); + +-- CreateIndex +CREATE INDEX "Booking_startTime_endTime_idx" ON "Booking"("startTime", "endTime"); + +-- CreateIndex +CREATE UNIQUE INDEX "Poll_slug_key" ON "Poll"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Availability_timeSlotId_participantName_key" ON "Availability"("timeSlotId", "participantName"); + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EventType" ADD CONSTRAINT "EventType_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserAvailability" ADD CONSTRAINT "UserAvailability_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Booking" ADD CONSTRAINT "Booking_hostId_fkey" FOREIGN KEY ("hostId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Booking" ADD CONSTRAINT "Booking_eventTypeId_fkey" FOREIGN KEY ("eventTypeId") REFERENCES "EventType"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Poll" ADD CONSTRAINT "Poll_hostId_fkey" FOREIGN KEY ("hostId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TimeSlot" ADD CONSTRAINT "TimeSlot_pollId_fkey" FOREIGN KEY ("pollId") REFERENCES "Poll"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Availability" ADD CONSTRAINT "Availability_pollId_fkey" FOREIGN KEY ("pollId") REFERENCES "Poll"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Availability" ADD CONSTRAINT "Availability_timeSlotId_fkey" FOREIGN KEY ("timeSlotId") REFERENCES "TimeSlot"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Availability" ADD CONSTRAINT "Availability_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/db/prisma/migrations/migration_lock.toml b/packages/db/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/packages/db/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" From a60379096416ede1460f7bc8cd2f621c54044d6b Mon Sep 17 00:00:00 2001 From: Janarthanan Soundhararajan Date: Mon, 14 Sep 2026 21:26:07 +0530 Subject: [PATCH 3/3] chore: ignore Turborepo build artifacts --- .gitignore | 3 +++ apps/app/.turbo/turbo-lint.log | 5 ----- apps/marketing/.turbo/turbo-lint.log | 21 --------------------- 3 files changed, 3 insertions(+), 26 deletions(-) delete mode 100644 apps/app/.turbo/turbo-lint.log delete mode 100644 apps/marketing/.turbo/turbo-lint.log diff --git a/.gitignore b/.gitignore index 7b279e0..abb731a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ yarn-error.log* next-env.d.ts /app/generated/prisma + +# Turborepo +**/.turbo/ \ No newline at end of file diff --git a/apps/app/.turbo/turbo-lint.log b/apps/app/.turbo/turbo-lint.log deleted file mode 100644 index d41d6cd..0000000 --- a/apps/app/.turbo/turbo-lint.log +++ /dev/null @@ -1,5 +0,0 @@ - -> app@0.1.0 lint C:\Users\Janarthanan\Jana\Projects\slotsyncro\apps\app -> eslint - -Terminate batch job (Y/N)? diff --git a/apps/marketing/.turbo/turbo-lint.log b/apps/marketing/.turbo/turbo-lint.log deleted file mode 100644 index c20ccbb..0000000 --- a/apps/marketing/.turbo/turbo-lint.log +++ /dev/null @@ -1,21 +0,0 @@ - -> marketing@0.1.0 lint C:\Users\Janarthanan\Jana\Projects\slotsyncro\apps\marketing -> eslint - - -Oops! Something went wrong! :( - -ESLint: 9.39.5 - -ESLint couldn't find an eslint.config.(js|mjs|cjs) file. - -From ESLint v9.0.0, the default configuration file is now eslint.config.js. -If you are using a .eslintrc.* file, please follow the migration guide -to update your configuration file to the new format: - -https://eslint.org/docs/latest/use/configure/migration-guide - -If you still have problems after following the migration guide, please stop by -https://eslint.org/chat/help to chat with the team. - - ELIFECYCLE  Command failed with exit code 2.