Files
bodyshop-media-server/server.ts
2025-11-04 15:00:44 -08:00

198 lines
6.4 KiB
TypeScript

import "source-map-support/register";
import bodyParser from "body-parser";
import compression from "compression";
import cors from "cors";
import dotenv from "dotenv";
import express, { Express } from "express";
import helmet from "helmet";
import morgan from "morgan";
import nocache from "nocache";
import path, { resolve } from "path";
import responseTime from "response-time";
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import BillRequestValidator from "./bills/billRequestValidator.js";
import { BillsListMedia } from "./bills/billsListMedia.js";
import { BillsMediaUploadMulter, BillsUploadMedia } from "./bills/billsUploadMedia.js";
import validateJobRequest from "./jobs/jobRequestValidator.js";
import { JobsDeleteMedia } from "./jobs/jobsDeleteMedia.js";
import { jobsDownloadMedia } from "./jobs/jobsDownloadMedia.js";
import { JobsListMedia } from "./jobs/jobsListMedia.js";
import { JobsMoveMedia } from "./jobs/jobsMoveMedia.js";
import { JobMediaUploadMulter, jobsUploadMedia } from "./jobs/jobsUploadMedia.js";
import InitServer, { FolderPaths } from "./util/serverInit.js";
import ValidateImsToken from "./util/validateToken.js";
import { dailyS3Scheduler } from "./util/dailyS3Scheduler.js";
import { analyzeJobsDirectory } from "./util/s3Sync.js";
dotenv.config({
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
// Global error handlers
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
// Logger setup
const commonTransportConfig = {
maxSize: "20m",
maxFiles: 14,
tailable: true,
zippedArchive: true,
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
datePattern: "YYYY-MM-DD"
};
const baseFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json(),
winston.format.prettyPrint()
);
export const logger = winston.createLogger({
format: baseFormat,
level: "http",
levels: { ...winston.config.syslog.levels, http: 8 },
exceptionHandlers: [
new DailyRotateFile({
filename: path.join(FolderPaths.Root, "logs", "exceptions-%DATE%.log"),
...commonTransportConfig
})
],
rejectionHandlers: [
new DailyRotateFile({
filename: path.join(FolderPaths.Root, "logs", "rejections-%DATE%.log"),
...commonTransportConfig
})
],
transports: [
new DailyRotateFile({
filename: path.join(FolderPaths.Root, "logs", "errors-%DATE%.log"),
level: "error",
...commonTransportConfig
}),
new DailyRotateFile({
filename: path.join(FolderPaths.Root, "logs", "debug-%DATE%.log"),
level: "debug",
...commonTransportConfig
}),
new DailyRotateFile({
filename: path.join(FolderPaths.Root, "logs", "ALL-%DATE%.log"),
...commonTransportConfig
}),
new winston.transports.Console({
format: winston.format.combine(winston.format.colorize(), winston.format.timestamp(), winston.format.simple())
})
]
});
// App init
const app: Express = express();
const port = process.env.PORT;
app.disable("x-powered-by");
app.set("trust proxy", 1);
// Middleware order: security, logging, parsing, etc.
app.set("etag", false);
app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }));
app.use(nocache());
app.use(cors());
app.use(compression());
app.use(responseTime());
app.use(bodyParser.json({ limit: "1000mb" }));
app.use(bodyParser.urlencoded({ limit: "1000mb", extended: true }));
app.use((req, res, next) => {
res.setHeader("Connection", "keep-alive");
next();
});
const morganMiddleware = morgan("combined", {
stream: { write: (message) => logger.http(message.trim()) }
});
app.use(morganMiddleware);
// Job endpoints
app.post("/jobs/list", ValidateImsToken, validateJobRequest, JobsListMedia);
app.post("/jobs/upload", ValidateImsToken, JobMediaUploadMulter.array("file"), validateJobRequest, jobsUploadMedia);
app.post("/jobs/download", ValidateImsToken, validateJobRequest, jobsDownloadMedia);
app.post("/jobs/move", ValidateImsToken, JobsMoveMedia);
app.post("/jobs/delete", ValidateImsToken, JobsDeleteMedia);
// Bill endpoints
app.post("/bills/list", BillRequestValidator, BillsListMedia);
app.post(
"/bills/upload",
ValidateImsToken,
BillsMediaUploadMulter.array("file"),
BillRequestValidator,
BillsUploadMedia
);
// Health and root
app.get("/", ValidateImsToken, (req, res) => {
res.send("IMS running.");
});
app.get("/health", (req, res) => {
res.status(200).send("OK");
});
// S3 sync status endpoint
app.get("/sync/status", ValidateImsToken, async (req, res) => {
try {
const status = await dailyS3Scheduler.getStatus();
res.json(status);
} catch (error) {
logger.error("Failed to get sync status:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ error: errorMessage });
}
});
// Manual S3 sync trigger endpoint (for testing)
app.post("/sync/trigger", ValidateImsToken, async (req, res) => {
try {
await dailyS3Scheduler.triggerManualSync();
res.json({ success: true, message: "Manual sync triggered successfully" });
} catch (error) {
logger.error("Manua--l sync failed:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ success: false, message: "Manual sync failed", error: errorMessage });
}
});
// Jobs directory analysis endpoint
app.get("/jobs/analysis", ValidateImsToken, async (req, res) => {
try {
const analysis = await analyzeJobsDirectory();
res.json(analysis);
} catch (error) {
logger.error("Failed to analyze jobs directory:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error";
res.status(500).json({ success: false, message: "Jobs analysis failed", error: errorMessage });
}
});
// Static files
InitServer();
app.use(FolderPaths.StaticPath, express.static(FolderPaths.Root, { etag: false, maxAge: 30 * 1000 }));
app.use("/assets", express.static("/assets", { etag: false, maxAge: 30 * 1000 }));
// Start the daily S3 sync scheduler
dailyS3Scheduler.start().catch((error) => {
logger.error("Failed to start sync scheduler:", error);
});
app.listen(port, () => {
logger.info(`ImEX Media Server is running at http://localhost:${port}`);
});