From 9cba24aad810dbbde7793953695e72de50414504 Mon Sep 17 00:00:00 2001 From: Pepe Ziberi Date: Thu, 21 May 2026 15:00:47 +0200 Subject: [PATCH] =?UTF-8?q?fix(icons):=20/api/icons=20auf=20Raw-SQL=20umge?= =?UTF-8?q?stellt=20=E2=80=93=20zeigt=20jetzt=20alle=20Tenant-Kategorien?= =?UTF-8?q?=20in=20der=20App?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/icons/route.ts | 138 +++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 50 deletions(-) diff --git a/src/app/api/icons/route.ts b/src/app/api/icons/route.ts index a8e223e..d77f6bf 100644 --- a/src/app/api/icons/route.ts +++ b/src/app/api/icons/route.ts @@ -2,6 +2,38 @@ import { NextRequest, NextResponse } from 'next/server' import { prisma } from '@/lib/db' import { getSession } from '@/lib/auth' +async function ensureTables() { + await prisma.$executeRawUnsafe(` + CREATE TABLE IF NOT EXISTS tenant_symbols ( + id TEXT PRIMARY KEY DEFAULT gen_random_uuid(), + "isActive" BOOLEAN NOT NULL DEFAULT true, + "tenantId" TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + "iconId" TEXT REFERENCES icon_assets(id) ON DELETE SET NULL, + "name" TEXT, + "svgPath" TEXT, + "isUploaded" BOOLEAN NOT NULL DEFAULT false, + "categoryId" TEXT, + "migratedFromIconId" TEXT, + "customName" TEXT, + "sortOrder" INTEGER, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + `) + await prisma.$executeRawUnsafe(` + CREATE TABLE IF NOT EXISTS tenant_categories ( + id TEXT PRIMARY KEY DEFAULT gen_random_uuid(), + "name" TEXT NOT NULL, + "description" TEXT, + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "tenantId" TEXT NOT NULL REFERENCES tenants(id) ON DELETE CASCADE + ) + `) +} + export async function GET() { /* ─── 1. Global library (legacy IconAsset) ─── */ let categoriesWithUrls: any[] = [] @@ -9,41 +41,38 @@ export async function GET() { const user = await getSession() const tenantId = user?.tenantId - const categoryWhere: any = tenantId - ? { OR: [{ tenantId: null }, { tenantId }] } - : {} - - const categories = await (prisma as any).iconCategory.findMany({ - where: categoryWhere, - orderBy: { sortOrder: 'asc' }, - include: { - icons: { - where: tenantId - ? { isActive: true, OR: [{ tenantId: null }, { tenantId }] } - : { isActive: true, tenantId: null }, - orderBy: { name: 'asc' }, - }, - }, - }) + const categories = await prisma.$queryRawUnsafe( + `SELECT * FROM icon_categories WHERE "tenantId" IS NULL OR "tenantId" = $1 ORDER BY "sortOrder" ASC`, + tenantId || null + ) as any[] let hiddenIconIds: string[] = [] if (tenantId) { - const tenant = await (prisma as any).tenant.findUnique({ - where: { id: tenantId }, - select: { hiddenIconIds: true }, - }) - hiddenIconIds = tenant?.hiddenIconIds || [] + const tenant = await prisma.$queryRawUnsafe( + `SELECT "hiddenIconIds" FROM tenants WHERE id = $1 LIMIT 1`, + tenantId + ) as any[] + hiddenIconIds = tenant[0]?.hiddenIconIds || [] } - categoriesWithUrls = categories.map((cat: any) => ({ - ...cat, - icons: cat.icons + for (const cat of categories) { + const icons = await prisma.$queryRawUnsafe( + `SELECT * FROM icon_assets WHERE "categoryId" = $1 AND "isActive" = true AND ("tenantId" IS NULL OR "tenantId" = $2) ORDER BY name ASC`, + cat.id, tenantId || null + ) as any[] + const filteredIcons = icons .filter((icon: any) => !hiddenIconIds.includes(icon.id)) .map((icon: any) => ({ ...icon, url: `/api/icons/${icon.id}/image`, - })), - })) + })) + if (filteredIcons.length > 0) { + categoriesWithUrls.push({ + ...cat, + icons: filteredIcons, + }) + } + } } catch (err) { console.error('Error fetching legacy icon categories:', err) categoriesWithUrls = [] @@ -58,18 +87,23 @@ export async function GET() { const tenantId = user?.tenantId if (tenantId) { - const tenantSymbols = await (prisma as any).tenantSymbol.findMany({ - where: { tenantId }, - include: { category: true }, - orderBy: [{ category: { sortOrder: 'asc' } }, { sortOrder: 'asc' }], - }) + await ensureTables() + + const tenantSymbols = await prisma.$queryRawUnsafe( + `SELECT ts.*, tc.id as "catId", tc.name as "catName" + FROM tenant_symbols ts + LEFT JOIN tenant_categories tc ON ts."categoryId" = tc.id + WHERE ts."tenantId" = $1 AND ts."isActive" = true + ORDER BY COALESCE(tc."sortOrder", 9999) ASC, COALESCE(ts."sortOrder", 9999) ASC`, + tenantId + ) as any[] flatTenantSymbols = tenantSymbols.map((ts: any) => ({ id: ts.id, name: ts.customName || ts.name, customName: ts.customName, categoryId: ts.categoryId, - categoryName: ts.category?.name || null, + categoryName: ts.catName || null, isUploaded: ts.isUploaded, migratedFromIconId: ts.migratedFromIconId, imageUrl: ts.isUploaded @@ -87,12 +121,13 @@ export async function GET() { } const catIds = Array.from(groups.keys()).filter(Boolean) as string[] - const tenantCategories = catIds.length - ? await (prisma as any).tenantCategory.findMany({ - where: { id: { in: catIds }, tenantId }, - orderBy: { sortOrder: 'asc' }, - }) - : [] + let tenantCategories: any[] = [] + if (catIds.length > 0) { + tenantCategories = await prisma.$queryRawUnsafe( + `SELECT * FROM tenant_categories WHERE id = ANY($1) AND "tenantId" = $2 ORDER BY "sortOrder" ASC`, + catIds, tenantId + ) as any[] + } const catMap = new Map(tenantCategories.map((c: any) => [c.id, c])) @@ -100,8 +135,8 @@ export async function GET() { const cat = catId ? catMap.get(catId) : null return { categoryId: catId, - categoryName: cat ? (cat as any).name || 'Kategorie' : 'Ohne Kategorie', - sortOrder: cat ? (cat as any).sortOrder ?? 999 : 999, + categoryName: cat ? cat.name || 'Kategorie' : 'Ohne Kategorie', + sortOrder: cat ? cat.sortOrder ?? 999 : 999, symbols, } }) @@ -120,19 +155,22 @@ export async function GET() { const tenantId = user?.tenantId if (tenantId) { - const legacy = await (prisma as any).tenantSymbol.findMany({ - where: { tenantId, iconId: { not: null } }, - include: { icon: { select: { id: true, name: true, mimeType: true, iconType: true } } }, - orderBy: { sortOrder: 'asc' }, - }) + const legacy = await prisma.$queryRawUnsafe( + `SELECT ts.*, ia.id as "iconId", ia.name as "iconName", ia."mimeType", ia."iconType" + FROM tenant_symbols ts + JOIN icon_assets ia ON ts."iconId" = ia.id + WHERE ts."tenantId" = $1 AND ts."iconId" IS NOT NULL + ORDER BY COALESCE(ts."sortOrder", 9999) ASC`, + tenantId + ) as any[] mySymbolsLegacy = legacy.map((ts: any) => ({ - id: ts.icon.id, + id: ts.iconId, tenantSymbolId: ts.id, - name: ts.customName || ts.icon.name, + name: ts.customName || ts.iconName, customName: ts.customName, - mimeType: ts.icon.mimeType, - iconType: ts.icon.iconType, - url: `/api/icons/${ts.icon.id}/image`, + mimeType: ts.mimeType, + iconType: ts.iconType, + url: `/api/icons/${ts.iconId}/image`, })) } } catch (err) {