Skip to content

Commit e148673

Browse files
committed
Fixed issues
1 parent 73969b6 commit e148673

1 file changed

Lines changed: 123 additions & 70 deletions

File tree

backend/src/app.ts

Lines changed: 123 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
1-
import express, { type Request, type Response, type NextFunction } from 'express';
2-
import cors from 'cors';
3-
import swaggerUi from 'swagger-ui-express';
4-
import { swaggerSpec } from './config/swagger.js';
5-
import { apiVersionMiddleware, type VersionedRequest } from './middleware/api-version.middleware.js';
6-
import { sandboxMiddleware } from './middleware/sandbox.middleware.js';
7-
import { globalRateLimiter } from './middleware/rate-limiter.middleware.js';
8-
import { requestIdMiddleware } from './middleware/requestId.js';
9-
import v1Routes from './routes/v1/index.js';
10-
11-
import healthRoutes from './routes/health.routes.js';
1+
import express, {
2+
type Request,
3+
type Response,
4+
type NextFunction,
5+
} from "express";
6+
import cors from "cors";
7+
import swaggerUi from "swagger-ui-express";
8+
import { swaggerSpec } from "./config/swagger.js";
9+
import {
10+
apiVersionMiddleware,
11+
type VersionedRequest,
12+
} from "./middleware/api-version.middleware.js";
13+
import { sandboxMiddleware } from "./middleware/sandbox.middleware.js";
14+
import { globalRateLimiter } from "./middleware/rate-limiter.middleware.js";
15+
import { requestIdMiddleware } from "./middleware/requestId.js";
16+
import v1Routes from "./routes/v1/index.js";
17+
18+
import healthRoutes from "./routes/health.routes.js";
1219

1320
const app = express();
14-
const isProduction = process.env.NODE_ENV === 'production';
15-
const rawCors = process.env.CORS_ALLOWED_ORIGINS ?? '';
21+
const isProduction = process.env.NODE_ENV === "production";
22+
const rawCors = process.env.CORS_ALLOWED_ORIGINS ?? "";
1623
const allowedOrigins = rawCors
17-
.split(',')
18-
.map((origin) => origin.trim())
19-
.filter(Boolean);
24+
.split(",")
25+
.map((origin) => origin.trim())
26+
.filter(Boolean);
2027

2128
// Default in development to only localhost:3000 (frontend dev server)
2229
if (!process.env.CORS_ALLOWED_ORIGINS && !isProduction) {
23-
allowedOrigins.push('http://localhost:3000');
30+
allowedOrigins.push("http://localhost:3000");
2431
}
2532

2633
// Apply global rate limiter first
@@ -29,52 +36,60 @@ app.use(globalRateLimiter);
2936
// Request ID tracing
3037
app.use(requestIdMiddleware);
3138

32-
app.disable('x-powered-by');
39+
app.disable("x-powered-by");
3340

3441
// Helmet-equivalent core headers without external dependency.
3542
// Strict CSP applied globally; the /api-docs route overrides it below for Swagger UI.
3643
app.use((req: Request, res: Response, next: NextFunction) => {
37-
res.setHeader('X-Content-Type-Options', 'nosniff');
38-
res.setHeader('X-Frame-Options', 'DENY');
39-
res.setHeader('Referrer-Policy', 'no-referrer');
40-
res.setHeader('X-DNS-Prefetch-Control', 'off');
41-
res.setHeader('X-Download-Options', 'noopen');
42-
res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
43-
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'");
44-
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
45-
res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
46-
if (process.env.NODE_ENV === 'production') {
47-
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
48-
}
49-
next();
44+
res.setHeader("X-Content-Type-Options", "nosniff");
45+
res.setHeader("X-Frame-Options", "DENY");
46+
res.setHeader("Referrer-Policy", "no-referrer");
47+
res.setHeader("X-DNS-Prefetch-Control", "off");
48+
res.setHeader("X-Download-Options", "noopen");
49+
res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
50+
res.setHeader(
51+
"Content-Security-Policy",
52+
"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'",
53+
);
54+
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
55+
res.setHeader("Cross-Origin-Resource-Policy", "same-origin");
56+
if (process.env.NODE_ENV === "production") {
57+
res.setHeader(
58+
"Strict-Transport-Security",
59+
"max-age=31536000; includeSubDomains",
60+
);
61+
}
62+
next();
5063
});
5164

52-
app.use(cors({
65+
app.use(
66+
cors({
5367
origin(origin, callback) {
54-
// Allow non-browser clients (no Origin header)
55-
if (!origin) {
56-
callback(null, true);
57-
return;
58-
}
59-
60-
if (allowedOrigins.includes(origin)) {
61-
callback(null, true);
62-
return;
63-
}
64-
65-
// Not allowed
66-
callback(new Error('CORS origin not allowed'));
68+
// Allow non-browser clients (no Origin header)
69+
if (!origin) {
70+
callback(null, true);
71+
return;
72+
}
73+
74+
if (allowedOrigins.includes(origin)) {
75+
callback(null, true);
76+
return;
77+
}
78+
79+
// Not allowed
80+
callback(new Error("CORS origin not allowed"));
6781
},
6882
credentials: true,
69-
}));
83+
}),
84+
);
7085

7186
// Convert CORS errors into 403 responses so callers get a clear status code
7287
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
73-
if (err instanceof Error && err.message === 'CORS origin not allowed') {
74-
res.status(403).json({ error: 'CORS origin not allowed' });
75-
return;
76-
}
77-
next(err);
88+
if (err instanceof Error && err.message === "CORS origin not allowed") {
89+
res.status(403).json({ error: "CORS origin not allowed" });
90+
return;
91+
}
92+
next(err);
7893
});
7994
app.use(express.json());
8095

@@ -83,38 +98,76 @@ app.use(sandboxMiddleware);
8398

8499
// Swagger UI setup
85100
// Override CSP for /api-docs only: Swagger UI requires inline scripts/styles.
86-
app.use('/api-docs', (req: Request, res: Response, next: NextFunction) => {
87-
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'");
101+
app.use(
102+
"/api-docs",
103+
(req: Request, res: Response, next: NextFunction) => {
104+
res.setHeader(
105+
"Content-Security-Policy",
106+
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; object-src 'none'",
107+
);
88108
next();
89-
}, swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
90-
customCss: '.swagger-ui .topbar { display: none }',
91-
customSiteTitle: 'FlowFi API Documentation',
92-
}));
109+
},
110+
swaggerUi.serve,
111+
swaggerUi.setup(swaggerSpec, {
112+
customCss: ".swagger-ui .topbar { display: none }",
113+
customSiteTitle: "FlowFi API Documentation",
114+
}),
115+
);
93116

94117
// Serve raw OpenAPI spec as JSON
95-
app.get('/api-docs.json', (req: Request, res: Response) => {
96-
res.setHeader('Content-Type', 'application/json');
97-
res.send(swaggerSpec);
118+
app.get("/api-docs.json", (req: Request, res: Response) => {
119+
res.setHeader("Content-Type", "application/json");
120+
res.send(swaggerSpec);
98121
});
99122

100123
// API Versioning
101124
// All versioned routes must include version prefix (e.g., /v1/streams)
102125
app.use(apiVersionMiddleware);
103126

127+
// Legacy (pre-versioning) routes: respond with 410 Gone and a migration hint
128+
// instead of falling through to a bare 404. Only the specific legacy paths
129+
// that used to exist before /v1 are covered here.
130+
const DEPRECATED_ROUTES: Record<string, string> = {
131+
"/streams": "/v1/streams",
132+
"/events": "/v1/events",
133+
};
134+
135+
app.use((req: Request, res: Response, next: NextFunction) => {
136+
const versionedReq = req as VersionedRequest;
137+
if (versionedReq.apiVersion) {
138+
// Already versioned; nothing to do here.
139+
return next();
140+
}
141+
142+
const newPath = DEPRECATED_ROUTES[req.path];
143+
if (newPath) {
144+
res.status(410).json({
145+
deprecated: true,
146+
migration: {
147+
old: req.path,
148+
new: newPath,
149+
},
150+
});
151+
return;
152+
}
153+
154+
return next();
155+
});
156+
104157
// Versioned API routes
105158
// After versioning middleware, /v1/streams becomes /streams, so we mount v1Routes at root
106159
// But only handle requests that had a version prefix (apiVersion is set)
107160
app.use((req: Request, res: Response, next: NextFunction) => {
108-
const versionedReq = req as VersionedRequest;
109-
if (versionedReq.apiVersion) {
110-
// This was a versioned request, route to v1 handlers
111-
return v1Routes(req, res, next);
112-
}
113-
return next(); // Not versioned, continue to deprecated handlers
161+
const versionedReq = req as VersionedRequest;
162+
if (versionedReq.apiVersion) {
163+
// This was a versioned request, route to v1 handlers
164+
return v1Routes(req, res, next);
165+
}
166+
return next(); // Not versioned, continue to deprecated handlers
114167
});
115168

116169
// Health check routes
117-
app.use('/health', healthRoutes);
170+
app.use("/health", healthRoutes);
118171

119172
/**
120173
* @openapi
@@ -128,11 +181,11 @@ app.use('/health', healthRoutes);
128181
* 200:
129182
* description: API is running successfully
130183
*/
131-
app.get('/', (req: Request, res: Response) => {
132-
res.send('FlowFi Backend is running');
184+
app.get("/", (req: Request, res: Response) => {
185+
res.send("FlowFi Backend is running");
133186
});
134187

135-
import { errorHandler } from './middleware/error.middleware.js';
188+
import { errorHandler } from "./middleware/error.middleware.js";
136189

137190
app.use(errorHandler);
138191

0 commit comments

Comments
 (0)