chahinebrini 69f01c5a0c feat(chat): DM-Reaktionen + Soft-Delete Backend + comment_likes realtime
- db/chat: getDmHistory liefert reactions + deletedAt; toggleDmReaction
  (WA-Toggle) + softDeleteDmMessage (nur eigene)
- Endpoints: /api/chat/reaction (Emoji-Toggle, 7er-Allowlist) +
  /api/chat/delete-message (Soft-Delete für alle)
- dm/[userId].get: aggregierte reactions + deleted im Response, Inhalt bei
  Tombstone geblankt
- Migration: comment_likes zur supabase_realtime-Publication (fixt
  Post-Kommentar-Like-Realtime, eskaliert von rebreak-native-ui)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 11:18:32 +02:00

68 lines
2.3 KiB
TypeScript

import { getDmHistory, markDmsAsRead } from "../../../db/chat";
import { getProfile } from "../../../db/profile";
/** Roh-Reaktionen ({userId,emoji}[]) → aggregiert pro Emoji für die UI-Pills. */
function aggregateReactions(
reactions: { userId: string; emoji: string }[],
myId: string,
): { emoji: string; count: number; mine: boolean }[] {
const map = new Map<string, { emoji: string; count: number; mine: boolean }>();
for (const r of reactions) {
const e = map.get(r.emoji) ?? { emoji: r.emoji, count: 0, mine: false };
e.count++;
if (r.userId === myId) e.mine = true;
map.set(r.emoji, e);
}
return [...map.values()];
}
/** GET /api/chat/dm/[userId]?page=1 */
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const partnerId = getRouterParam(event, "userId");
if (!partnerId)
throw createError({ statusCode: 400, message: "userId fehlt" });
const page = Math.max(1, parseInt((getQuery(event).page as string) || "1"));
const [messages, partnerProfile] = await Promise.all([
getDmHistory(user.id, partnerId, page),
getProfile(partnerId),
markDmsAsRead(partnerId, user.id),
]);
return {
partner: partnerProfile
? {
id: partnerProfile.id,
nickname: partnerProfile.nickname ?? partnerProfile.username ?? "Anonym",
username: partnerProfile.username ?? "anonym",
avatar: partnerProfile.avatar,
}
: { id: partnerId, nickname: "Anonym", username: "anonym", avatar: null },
messages: [...messages].reverse().map((m) => ({
id: m.id,
senderId: m.senderId,
receiverId: m.receiverId,
// Bei Soft-Delete keinen Inhalt mehr ausliefern — Frontend zeigt Tombstone.
content: m.deletedAt ? "" : m.content,
createdAt: m.createdAt,
isOwn: m.senderId === user.id,
readAt: m.readAt,
deleted: m.deletedAt != null,
reactions: aggregateReactions(m.reactions, user.id),
attachmentUrl: m.deletedAt ? null : m.attachmentUrl,
attachmentType: m.deletedAt ? null : m.attachmentType,
attachmentName: m.deletedAt ? null : m.attachmentName,
likesCount: m.likesCount,
replyTo: m.replyTo
? {
id: m.replyTo.id,
senderId: m.replyTo.senderId,
content: m.replyTo.content,
}
: null,
})),
};
});