feat: major UI/UX overhaul, snippets system, and performance fixes
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 9m26s

This commit is contained in:
2026-02-16 12:31:40 +01:00
parent 6f62b37c3a
commit a5dba298f3
41 changed files with 1610 additions and 499 deletions

View File

@@ -32,7 +32,7 @@ export async function GET(
}
// Flatten das Objekt zu flachen Keys
const flatKeys = flattenObject(namespaceData);
const flatKeys = flattenObject(namespaceData as Record<string, unknown>);
// Lade jeden Key aus Directus (mit Fallback auf JSON)
const result: Record<string, string> = {};
@@ -57,19 +57,24 @@ export async function GET(
}
// Helper: Holt verschachtelte Werte aus Objekt
function getNestedValue(obj: any, path: string): any {
return path.split('.').reduce((current, key) => current?.[key], obj);
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
return path.split('.').reduce<unknown>((current, key) => {
if (current && typeof current === 'object' && key in current) {
return (current as Record<string, unknown>)[key];
}
return undefined;
}, obj);
}
// Helper: Flatten verschachteltes Objekt zu flachen Keys
function flattenObject(obj: any, prefix = ''): Record<string, string> {
function flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(obj)) {
const newKey = prefix ? `${prefix}.${key}` : key;
if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.assign(result, flattenObject(value, newKey));
Object.assign(result, flattenObject(value as Record<string, unknown>, newKey));
} else {
result[newKey] = String(value);
}

View File

@@ -8,7 +8,7 @@ export async function GET(request: NextRequest) {
try {
const messages = await getMessages(locale);
return NextResponse.json({ messages });
} catch (error) {
} catch {
return NextResponse.json({ messages: {} }, { status: 500 });
}
}

View File

@@ -5,6 +5,7 @@ import { requireSessionAuth, checkRateLimit, getRateLimitHeaders, getClientIp }
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
import { generateUniqueSlug } from '@/lib/slug';
import { getProjects as getDirectusProjects } from '@/lib/directus';
import { ProjectListItem } from '@/app/_ui/ProjectsPageClient';
export async function GET(request: NextRequest) {
try {
@@ -41,17 +42,18 @@ export async function GET(request: NextRequest) {
const limit = Number.isFinite(limitRaw) && limitRaw > 0 && limitRaw <= 200 ? limitRaw : 50;
const category = searchParams.get('category');
const featured = searchParams.get('featured');
const published = searchParams.get('published');
const published = searchParams.get('published') === 'false' ? false : true; // Default to true if not specified
const difficulty = searchParams.get('difficulty');
const search = searchParams.get('search');
const locale = searchParams.get('locale') || 'en';
// Try Directus FIRST (Primary Source)
let directusProjects: any[] = [];
let directusProjects: ProjectListItem[] = [];
let directusSuccess = false;
try {
const fetched = await getDirectusProjects(locale, {
featured: featured === 'true' ? true : featured === 'false' ? false : undefined,
published: published === 'true' ? true : published === 'false' ? false : undefined,
published: published,
category: category || undefined,
difficulty: difficulty || undefined,
search: search || undefined,
@@ -59,29 +61,41 @@ export async function GET(request: NextRequest) {
});
if (fetched) {
directusProjects = fetched;
directusProjects = fetched.map(p => ({
id: typeof p.id === 'string' ? (parseInt(p.id) || 0) : p.id,
slug: p.slug,
title: p.title,
description: p.description,
tags: p.tags || [],
category: p.category || '',
date: p.created_at,
createdAt: p.created_at,
imageUrl: p.image_url,
}));
directusSuccess = true;
}
} catch (directusError) {
console.log('Directus error, continuing with PostgreSQL');
} catch {
console.log('Directus error, continuing with PostgreSQL fallback');
}
// Fallback 1: Try PostgreSQL
// If Directus returned projects, use them EXCLUSIVELY to avoid showing un-synced local data
if (directusSuccess && directusProjects.length > 0) {
return NextResponse.json({
projects: directusProjects,
total: directusProjects.length,
source: 'directus'
});
}
// Fallback 1: Try PostgreSQL only if Directus failed or is empty
try {
await prisma.$queryRaw`SELECT 1`;
} catch (dbError) {
} catch {
console.log('PostgreSQL not available');
if (directusProjects.length > 0) {
return NextResponse.json({
projects: directusProjects,
total: directusProjects.length,
source: 'directus'
});
}
return NextResponse.json({
projects: [],
total: 0,
source: 'fallback'
projects: directusProjects, // Might be empty
total: directusProjects.length,
source: 'directus-empty'
});
}
@@ -90,7 +104,7 @@ export async function GET(request: NextRequest) {
if (category) where.category = category;
if (featured !== null) where.featured = featured === 'true';
if (published !== null) where.published = published === 'true';
where.published = published;
if (difficulty) where.difficulty = difficulty;
if (search) {
@@ -113,7 +127,17 @@ export async function GET(request: NextRequest) {
// Merge logic
const dbSlugs = new Set(dbProjects.map(p => p.slug));
const mergedProjects = [...dbProjects];
const mergedProjects: ProjectListItem[] = dbProjects.map(p => ({
id: p.id,
slug: p.slug,
title: p.title,
description: p.description,
tags: p.tags,
category: p.category,
date: p.date,
createdAt: p.createdAt.toISOString(),
imageUrl: p.imageUrl,
}));
for (const dp of directusProjects) {
if (!dbSlugs.has(dp.slug)) {

18
app/api/snippets/route.ts Normal file
View File

@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
import { getSnippets } from '@/lib/directus';
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const limit = parseInt(searchParams.get('limit') || '10');
const featured = searchParams.get('featured') === 'true' ? true : undefined;
const snippets = await getSnippets(limit, featured);
return NextResponse.json({
snippets: snippets || []
});
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch snippets' }, { status: 500 });
}
}