81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import cors from "@fastify/cors";
|
||
import jwt from "@fastify/jwt";
|
||
import multipart from "@fastify/multipart";
|
||
import Fastify from "fastify";
|
||
import { ZodError } from "zod";
|
||
import { MAX_IMAGE_UPLOAD_BYTES, mediaStorage } from "./media-storage.js";
|
||
import { prisma } from "./prisma.js";
|
||
import { registerAdminRoutes } from "./routes/admin.js";
|
||
import { registerMemoryRoutes } from "./routes/memory.js";
|
||
import { registerPublicRoutes } from "./routes/public.js";
|
||
import { registerWecomRoutes } from "./routes/wecom.js";
|
||
|
||
export async function createServer() {
|
||
const app = Fastify({
|
||
logger: {
|
||
level: process.env.LOG_LEVEL ?? "info",
|
||
},
|
||
});
|
||
|
||
await app.register(cors, {
|
||
origin: true,
|
||
credentials: true,
|
||
});
|
||
|
||
app.addContentTypeParser(["application/xml", "text/xml"], { parseAs: "string" }, (_request, body, done) => {
|
||
done(null, body);
|
||
});
|
||
|
||
await app.register(jwt, {
|
||
secret: process.env.JWT_SECRET ?? "dev-only-change-me-before-production",
|
||
});
|
||
|
||
await mediaStorage.ensureReady();
|
||
await app.register(multipart, {
|
||
limits: {
|
||
files: 1,
|
||
fileSize: MAX_IMAGE_UPLOAD_BYTES,
|
||
},
|
||
});
|
||
app.setErrorHandler((error: unknown, _request, reply) => {
|
||
if (error instanceof ZodError) {
|
||
app.log.warn(error);
|
||
reply.status(400).send({
|
||
message: error.issues[0]?.message ?? "请求参数不正确",
|
||
});
|
||
return;
|
||
}
|
||
|
||
const normalized = error instanceof Error ? error : new Error("Unknown server error");
|
||
const maybeStatus = "statusCode" in normalized && typeof normalized.statusCode === "number" ? normalized.statusCode : undefined;
|
||
const statusCode = maybeStatus && maybeStatus >= 400 ? maybeStatus : 500;
|
||
if (statusCode >= 500) app.log.error(normalized);
|
||
else app.log.warn(normalized);
|
||
reply.status(statusCode).send({
|
||
message: statusCode === 500 ? "服务暂时不可用" : normalized.message,
|
||
});
|
||
});
|
||
|
||
if (process.env.MINIAPP_MEMORY_DB === "1") {
|
||
if (process.env.MINIAPP_ALLOW_EPHEMERAL_DATA !== "1") {
|
||
throw new Error("检测到 MINIAPP_MEMORY_DB=1,但未显式允许临时数据。请使用 npm run dev:api:memory,或配置 DATABASE_URL 后使用 npm run dev:api。");
|
||
}
|
||
app.log.warn("临时内存模式已启用:后台配置只保存在当前进程,API 重启后会恢复默认。需要持久化请配置 DATABASE_URL 并使用 npm run dev:api。");
|
||
await registerMemoryRoutes(app);
|
||
} else {
|
||
if (!process.env.DATABASE_URL) {
|
||
throw new Error("DATABASE_URL 未配置。首页后台配置需要 PostgreSQL 持久化;请复制 .env.example 配置数据库,或仅在临时演示时使用 npm run dev:api:memory。");
|
||
}
|
||
await prisma.$connect();
|
||
app.log.info("PostgreSQL 持久化模式已启用。");
|
||
app.addHook("onClose", async () => {
|
||
await prisma.$disconnect();
|
||
});
|
||
await registerPublicRoutes(app);
|
||
await registerAdminRoutes(app);
|
||
await registerWecomRoutes(app);
|
||
}
|
||
|
||
return app;
|
||
}
|