import { and, desc, eq, gte, inArray } from 'drizzle-orm' import { tags, uploads, uploadTags, users } from '../../db/schema' import { normalizeTagName } from '../../../shared/utils/tags' type UploadType = typeof uploads.$inferSelect['type'] // Leichtgewichtige Liste — Thumbnails und Kommentarzahlen lädt der Client // separat über /api/uploads/meta nach. // Pagination per Dense-Pack-Simulation: Der Client schickt, wie viele // Grid-Zellen auf den Bildschirm passen (cells) und wie breit das Grid ist // (cols). Tiles belegen je nach Modus/Kommentarzahl 1x1, 2x2 oder 3x3 Zellen. // grid-flow-dense platziert jedes Tile in die erste freie Position — was // nicht passt, wandert auf die nächste Seite. So passt jede Seite garantiert // ohne Scrollen in den Viewport, auch bei fragmentierten Zeilenenden. export default defineEventHandler(async (event) => { const db = useDb() const q = getQuery(event) const tag = normalizeTagName(String(q.tag ?? '')) const types = String(q.type ?? '') .split(',') .map(t => t.trim()) .filter(t => (uploads.type.enumValues as readonly string[]).includes(t)) as UploadType[] const uploaderId = Number(q.uploader) const days = Number(q.days) const { page, cells, cols, uniform } = getGridPageParams(q) const conditions = [] if (tag) { conditions.push(inArray( uploads.id, db.select({ id: uploadTags.uploadId }).from(uploadTags) .innerJoin(tags, eq(uploadTags.tagId, tags.id)) .where(eq(tags.name, tag)), )) } if (types.length) conditions.push(inArray(uploads.type, types)) if (Number.isInteger(uploaderId) && uploaderId > 0) conditions.push(eq(uploads.userId, uploaderId)) if (Number.isFinite(days) && days > 0) { conditions.push(gte(uploads.createdAt, new Date(Date.now() - days * 86400_000))) } // Alle Treffer holen (kleine Felder) und clientseitig dieselbe // Span-Logik wie UploadTile anwenden, um Seiten exakt zu füllen const rows = await db .select({ id: uploads.id, type: uploads.type, fileName: uploads.fileName, externalUrl: uploads.externalUrl, commentCount: uploads.commentCount, createdAt: uploads.createdAt, userId: users.id, userName: users.name, userColor: users.color, userAvatar: users.avatar, }) .from(uploads) .innerJoin(users, eq(uploads.userId, users.id)) .where(conditions.length ? and(...conditions) : undefined) .orderBy(desc(uploads.createdAt)) const pages = paginateGrid(rows, r => gridTileDims(r.commentCount, uniform, cols), cells, cols) const pageRows = pages[page - 1] ?? [] const items = pageRows.map((row) => { const url = row.externalUrl ?? `/uploads/${row.fileName}` return { id: row.id, type: row.type, url, commentCount: row.commentCount, createdAt: row.createdAt, userId: row.userId, userName: row.userName, userColor: row.userColor, userAvatarUrl: avatarUrl(row.userAvatar) } }) return { items, total: rows.length, pageCount: pages.length } })