mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
- Add Author, Language, Work Type, Date Range filters - Implement Save Search Preferences - Add backend routes for search and filtering with client-side pagination support Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
109 lines
3.1 KiB
TypeScript
109 lines
3.1 KiB
TypeScript
import express, {
|
|
type NextFunction,
|
|
type Request,
|
|
type Response,
|
|
} from "express";
|
|
import { attachGraphQLClient } from "./middleware/graphql";
|
|
import commentRouter from "./routes/comment";
|
|
import userRouter from "./routes/user";
|
|
import authorRouter from "./routes/author";
|
|
import workRouter from "./routes/work";
|
|
import tagRouter from "./routes/tag";
|
|
import collectionRouter from "./routes/collection";
|
|
import blogRouter from "./routes/blog";
|
|
import statsRouter from "./routes/stats";
|
|
import searchRouter from "./routes/search";
|
|
import filterRouter from "./routes/filter";
|
|
import { log, serveStatic, setupVite } from "./vite";
|
|
import { createServer } from "node:http";
|
|
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: false }));
|
|
|
|
// Attach per-request GraphQL client
|
|
app.use(attachGraphQLClient);
|
|
|
|
app.use((req, res, next) => {
|
|
const start = Date.now();
|
|
const path = req.path;
|
|
let capturedJsonResponse: unknown;
|
|
|
|
const originalResJson = res.json;
|
|
res.json = (bodyJson, ...args) => {
|
|
capturedJsonResponse = bodyJson;
|
|
return originalResJson.apply(res, [bodyJson, ...args]);
|
|
};
|
|
|
|
res.on("finish", () => {
|
|
const duration = Date.now() - start;
|
|
if (path.startsWith("/api")) {
|
|
let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
|
|
if (capturedJsonResponse) {
|
|
logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
|
|
}
|
|
|
|
if (logLine.length > 80) {
|
|
logLine = `${logLine.slice(0, 79)}…`;
|
|
}
|
|
|
|
log(logLine);
|
|
}
|
|
});
|
|
|
|
next();
|
|
});
|
|
|
|
(async () => {
|
|
// Domain routers (incremental migration from monolith)
|
|
app.use("/api/users", userRouter);
|
|
app.use("/api/authors", authorRouter);
|
|
app.use("/api/works", workRouter);
|
|
app.use("/api/tags", tagRouter);
|
|
app.use("/api/comments", commentRouter);
|
|
app.use("/api/collections", collectionRouter);
|
|
app.use("/api/blog", blogRouter);
|
|
app.use("/api/stats", statsRouter);
|
|
app.use("/api/search", searchRouter);
|
|
app.use("/api/filter", filterRouter);
|
|
// comments router already mounted earlier at /api/comments
|
|
|
|
const server = createServer(app);
|
|
|
|
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
|
const fallbackStatus = 500;
|
|
interface StatusCarrier {
|
|
status?: number;
|
|
statusCode?: number;
|
|
}
|
|
const status =
|
|
typeof err === "object" && err
|
|
? ((err as StatusCarrier).status ??
|
|
(err as StatusCarrier).statusCode ??
|
|
fallbackStatus)
|
|
: fallbackStatus;
|
|
const message =
|
|
err instanceof Error ? err.message : "Internal Server Error";
|
|
|
|
res.status(status).json({ message });
|
|
throw err;
|
|
});
|
|
|
|
// importantly only setup vite in development and after
|
|
// setting up all the other routes so the catch-all route
|
|
// doesn't interfere with the other routes
|
|
if (app.get("env") === "development") {
|
|
await setupVite(app, server);
|
|
} else {
|
|
serveStatic(app);
|
|
}
|
|
|
|
// ALWAYS serve the app on port 5000
|
|
// this serves both the API and the client.
|
|
// It is the only port that is not firewalled.
|
|
const port = 5000;
|
|
server.listen(port, "0.0.0.0", () => {
|
|
log(`serving on port ${port}`);
|
|
});
|
|
})();
|