fix(symbol-arch): robuste API-Endpoints + docker-entrypoint Migration/Seed
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 14m59s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 14m59s
This commit is contained in:
@@ -5,7 +5,11 @@ echo "=== Lageplan Startup ==="
|
|||||||
|
|
||||||
# ─── Step 1: Run migrations (uses PrismaClient raw SQL, no CLI needed) ───
|
# ─── Step 1: Run migrations (uses PrismaClient raw SQL, no CLI needed) ───
|
||||||
echo "[1/3] Running database migrations..."
|
echo "[1/3] Running database migrations..."
|
||||||
node prisma/migrate.js || echo " Warning: migrations had issues (may be first run)"
|
node prisma/migrate.js
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Database migrations failed. Aborting startup."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── Step 2: Conditional seeding ───
|
# ─── Step 2: Conditional seeding ───
|
||||||
echo "[2/3] Checking if seeding is needed..."
|
echo "[2/3] Checking if seeding is needed..."
|
||||||
@@ -34,6 +38,44 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── Step 3: Start server ───
|
# ─── Step 3: Seed SymbolTemplates (idempotent) ───
|
||||||
|
echo "[2b/3] Seeding symbol templates..."
|
||||||
|
node -e "
|
||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function seedTemplates() {
|
||||||
|
const sigDir = path.join(process.cwd(), 'public', 'signaturen');
|
||||||
|
let files = [];
|
||||||
|
try { files = fs.readdirSync(sigDir).filter(f => f.endsWith('.svg')).sort(); } catch(e) { console.log('No signaturen dir'); }
|
||||||
|
let created = 0, skipped = 0;
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const displayName = file.replace(/\.svg$/i, '').replace(/[_-]/g, ' ').trim();
|
||||||
|
const fileKey = 'signaturen/' + file;
|
||||||
|
const existing = await prisma.symbolTemplate.findFirst({ where: { svgPath: fileKey } }).catch(() => null);
|
||||||
|
if (existing) { skipped++; continue; }
|
||||||
|
await prisma.symbolTemplate.create({
|
||||||
|
data: {
|
||||||
|
packageId: 'feuerwehr-ch',
|
||||||
|
packageName: 'Feuerwehr Schweiz',
|
||||||
|
categoryName: 'Sonstiges',
|
||||||
|
name: displayName,
|
||||||
|
svgPath: fileKey,
|
||||||
|
tags: [displayName.toLowerCase()],
|
||||||
|
sortOrder: i,
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
console.log('Templates: ' + created + ' created, ' + skipped + ' skipped');
|
||||||
|
await prisma.\$disconnect();
|
||||||
|
}
|
||||||
|
seedTemplates().catch(() => { console.log('Template seed skipped'); prisma.\$disconnect(); });
|
||||||
|
" || echo " Warning: template seed failed"
|
||||||
|
|
||||||
|
# ─── Step 4: Start server ───
|
||||||
echo "[3/3] Starting server..."
|
echo "[3/3] Starting server..."
|
||||||
exec node server-custom.js
|
exec node server-custom.js
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { prisma } from '@/lib/db'
|
|||||||
import { getSession } from '@/lib/auth'
|
import { getSession } from '@/lib/auth'
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
|
/* ─── 1. Global library (legacy IconAsset) ─── */
|
||||||
|
let categoriesWithUrls: any[] = []
|
||||||
try {
|
try {
|
||||||
const user = await getSession()
|
const user = await getSession()
|
||||||
const tenantId = user?.tenantId
|
const tenantId = user?.tenantId
|
||||||
|
|
||||||
/* ─── 1. Global library (legacy IconAsset) ─── */
|
|
||||||
const categoryWhere: any = tenantId
|
const categoryWhere: any = tenantId
|
||||||
? { OR: [{ tenantId: null }, { tenantId }] }
|
? { OR: [{ tenantId: null }, { tenantId }] }
|
||||||
: {}
|
: {}
|
||||||
@@ -34,7 +35,7 @@ export async function GET() {
|
|||||||
hiddenIconIds = tenant?.hiddenIconIds || []
|
hiddenIconIds = tenant?.hiddenIconIds || []
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoriesWithUrls = categories.map((cat: any) => ({
|
categoriesWithUrls = categories.map((cat: any) => ({
|
||||||
...cat,
|
...cat,
|
||||||
icons: cat.icons
|
icons: cat.icons
|
||||||
.filter((icon: any) => !hiddenIconIds.includes(icon.id))
|
.filter((icon: any) => !hiddenIconIds.includes(icon.id))
|
||||||
@@ -43,11 +44,19 @@ export async function GET() {
|
|||||||
url: `/api/icons/${icon.id}/image`,
|
url: `/api/icons/${icon.id}/image`,
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching legacy icon categories:', err)
|
||||||
|
categoriesWithUrls = []
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── 2. Tenant symbols (Phase 1 architecture) ─── */
|
/* ─── 2. Tenant symbols (Phase 1 architecture) ─── */
|
||||||
let tenantSymbolGroups: any[] = []
|
let tenantSymbolGroups: any[] = []
|
||||||
let flatTenantSymbols: any[] = []
|
let flatTenantSymbols: any[] = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await getSession()
|
||||||
|
const tenantId = user?.tenantId
|
||||||
|
|
||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
const tenantSymbols = await (prisma as any).tenantSymbol.findMany({
|
const tenantSymbols = await (prisma as any).tenantSymbol.findMany({
|
||||||
where: { tenantId },
|
where: { tenantId },
|
||||||
@@ -70,7 +79,6 @@ export async function GET() {
|
|||||||
: `/api/icons/${ts.id}/image`,
|
: `/api/icons/${ts.id}/image`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Group by category for sidebar display
|
|
||||||
const groups = new Map<string | null, any[]>()
|
const groups = new Map<string | null, any[]>()
|
||||||
for (const sym of flatTenantSymbols) {
|
for (const sym of flatTenantSymbols) {
|
||||||
const key = sym.categoryId || null
|
const key = sym.categoryId || null
|
||||||
@@ -78,7 +86,6 @@ export async function GET() {
|
|||||||
groups.get(key)!.push(sym)
|
groups.get(key)!.push(sym)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch categories that have symbols but may not be in the symbol list (empty ones are omitted)
|
|
||||||
const catIds = Array.from(groups.keys()).filter(Boolean) as string[]
|
const catIds = Array.from(groups.keys()).filter(Boolean) as string[]
|
||||||
const tenantCategories = catIds.length
|
const tenantCategories = catIds.length
|
||||||
? await (prisma as any).tenantCategory.findMany({
|
? await (prisma as any).tenantCategory.findMany({
|
||||||
@@ -100,9 +107,18 @@ export async function GET() {
|
|||||||
})
|
})
|
||||||
tenantSymbolGroups.sort((a, b) => a.sortOrder - b.sortOrder)
|
tenantSymbolGroups.sort((a, b) => a.sortOrder - b.sortOrder)
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching tenant symbols:', err)
|
||||||
|
tenantSymbolGroups = []
|
||||||
|
flatTenantSymbols = []
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── 3. Legacy mySymbols (keep for old clients during transition) ─── */
|
/* ─── 3. Legacy mySymbols (keep for old clients during transition) ─── */
|
||||||
let mySymbolsLegacy: any[] = []
|
let mySymbolsLegacy: any[] = []
|
||||||
|
try {
|
||||||
|
const user = await getSession()
|
||||||
|
const tenantId = user?.tenantId
|
||||||
|
|
||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
const legacy = await (prisma as any).tenantSymbol.findMany({
|
const legacy = await (prisma as any).tenantSymbol.findMany({
|
||||||
where: { tenantId, iconId: { not: null } },
|
where: { tenantId, iconId: { not: null } },
|
||||||
@@ -119,15 +135,15 @@ export async function GET() {
|
|||||||
url: `/api/icons/${ts.icon.id}/image`,
|
url: `/api/icons/${ts.icon.id}/image`,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching legacy mySymbols:', err)
|
||||||
|
mySymbolsLegacy = []
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
categories: categoriesWithUrls,
|
categories: categoriesWithUrls,
|
||||||
mySymbols: mySymbolsLegacy, // legacy shape for old clients
|
mySymbols: mySymbolsLegacy,
|
||||||
tenantSymbols: flatTenantSymbols, // new flat list
|
tenantSymbols: flatTenantSymbols,
|
||||||
tenantSymbolGroups, // grouped by TenantCategory
|
tenantSymbolGroups,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching icons:', error)
|
|
||||||
return NextResponse.json({ error: 'Serverfehler' }, { status: 500 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,12 @@ export async function GET() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return NextResponse.json({ packages: result })
|
return NextResponse.json({ packages: result })
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Error fetching templates:', error)
|
console.error('Error fetching templates:', error)
|
||||||
|
// Return empty packages list on DB schema mismatch so UI doesn't crash
|
||||||
|
if (error?.message?.includes('symbol_templates') || error?.code === 'P2021') {
|
||||||
|
return NextResponse.json({ packages: [] })
|
||||||
|
}
|
||||||
return NextResponse.json({ error: 'Interner Fehler' }, { status: 500 })
|
return NextResponse.json({ error: 'Interner Fehler' }, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ export async function GET() {
|
|||||||
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||||
const { tenantId } = auth
|
const { tenantId } = auth
|
||||||
|
|
||||||
const categories = await (prisma as any).tenantCategory.findMany({
|
let categories: any[] = []
|
||||||
|
try {
|
||||||
|
categories = await (prisma as any).tenantCategory.findMany({
|
||||||
where: { tenantId },
|
where: { tenantId },
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
@@ -33,13 +35,20 @@ export async function GET() {
|
|||||||
},
|
},
|
||||||
orderBy: { sortOrder: 'asc' },
|
orderBy: { sortOrder: 'asc' },
|
||||||
})
|
})
|
||||||
|
} catch (dbErr: any) {
|
||||||
|
console.error('tenantCategory.findMany failed:', dbErr)
|
||||||
|
if (dbErr?.message?.includes('tenant_categories') || dbErr?.code === 'P2021') {
|
||||||
|
return NextResponse.json({ categories: [] })
|
||||||
|
}
|
||||||
|
throw dbErr
|
||||||
|
}
|
||||||
|
|
||||||
const result = categories.map((cat: any) => ({
|
const result = categories.map((cat: any) => ({
|
||||||
id: cat.id,
|
id: cat.id,
|
||||||
name: cat.name,
|
name: cat.name,
|
||||||
sortOrder: cat.sortOrder,
|
sortOrder: cat.sortOrder,
|
||||||
icon: cat.icon,
|
icon: cat.icon,
|
||||||
symbolCount: cat._count.symbols,
|
symbolCount: cat._count?.symbols ?? 0,
|
||||||
createdAt: cat.createdAt,
|
createdAt: cat.createdAt,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,22 @@ export async function GET(req: NextRequest) {
|
|||||||
const { searchParams } = new URL(req.url)
|
const { searchParams } = new URL(req.url)
|
||||||
const grouped = searchParams.get('grouped') !== 'false'
|
const grouped = searchParams.get('grouped') !== 'false'
|
||||||
|
|
||||||
const tenantSymbols = await (prisma as any).tenantSymbol.findMany({
|
let tenantSymbols: any[] = []
|
||||||
|
try {
|
||||||
|
tenantSymbols = await (prisma as any).tenantSymbol.findMany({
|
||||||
where: { tenantId },
|
where: { tenantId },
|
||||||
include: {
|
include: {
|
||||||
category: { select: { id: true, name: true, sortOrder: true } },
|
category: { select: { id: true, name: true, sortOrder: true } },
|
||||||
},
|
},
|
||||||
orderBy: [{ category: { sortOrder: 'asc' } }, { sortOrder: 'asc' }, { name: 'asc' }],
|
orderBy: [{ category: { sortOrder: 'asc' } }, { sortOrder: 'asc' }, { name: 'asc' }],
|
||||||
})
|
})
|
||||||
|
} catch (dbErr: any) {
|
||||||
|
console.error('tenantSymbol.findMany failed:', dbErr)
|
||||||
|
if (dbErr?.message?.includes('tenant_symbols') || dbErr?.code === 'P2021') {
|
||||||
|
return NextResponse.json({ categories: [], symbols: [] })
|
||||||
|
}
|
||||||
|
throw dbErr
|
||||||
|
}
|
||||||
|
|
||||||
const mapped = tenantSymbols.map((ts: any) => ({
|
const mapped = tenantSymbols.map((ts: any) => ({
|
||||||
id: ts.id,
|
id: ts.id,
|
||||||
@@ -62,10 +71,20 @@ export async function GET(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort categories by sortOrder from category
|
// Sort categories by sortOrder from category
|
||||||
const categories = await (prisma as any).tenantCategory.findMany({
|
let categories: any[] = []
|
||||||
|
try {
|
||||||
|
categories = await (prisma as any).tenantCategory.findMany({
|
||||||
where: { tenantId },
|
where: { tenantId },
|
||||||
orderBy: { sortOrder: 'asc' },
|
orderBy: { sortOrder: 'asc' },
|
||||||
})
|
})
|
||||||
|
} catch (catErr: any) {
|
||||||
|
console.error('tenantCategory.findMany failed:', catErr)
|
||||||
|
if (catErr?.message?.includes('tenant_categories') || catErr?.code === 'P2021') {
|
||||||
|
categories = []
|
||||||
|
} else {
|
||||||
|
throw catErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ordered: Array<{ categoryId: string | null; categoryName: string; symbols: any[] }> = []
|
const ordered: Array<{ categoryId: string | null; categoryName: string; symbols: any[] }> = []
|
||||||
for (const cat of categories) {
|
for (const cat of categories) {
|
||||||
|
|||||||
Reference in New Issue
Block a user