- 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>
68 lines
2.3 KiB
TypeScript
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,
|
|
})),
|
|
};
|
|
});
|