119 行
3.5 KiB
TypeScript
119 行
3.5 KiB
TypeScript
import "dotenv/config";
|
|
import express from "express";
|
|
import { createServer } from "http";
|
|
import net from "net";
|
|
import path from "node:path";
|
|
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
|
import { registerOAuthRoutes } from "./oauth";
|
|
import { appRouter } from "../routers";
|
|
import { createContext } from "./context";
|
|
import { registerMediaProxy } from "./mediaProxy";
|
|
import { serveStatic } from "./static";
|
|
import { createBackgroundTask, getAdminUserId, hasRecentBackgroundTaskOfType, seedAchievementDefinitions, seedAppSettings, seedTutorials, seedVisionReferenceImages } from "../db";
|
|
import { nanoid } from "nanoid";
|
|
|
|
async function scheduleDailyNtrpRefresh() {
|
|
const now = new Date();
|
|
if (now.getHours() !== 0 || now.getMinutes() > 5) {
|
|
return;
|
|
}
|
|
|
|
const midnight = new Date();
|
|
midnight.setHours(0, 0, 0, 0);
|
|
const exists = await hasRecentBackgroundTaskOfType("ntrp_refresh_all", midnight);
|
|
if (exists) {
|
|
return;
|
|
}
|
|
|
|
const adminUserId = await getAdminUserId();
|
|
if (!adminUserId) {
|
|
return;
|
|
}
|
|
|
|
const taskId = nanoid();
|
|
await createBackgroundTask({
|
|
id: taskId,
|
|
userId: adminUserId,
|
|
type: "ntrp_refresh_all",
|
|
title: "每日 NTRP 刷新",
|
|
message: "系统已自动创建每日 NTRP 刷新任务",
|
|
payload: { source: "scheduler", scheduledAt: now.toISOString() },
|
|
progress: 0,
|
|
maxAttempts: 3,
|
|
});
|
|
}
|
|
|
|
function isPortAvailable(port: number): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const server = net.createServer();
|
|
server.listen(port, () => {
|
|
server.close(() => resolve(true));
|
|
});
|
|
server.on("error", () => resolve(false));
|
|
});
|
|
}
|
|
|
|
async function findAvailablePort(startPort: number = 3000): Promise<number> {
|
|
for (let port = startPort; port < startPort + 20; port++) {
|
|
if (await isPortAvailable(port)) {
|
|
return port;
|
|
}
|
|
}
|
|
throw new Error(`No available port found starting from ${startPort}`);
|
|
}
|
|
|
|
async function startServer() {
|
|
await seedTutorials();
|
|
await seedVisionReferenceImages();
|
|
await seedAchievementDefinitions();
|
|
await seedAppSettings();
|
|
|
|
const app = express();
|
|
const server = createServer(app);
|
|
registerMediaProxy(app);
|
|
// Configure body parser with larger size limit for file uploads
|
|
app.use(express.json({ limit: "50mb" }));
|
|
app.use(express.urlencoded({ limit: "50mb", extended: true }));
|
|
app.use(
|
|
"/uploads",
|
|
express.static(path.resolve(process.env.LOCAL_STORAGE_DIR || "data/storage"))
|
|
);
|
|
// OAuth callback under /api/oauth/callback
|
|
registerOAuthRoutes(app);
|
|
// tRPC API
|
|
app.use(
|
|
"/api/trpc",
|
|
createExpressMiddleware({
|
|
router: appRouter,
|
|
createContext,
|
|
})
|
|
);
|
|
// development mode uses Vite, production mode uses static files
|
|
if (process.env.NODE_ENV === "development") {
|
|
const { setupVite } = await import("./vite");
|
|
await setupVite(app, server);
|
|
} else {
|
|
serveStatic(app);
|
|
}
|
|
|
|
const preferredPort = parseInt(process.env.PORT || "3000");
|
|
const strictPort = process.env.STRICT_PORT === "1";
|
|
const port = strictPort ? preferredPort : await findAvailablePort(preferredPort);
|
|
|
|
if (port !== preferredPort) {
|
|
console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
|
|
}
|
|
|
|
server.listen(port, () => {
|
|
console.log(`Server running on http://localhost:${port}/`);
|
|
});
|
|
|
|
setInterval(() => {
|
|
void scheduleDailyNtrpRefresh().catch((error) => {
|
|
console.error("[scheduler] failed to schedule NTRP refresh", error);
|
|
});
|
|
}, 60_000);
|
|
}
|
|
|
|
startServer().catch(console.error);
|