From cea591b587ccfe9b7c5619959e7a33d999a0f769 Mon Sep 17 00:00:00 2001 From: MrSphay Date: Fri, 15 May 2026 18:59:03 +0200 Subject: [PATCH] Add initial Prisma migration --- .codex/project.md | 7 + README.md | 4 +- .../20260515170000_init/migration.sql | 156 ++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20260515170000_init/migration.sql diff --git a/.codex/project.md b/.codex/project.md index 764aeb2..6a3234a 100644 --- a/.codex/project.md +++ b/.codex/project.md @@ -22,6 +22,7 @@ Build: npm run build Audit: npm run audit Release check: npm run release:check Docker: docker compose up --build +Database migrate: npm run db:migrate ``` ## Stack @@ -30,6 +31,12 @@ Docker: docker compose up --build Next.js App Router, React, TypeScript, Prisma, Postgres, Socket.IO, Docker ``` +Database setup: + +```text +Prisma migrations live in prisma/migrations and are applied in Docker with prisma migrate deploy. +``` + Package manager: ```text diff --git a/README.md b/README.md index d929bd6..a0b5478 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ WatchLink is a self-hosted shared-watch web app with persistent user rooms, loca ```bash npm install cp .env.example .env -npm run db:push +npm run db:migrate npm run dev ``` @@ -108,6 +108,8 @@ docker compose up --build The Compose stack exposes the web app on `http://localhost:${HOST_PORT:-3000}` and uses a Postgres volume named `watchlink_postgres-data`. Keep the container `PORT` at `3000`; change `HOST_PORT` when another container already uses port 3000 on the host. +On first start, the web container runs `prisma migrate deploy` before starting Next.js. This creates the required tables in a clean Postgres volume. + Build and publish the Gitea image: ```bash diff --git a/prisma/migrations/20260515170000_init/migration.sql b/prisma/migrations/20260515170000_init/migration.sql new file mode 100644 index 0000000..3510a6c --- /dev/null +++ b/prisma/migrations/20260515170000_init/migration.sql @@ -0,0 +1,156 @@ +-- CreateEnum +CREATE TYPE "RoleScope" AS ENUM ('SYSTEM', 'ROOM'); + +-- CreateEnum +CREATE TYPE "FriendStatus" AS ENUM ('PENDING', 'ACCEPTED', 'DECLINED', 'BLOCKED'); + +-- CreateEnum +CREATE TYPE "RoomVisibility" AS ENUM ('PUBLIC', 'FRIENDS', 'ROLE_RESTRICTED', 'EXPLICIT'); + +-- CreateEnum +CREATE TYPE "MediaProvider" AS ENUM ('YOUTUBE', 'TWITCH', 'DIRECT', 'UNKNOWN'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "username" TEXT NOT NULL, + "passwordHash" TEXT NOT NULL, + "displayName" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Role" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "scope" "RoleScope" NOT NULL DEFAULT 'SYSTEM', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Role_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Permission" ( + "id" TEXT NOT NULL, + "key" TEXT NOT NULL, + "description" TEXT, + + CONSTRAINT "Permission_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UserRole" ( + "userId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + + CONSTRAINT "UserRole_pkey" PRIMARY KEY ("userId","roleId") +); + +-- CreateTable +CREATE TABLE "RolePermission" ( + "roleId" TEXT NOT NULL, + "permissionId" TEXT NOT NULL, + + CONSTRAINT "RolePermission_pkey" PRIMARY KEY ("roleId","permissionId") +); + +-- CreateTable +CREATE TABLE "Friendship" ( + "id" TEXT NOT NULL, + "requesterId" TEXT NOT NULL, + "receiverId" TEXT NOT NULL, + "status" "FriendStatus" NOT NULL DEFAULT 'PENDING', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Friendship_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Room" ( + "id" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "name" TEXT NOT NULL, + "ownerId" TEXT, + "visibility" "RoomVisibility" NOT NULL DEFAULT 'FRIENDS', + "currentState" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Room_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "RoomMember" ( + "roomId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "canManage" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "RoomMember_pkey" PRIMARY KEY ("roomId","userId") +); + +-- CreateTable +CREATE TABLE "MediaSource" ( + "id" TEXT NOT NULL, + "roomId" TEXT NOT NULL, + "submitterId" TEXT, + "provider" "MediaProvider" NOT NULL, + "originalUrl" TEXT NOT NULL, + "playbackUrl" TEXT NOT NULL, + "title" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MediaSource_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "Role_name_key" ON "Role"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Permission_key_key" ON "Permission"("key"); + +-- CreateIndex +CREATE UNIQUE INDEX "Friendship_requesterId_receiverId_key" ON "Friendship"("requesterId", "receiverId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Room_slug_key" ON "Room"("slug"); + +-- AddForeignKey +ALTER TABLE "UserRole" ADD CONSTRAINT "UserRole_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserRole" ADD CONSTRAINT "UserRole_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RolePermission" ADD CONSTRAINT "RolePermission_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RolePermission" ADD CONSTRAINT "RolePermission_permissionId_fkey" FOREIGN KEY ("permissionId") REFERENCES "Permission"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Friendship" ADD CONSTRAINT "Friendship_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Friendship" ADD CONSTRAINT "Friendship_receiverId_fkey" FOREIGN KEY ("receiverId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Room" ADD CONSTRAINT "Room_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RoomMember" ADD CONSTRAINT "RoomMember_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "RoomMember" ADD CONSTRAINT "RoomMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MediaSource" ADD CONSTRAINT "MediaSource_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MediaSource" ADD CONSTRAINT "MediaSource_submitterId_fkey" FOREIGN KEY ("submitterId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;