Complete WatchLink V1 realtime features
Some checks failed
Template Compliance / compliance (push) Successful in 7s
Release Dry Run / release-dry-run (push) Failing after 1m8s
Build / build (push) Failing after 1m15s

This commit is contained in:
MrSphay
2026-05-15 23:27:18 +02:00
parent 04d75c386f
commit c1ac6e4142
25 changed files with 1775 additions and 253 deletions

View File

@@ -0,0 +1,59 @@
-- WatchLink V1 completion support: persistent realtime state, messages, audit,
-- invites, disabled users, and protected personal rooms.
CREATE TYPE "InviteStatus" AS ENUM ('ACTIVE', 'REVOKED', 'USED', 'EXPIRED');
ALTER TABLE "User" ADD COLUMN "disabledAt" TIMESTAMP(3);
ALTER TABLE "Room" ADD COLUMN "isPersonal" BOOLEAN NOT NULL DEFAULT false;
UPDATE "Room"
SET "isPersonal" = true
WHERE "slug" LIKE '@%';
CREATE TABLE "RoomMessage" (
"id" TEXT NOT NULL,
"roomId" TEXT NOT NULL,
"userId" TEXT,
"body" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "RoomMessage_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "AuditEvent" (
"id" TEXT NOT NULL,
"actorId" TEXT,
"roomId" TEXT,
"action" TEXT NOT NULL,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AuditEvent_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Invite" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"roomId" TEXT,
"creatorId" TEXT,
"status" "InviteStatus" NOT NULL DEFAULT 'ACTIVE',
"expiresAt" TIMESTAMP(3),
"usedById" TEXT,
"usedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Invite_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "Invite_code_key" ON "Invite"("code");
CREATE INDEX "RoomMessage_roomId_createdAt_idx" ON "RoomMessage"("roomId", "createdAt");
CREATE INDEX "AuditEvent_roomId_createdAt_idx" ON "AuditEvent"("roomId", "createdAt");
CREATE INDEX "AuditEvent_actorId_createdAt_idx" ON "AuditEvent"("actorId", "createdAt");
CREATE INDEX "Invite_roomId_status_idx" ON "Invite"("roomId", "status");
ALTER TABLE "RoomMessage" ADD CONSTRAINT "RoomMessage_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "RoomMessage" ADD CONSTRAINT "RoomMessage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "AuditEvent" ADD CONSTRAINT "AuditEvent_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -33,12 +33,20 @@ enum MediaProvider {
UNKNOWN
}
enum InviteStatus {
ACTIVE
REVOKED
USED
EXPIRED
}
model User {
id String @id @default(cuid())
username String @unique
passwordHash String
displayName String?
avatarUrl String?
disabledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
roles UserRole[]
@@ -47,6 +55,9 @@ model User {
gotFriends Friendship[] @relation("FriendReceiver")
roomMembers RoomMember[]
submitted MediaSource[]
messages RoomMessage[]
auditEvents AuditEvent[]
invites Invite[]
}
model AppSetting {
@@ -108,6 +119,7 @@ model Room {
slug String @unique
name String
ownerId String?
isPersonal Boolean @default(false)
visibility RoomVisibility @default(FRIENDS)
currentState Json?
createdAt DateTime @default(now())
@@ -115,6 +127,9 @@ model Room {
owner User? @relation("RoomOwner", fields: [ownerId], references: [id], onDelete: SetNull)
members RoomMember[]
mediaSources MediaSource[]
messages RoomMessage[]
auditEvents AuditEvent[]
invites Invite[]
}
model RoomMember {
@@ -141,3 +156,45 @@ model MediaSource {
room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
submitter User? @relation(fields: [submitterId], references: [id], onDelete: SetNull)
}
model RoomMessage {
id String @id @default(cuid())
roomId String
userId String?
body String
createdAt DateTime @default(now())
room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([roomId, createdAt])
}
model AuditEvent {
id String @id @default(cuid())
actorId String?
roomId String?
action String
metadata Json?
createdAt DateTime @default(now())
actor User? @relation(fields: [actorId], references: [id], onDelete: SetNull)
room Room? @relation(fields: [roomId], references: [id], onDelete: SetNull)
@@index([roomId, createdAt])
@@index([actorId, createdAt])
}
model Invite {
id String @id @default(cuid())
code String @unique
roomId String?
creatorId String?
status InviteStatus @default(ACTIVE)
expiresAt DateTime?
usedById String?
usedAt DateTime?
createdAt DateTime @default(now())
room Room? @relation(fields: [roomId], references: [id], onDelete: Cascade)
creator User? @relation(fields: [creatorId], references: [id], onDelete: SetNull)
@@index([roomId, status])
}