feat: replace Directus with custom Node.js GraphQL backend
Removes Directus 11 and replaces it with a lean, purpose-built backend: - packages/backend/: Fastify v5 + GraphQL Yoga v5 + Pothos (code-first) with Drizzle ORM, Redis sessions (session_token cookie), argon2 auth, Nodemailer, fluent-ffmpeg, and full gamification system ported from bundle - Frontend: @directus/sdk replaced by graphql-request v7; services.ts fully rewritten with identical signatures; directus.ts now re-exports from api.ts - Cookie renamed directus_session_token → session_token - Dev proxy target updated 8055 → 4000 - compose.yml: Directus service removed, backend service added (port 4000) - Dockerfile.backend: new multi-stage image with ffmpeg - Dockerfile: bundle build step and ffmpeg removed from frontend image - data-migration.ts: one-time script to migrate all Directus/sexy_ tables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
87
packages/backend/src/index.ts
Normal file
87
packages/backend/src/index.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import Fastify from "fastify";
|
||||
import fastifyCookie from "@fastify/cookie";
|
||||
import fastifyCors from "@fastify/cors";
|
||||
import fastifyMultipart from "@fastify/multipart";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import { createYoga } from "graphql-yoga";
|
||||
import path from "path";
|
||||
import { schema } from "./graphql/index.js";
|
||||
import { buildContext } from "./graphql/context.js";
|
||||
import { db } from "./db/connection.js";
|
||||
import { redis } from "./lib/auth.js";
|
||||
|
||||
const PORT = parseInt(process.env.PORT || "4000");
|
||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || "/data/uploads";
|
||||
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:3000";
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: {
|
||||
level: process.env.LOG_LEVEL || "info",
|
||||
},
|
||||
});
|
||||
|
||||
await fastify.register(fastifyCookie, {
|
||||
secret: process.env.COOKIE_SECRET || "change-me-in-production",
|
||||
});
|
||||
|
||||
await fastify.register(fastifyCors, {
|
||||
origin: CORS_ORIGIN,
|
||||
credentials: true,
|
||||
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||
});
|
||||
|
||||
await fastify.register(fastifyMultipart, {
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024 * 1024, // 5 GB
|
||||
},
|
||||
});
|
||||
|
||||
await fastify.register(fastifyStatic, {
|
||||
root: path.resolve(UPLOAD_DIR),
|
||||
prefix: "/assets/",
|
||||
decorateReply: false,
|
||||
});
|
||||
|
||||
const yoga = createYoga({
|
||||
schema,
|
||||
context: buildContext,
|
||||
graphqlEndpoint: "/graphql",
|
||||
healthCheckEndpoint: "/health",
|
||||
logging: {
|
||||
debug: (...args) => fastify.log.debug(...args),
|
||||
info: (...args) => fastify.log.info(...args),
|
||||
warn: (...args) => fastify.log.warn(...args),
|
||||
error: (...args) => fastify.log.error(...args),
|
||||
},
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
url: "/graphql",
|
||||
method: ["GET", "POST", "OPTIONS"],
|
||||
handler: async (request, reply) => {
|
||||
const response = await yoga.handleNodeRequestAndResponse(request, reply, {
|
||||
request,
|
||||
reply,
|
||||
db,
|
||||
redis,
|
||||
});
|
||||
reply.status(response.status);
|
||||
for (const [key, value] of response.headers.entries()) {
|
||||
reply.header(key, value);
|
||||
}
|
||||
return reply.send(response.body);
|
||||
},
|
||||
});
|
||||
|
||||
fastify.get("/health", async (_request, reply) => {
|
||||
return reply.send({ status: "ok", timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
try {
|
||||
await fastify.listen({ port: PORT, host: "0.0.0.0" });
|
||||
fastify.log.info(`Backend running at http://0.0.0.0:${PORT}`);
|
||||
fastify.log.info(`GraphQL at http://0.0.0.0:${PORT}/graphql`);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user