import { and, asc, desc, eq, gt, inArray, lt, or } from 'drizzle-orm' import { commentAttachments, commentReactions, comments, favorites, tags, threadSubscriptions, uploads, uploadReactions, uploadTags, users } from '../../db/schema' import type { CommentAttachment } from '~~/shared/utils/attachments' export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') ?? '' if (!/^[A-Za-z0-9]{1,16}$/.test(id)) { throw createError({ statusCode: 400, statusMessage: 'Ungültige ID' }) } const db = useDb() const uploadRows = await db .select({ id: uploads.id, type: uploads.type, fileName: uploads.fileName, externalUrl: uploads.externalUrl, originalName: uploads.originalName, mimeType: uploads.mimeType, previewImage: uploads.previewImage, previewTitle: uploads.previewTitle, audioMeta: uploads.audioMeta, createdAt: uploads.createdAt, userName: users.name, userId: users.id, userColor: users.color, userAvatar: users.avatar, userCardEffect: users.cardEffect, userCardEffectColor: users.cardEffectColor, userCardVignette: users.cardVignette, userCardBorderEffect: users.cardBorderEffect, userCardBorderEffectColor: users.cardBorderEffectColor, }) .from(uploads) .innerJoin(users, eq(uploads.userId, users.id)) .where(eq(uploads.id, id)) .limit(1) const upload = uploadRows[0] if (!upload) { throw createError({ statusCode: 404, statusMessage: 'Upload nicht gefunden' }) } const commentRows = await db .select({ id: comments.id, content: comments.content, createdAt: comments.createdAt, userName: users.name, userId: users.id, userColor: users.color, userAvatar: users.avatar, }) .from(comments) .innerJoin(users, eq(comments.userId, users.id)) .where(eq(comments.uploadId, id)) .orderBy(asc(comments.createdAt)) // Nachbarn in Grid-Reihenfolge (createdAt desc, id desc als Tiebreak) const sessionUser = await getSessionUser(event) const commentIds = commentRows.map(c => c.id) const [reactionRows, uploadReactionRows, favRows, newerRows, olderRows, tagRows, attachmentRows, subscriptionRows] = await Promise.all([ commentIds.length ? db .select({ commentId: commentReactions.commentId, emoji: commentReactions.emoji, userId: commentReactions.userId, userName: users.name, }) .from(commentReactions) .innerJoin(users, eq(commentReactions.userId, users.id)) .where(inArray(commentReactions.commentId, commentIds)) .orderBy(asc(commentReactions.createdAt)) : Promise.resolve([]), db .select({ emoji: uploadReactions.emoji, userId: uploadReactions.userId, userName: users.name, }) .from(uploadReactions) .innerJoin(users, eq(uploadReactions.userId, users.id)) .where(eq(uploadReactions.uploadId, id)) .orderBy(asc(uploadReactions.createdAt)), db .select({ id: users.id, name: users.name, color: users.color, avatar: users.avatar }) .from(favorites) .innerJoin(users, eq(favorites.userId, users.id)) .where(eq(favorites.uploadId, id)) .orderBy(desc(favorites.createdAt)), db .select({ id: uploads.id }) .from(uploads) .where(or( gt(uploads.createdAt, upload.createdAt), and(eq(uploads.createdAt, upload.createdAt), gt(uploads.id, upload.id)), )) .orderBy(asc(uploads.createdAt), asc(uploads.id)) .limit(1), db .select({ id: uploads.id }) .from(uploads) .where(or( lt(uploads.createdAt, upload.createdAt), and(eq(uploads.createdAt, upload.createdAt), lt(uploads.id, upload.id)), )) .orderBy(desc(uploads.createdAt), desc(uploads.id)) .limit(1), db .select({ name: tags.name }) .from(uploadTags) .innerJoin(tags, eq(uploadTags.tagId, tags.id)) .where(eq(uploadTags.uploadId, id)) .orderBy(asc(tags.name)), commentIds.length ? db .select() .from(commentAttachments) .where(inArray(commentAttachments.commentId, commentIds)) .orderBy(asc(commentAttachments.id)) : Promise.resolve([]), sessionUser ? db.select({ enabled: threadSubscriptions.enabled }).from(threadSubscriptions) .where(and(eq(threadSubscriptions.uploadId, id), eq(threadSubscriptions.userId, sessionUser.id))).limit(1) : Promise.resolve([]), ]) const attachmentsByComment = new Map() for (const a of attachmentRows) { let list = attachmentsByComment.get(a.commentId) if (!list) { list = [] attachmentsByComment.set(a.commentId, list) } list.push({ id: a.id, name: a.originalName, url: `/uploads/attachments/${a.fileName}`, mimeType: a.mimeType, size: a.size, cover: a.cover, meta: a.meta as CommentAttachment['meta'] }) } const reactionsByComment = new Map() for (const r of reactionRows) { let list = reactionsByComment.get(r.commentId) if (!list) { list = [] reactionsByComment.set(r.commentId, list) } let entry = list.find(e => e.emoji === r.emoji) if (!entry) { entry = { emoji: r.emoji, count: 0, me: false, names: [] } list.push(entry) } entry.count += 1 entry.names.push(r.userName) if (r.userId === sessionUser?.id) entry.me = true } const uploadReactionList: { emoji: string, count: number, me: boolean, names: string[] }[] = [] for (const r of uploadReactionRows) { let entry = uploadReactionList.find(e => e.emoji === r.emoji) if (!entry) { entry = { emoji: r.emoji, count: 0, me: false, names: [] } uploadReactionList.push(entry) } entry.count += 1 entry.names.push(r.userName) if (r.userId === sessionUser?.id) entry.me = true } const url = upload.externalUrl ?? `/uploads/${upload.fileName}` const embedId = upload.type === 'youtube' && upload.externalUrl ? getYoutubeId(upload.externalUrl) : upload.type === 'twitch' && upload.externalUrl ? serializeTwitchTarget(getTwitchTarget(upload.externalUrl)) : null return { upload: { ...upload, url, embedId, userAvatarUrl: avatarUrl(upload.userAvatar), tags: tagRows.map(t => t.name) }, comments: commentRows.map(c => ({ ...c, userAvatarUrl: avatarUrl(c.userAvatar), reactions: reactionsByComment.get(c.id) ?? [], attachments: attachmentsByComment.get(c.id) ?? [] })), uploadReactions: uploadReactionList, favoritedBy: favRows.map(f => ({ id: f.id, name: f.name, color: f.color, avatarUrl: avatarUrl(f.avatar) })), isFavorited: sessionUser ? favRows.some(f => f.id === sessionUser.id) : false, isSubscribed: subscriptionRows[0]?.enabled ?? false, newerId: newerRows[0]?.id ?? null, olderId: olderRows[0]?.id ?? null, } })