Files
2026-10-03 20:07:49 +02:00

72 lines
2.9 KiB
TypeScript

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 }
})