feat: Add Directus setup scripts for collections, fields, and relations
- Created setup-directus-collections.js to automate the creation of tech stack collections, fields, and relations in Directus. - Created setup-directus-hobbies.js for setting up hobbies collection with translations. - Created setup-directus-projects.js for establishing projects collection with comprehensive fields and translations. - Added setup-tech-stack-directus.js to populate tech_stack_items with predefined data.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getContentByKey } from "@/lib/content";
|
||||
import { getContentPage } from "@/lib/directus";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
@@ -11,9 +12,24 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
try {
|
||||
// 1) Try Directus first
|
||||
const directusPage = await getContentPage(key, locale);
|
||||
if (directusPage) {
|
||||
return NextResponse.json({
|
||||
content: {
|
||||
title: directusPage.title,
|
||||
slug: directusPage.slug,
|
||||
locale: directusPage.locale || locale,
|
||||
content: directusPage.content,
|
||||
},
|
||||
source: "directus",
|
||||
});
|
||||
}
|
||||
|
||||
// 2) Fallback: PostgreSQL
|
||||
const translation = await getContentByKey({ key, locale });
|
||||
if (!translation) return NextResponse.json({ content: null });
|
||||
return NextResponse.json({ content: translation });
|
||||
return NextResponse.json({ content: translation, source: "postgresql" });
|
||||
} catch (error) {
|
||||
// If DB isn't migrated/available, fail soft so the UI can fall back to next-intl strings.
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
|
||||
47
app/api/hobbies/route.ts
Normal file
47
app/api/hobbies/route.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getHobbies } from '@/lib/directus';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/**
|
||||
* GET /api/hobbies
|
||||
*
|
||||
* Loads Hobbies from Directus with fallback to static data
|
||||
*
|
||||
* Query params:
|
||||
* - locale: en or de (default: en)
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const locale = searchParams.get('locale') || 'en';
|
||||
|
||||
// Try to load from Directus
|
||||
const hobbies = await getHobbies(locale);
|
||||
|
||||
if (hobbies && hobbies.length > 0) {
|
||||
return NextResponse.json({
|
||||
hobbies,
|
||||
source: 'directus'
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback: return empty (component will use hardcoded fallback)
|
||||
return NextResponse.json({
|
||||
hobbies: null,
|
||||
source: 'fallback'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading hobbies:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
hobbies: null,
|
||||
error: 'Failed to load hobbies',
|
||||
source: 'error'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { apiCache } from '@/lib/cache';
|
||||
import { requireSessionAuth, checkRateLimit, getRateLimitHeaders, getClientIp } from '@/lib/auth';
|
||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
|
||||
import { generateUniqueSlug } from '@/lib/slug';
|
||||
import { getProjects as getDirectusProjects } from '@/lib/directus';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
@@ -43,6 +44,47 @@ export async function GET(request: NextRequest) {
|
||||
const published = searchParams.get('published');
|
||||
const difficulty = searchParams.get('difficulty');
|
||||
const search = searchParams.get('search');
|
||||
const locale = searchParams.get('locale') || 'en';
|
||||
|
||||
// Try Directus FIRST (Primary Source)
|
||||
try {
|
||||
const directusProjects = await getDirectusProjects(locale, {
|
||||
featured: featured === 'true' ? true : featured === 'false' ? false : undefined,
|
||||
published: published === 'true' ? true : published === 'false' ? false : undefined,
|
||||
category: category || undefined,
|
||||
difficulty: difficulty || undefined,
|
||||
search: search || undefined,
|
||||
limit
|
||||
});
|
||||
|
||||
if (directusProjects && directusProjects.length > 0) {
|
||||
return NextResponse.json({
|
||||
projects: directusProjects,
|
||||
total: directusProjects.length,
|
||||
page: 1,
|
||||
limit: directusProjects.length,
|
||||
source: 'directus'
|
||||
});
|
||||
}
|
||||
} catch (directusError) {
|
||||
console.log('Directus not available, trying PostgreSQL fallback');
|
||||
}
|
||||
|
||||
// Fallback 1: Try PostgreSQL
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
} catch (dbError) {
|
||||
console.log('PostgreSQL also not available, using empty fallback');
|
||||
|
||||
// Fallback 2: Return empty (components should have hardcoded fallback)
|
||||
return NextResponse.json({
|
||||
projects: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit,
|
||||
source: 'fallback'
|
||||
});
|
||||
}
|
||||
|
||||
// Create cache parameters object
|
||||
const cacheParams = {
|
||||
@@ -93,7 +135,8 @@ export async function GET(request: NextRequest) {
|
||||
projects,
|
||||
total,
|
||||
pages: Math.ceil(total / limit),
|
||||
currentPage: page
|
||||
currentPage: page,
|
||||
source: 'postgresql'
|
||||
};
|
||||
|
||||
// Cache the result (only for non-search queries)
|
||||
@@ -105,7 +148,7 @@ export async function GET(request: NextRequest) {
|
||||
} catch (error) {
|
||||
// Handle missing database table gracefully
|
||||
if (error instanceof PrismaClientKnownRequestError && error.code === 'P2021') {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (process.env.NOD-fallbackE_ENV === 'development') {
|
||||
console.warn('Project table does not exist. Returning empty result.');
|
||||
}
|
||||
return NextResponse.json({
|
||||
|
||||
47
app/api/tech-stack/route.ts
Normal file
47
app/api/tech-stack/route.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getTechStack } from '@/lib/directus';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
/**
|
||||
* GET /api/tech-stack
|
||||
*
|
||||
* Loads Tech Stack from Directus with fallback to static data
|
||||
*
|
||||
* Query params:
|
||||
* - locale: en or de (default: en)
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const locale = searchParams.get('locale') || 'en';
|
||||
|
||||
// Try to load from Directus
|
||||
const techStack = await getTechStack(locale);
|
||||
|
||||
if (techStack && techStack.length > 0) {
|
||||
return NextResponse.json({
|
||||
techStack,
|
||||
source: 'directus'
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback: return empty (component will use hardcoded fallback)
|
||||
return NextResponse.json({
|
||||
techStack: null,
|
||||
source: 'fallback'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading tech stack:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
techStack: null,
|
||||
error: 'Failed to load tech stack',
|
||||
source: 'error'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user