diff --git a/backend/prisma/migrations/20260530_chat_reactions_and_soft_delete/migration.sql b/backend/prisma/migrations/20260530_chat_reactions_and_soft_delete/migration.sql new file mode 100644 index 0000000..66e6a5e --- /dev/null +++ b/backend/prisma/migrations/20260530_chat_reactions_and_soft_delete/migration.sql @@ -0,0 +1,42 @@ +-- Chat-Feature: Multi-Emoji-Reaktionen + Soft-Delete (Tombstone) für Room-Chat + DMs +-- Spiegelt die chat_message_likes-Struktur (20250712_chat_rooms_and_features), +-- erweitert um eine emoji-Spalte. PK (user_id, message_id) = eine Reaktion pro +-- User pro Message (neues Emoji ersetzt das alte — WhatsApp-Verhalten). + +-- ─── Soft-Delete: deleted_at auf beiden Message-Typen ─────────────────────── +ALTER TABLE "rebreak"."chat_messages" ADD COLUMN "deleted_at" TIMESTAMP(3); +ALTER TABLE "rebreak"."direct_messages" ADD COLUMN "deleted_at" TIMESTAMP(3); + +-- ─── Reaction-Tabelle: Room-Chat ──────────────────────────────────────────── +CREATE TABLE "rebreak"."chat_message_reactions" ( + "user_id" UUID NOT NULL, + "message_id" UUID NOT NULL, + "emoji" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "chat_message_reactions_pkey" PRIMARY KEY ("user_id","message_id") +); +CREATE INDEX "chat_message_reactions_message_id_idx" ON "rebreak"."chat_message_reactions"("message_id"); +ALTER TABLE "rebreak"."chat_message_reactions" + ADD CONSTRAINT "chat_message_reactions_message_id_fkey" + FOREIGN KEY ("message_id") REFERENCES "rebreak"."chat_messages"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- ─── Reaction-Tabelle: Direct-Messages ────────────────────────────────────── +CREATE TABLE "rebreak"."direct_message_reactions" ( + "user_id" UUID NOT NULL, + "message_id" UUID NOT NULL, + "emoji" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT "direct_message_reactions_pkey" PRIMARY KEY ("user_id","message_id") +); +CREATE INDEX "direct_message_reactions_message_id_idx" ON "rebreak"."direct_message_reactions"("message_id"); +ALTER TABLE "rebreak"."direct_message_reactions" + ADD CONSTRAINT "direct_message_reactions_message_id_fkey" + FOREIGN KEY ("message_id") REFERENCES "rebreak"."direct_messages"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- ─── Supabase Realtime ────────────────────────────────────────────────────── +-- Reaction-Tabellen zur Publication (chat_messages/direct_messages sind bereits +-- via 20260516_enable_chat_realtime drin; deren UPDATE-Events tragen das +-- deleted_at-Tombstone). Chat-Tabellen nutzen kein RLS (Prisma via Service-Role, +-- Scoping client-seitig via postgres_changes-Filter) — Reaction-Tabellen gleich. +ALTER PUBLICATION supabase_realtime ADD TABLE "rebreak"."chat_message_reactions"; +ALTER PUBLICATION supabase_realtime ADD TABLE "rebreak"."direct_message_reactions"; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 6cb35de..437ff07 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -381,11 +381,13 @@ model ChatMessage { attachmentName String? @map("attachment_name") likesCount Int @default(0) @map("likes_count") createdAt DateTime @default(now()) @map("created_at") + deletedAt DateTime? @map("deleted_at") - room ChatRoom? @relation(fields: [roomId], references: [id], onDelete: Cascade) - replyTo ChatMessage? @relation("ChatReplies", fields: [replyToId], references: [id], onDelete: SetNull) - replies ChatMessage[] @relation("ChatReplies") - likes ChatMessageLike[] + room ChatRoom? @relation(fields: [roomId], references: [id], onDelete: Cascade) + replyTo ChatMessage? @relation("ChatReplies", fields: [replyToId], references: [id], onDelete: SetNull) + replies ChatMessage[] @relation("ChatReplies") + likes ChatMessageLike[] + reactions ChatMessageReaction[] @@map("chat_messages") @@schema("rebreak") @@ -402,6 +404,21 @@ model ChatMessageLike { @@schema("rebreak") } +model ChatMessageReaction { + userId String @map("user_id") @db.Uuid + messageId String @map("message_id") @db.Uuid + emoji String + createdAt DateTime @default(now()) @map("created_at") + + message ChatMessage @relation(fields: [messageId], references: [id], onDelete: Cascade) + + // Eine Reaktion pro User pro Message (neues Emoji ersetzt das alte — WhatsApp-Verhalten) + @@id([userId, messageId]) + @@index([messageId]) + @@map("chat_message_reactions") + @@schema("rebreak") +} + model DirectMessage { id String @id @default(uuid()) @db.Uuid senderId String @map("sender_id") @db.Uuid @@ -414,10 +431,12 @@ model DirectMessage { likesCount Int @default(0) @map("likes_count") createdAt DateTime @default(now()) @map("created_at") readAt DateTime? @map("read_at") + deletedAt DateTime? @map("deleted_at") - replyTo DirectMessage? @relation("DmReplies", fields: [replyToId], references: [id], onDelete: SetNull) - replies DirectMessage[] @relation("DmReplies") - likes DirectMessageLike[] + replyTo DirectMessage? @relation("DmReplies", fields: [replyToId], references: [id], onDelete: SetNull) + replies DirectMessage[] @relation("DmReplies") + likes DirectMessageLike[] + reactions DirectMessageReaction[] @@map("direct_messages") @@schema("rebreak") @@ -434,6 +453,20 @@ model DirectMessageLike { @@schema("rebreak") } +model DirectMessageReaction { + userId String @map("user_id") @db.Uuid + messageId String @map("message_id") @db.Uuid + emoji String + createdAt DateTime @default(now()) @map("created_at") + + message DirectMessage @relation(fields: [messageId], references: [id], onDelete: Cascade) + + @@id([userId, messageId]) + @@index([messageId]) + @@map("direct_message_reactions") + @@schema("rebreak") +} + model UserFollow { followerId String @map("follower_id") @db.Uuid followingId String @map("following_id") @db.Uuid