218 lines
13 KiB
JavaScript
218 lines
13 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.corsOrigins = void 0;
|
|
exports.createApp = createApp;
|
|
const express_1 = __importDefault(require("express"));
|
|
const cors_1 = __importDefault(require("cors"));
|
|
const helmet_1 = __importDefault(require("helmet"));
|
|
const morgan_1 = __importDefault(require("morgan"));
|
|
const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
|
|
const openapi_1 = require("./swagger/openapi");
|
|
const storage_1 = require("./lib/storage");
|
|
const rateLimiter_1 = require("./middleware/rateLimiter");
|
|
const requestId_1 = require("./middleware/requestId");
|
|
// ─── Module routes ────────────────────────────────────────────
|
|
const webhook_routes_1 = __importDefault(require("./modules/webhooks/webhook.routes"));
|
|
const auth_company_routes_1 = __importDefault(require("./modules/auth/auth.company.routes"));
|
|
const auth_employee_routes_1 = __importDefault(require("./modules/auth/auth.employee.routes"));
|
|
const auth_renter_routes_1 = __importDefault(require("./modules/auth/auth.renter.routes"));
|
|
const team_routes_1 = __importDefault(require("./modules/team/team.routes"));
|
|
const offer_routes_1 = __importDefault(require("./modules/offers/offer.routes"));
|
|
const analytics_routes_1 = __importDefault(require("./modules/analytics/analytics.routes"));
|
|
const notification_routes_1 = __importDefault(require("./modules/notifications/notification.routes"));
|
|
const admin_routes_1 = __importDefault(require("./modules/admin/admin.routes"));
|
|
const subscription_routes_1 = __importStar(require("./modules/subscriptions/subscription.routes"));
|
|
const payment_routes_1 = __importDefault(require("./modules/payments/payment.routes"));
|
|
const customer_routes_1 = __importDefault(require("./modules/customers/customer.routes"));
|
|
const vehicle_routes_1 = __importDefault(require("./modules/vehicles/vehicle.routes"));
|
|
const company_routes_1 = __importDefault(require("./modules/companies/company.routes"));
|
|
const reservation_routes_1 = __importDefault(require("./modules/reservations/reservation.routes"));
|
|
const marketplace_routes_1 = __importDefault(require("./modules/marketplace/marketplace.routes"));
|
|
const site_routes_1 = __importDefault(require("./modules/site/site.routes"));
|
|
const review_routes_1 = __importDefault(require("./modules/reviews/review.routes"));
|
|
const complaint_routes_1 = __importDefault(require("./modules/complaints/complaint.routes"));
|
|
// ─── Centralized error handling ───────────────────────────────
|
|
const errorMiddleware_1 = require("./http/errors/errorMiddleware");
|
|
const v1 = '/api/v1';
|
|
const defaultCorsOrigins = [
|
|
'http://localhost:3000',
|
|
'http://localhost:4000',
|
|
'http://127.0.0.1:3000',
|
|
'http://127.0.0.1:4000',
|
|
];
|
|
exports.corsOrigins = process.env.CORS_ORIGINS
|
|
? process.env.CORS_ORIGINS.split(',').map((o) => o.trim()).filter(Boolean)
|
|
: defaultCorsOrigins;
|
|
const routeDocs = [
|
|
{ method: 'GET', path: '/health', description: 'Health check' },
|
|
{ method: 'GET', path: `${v1}/docs`, description: 'Machine-readable API index' },
|
|
{ method: 'GET', path: `${v1}/auth/renter/me`, description: 'Current renter profile' },
|
|
{ method: 'GET', path: `${v1}/vehicles`, description: 'List company vehicles' },
|
|
{ method: 'POST', path: `${v1}/vehicles`, description: 'Create vehicle' },
|
|
{ method: 'GET', path: `${v1}/reservations`, description: 'List reservations' },
|
|
{ method: 'POST', path: `${v1}/reservations`, description: 'Create reservation' },
|
|
{ method: 'GET', path: `${v1}/customers`, description: 'List customers' },
|
|
{ method: 'POST', path: `${v1}/customers`, description: 'Create customer' },
|
|
{ method: 'GET', path: `${v1}/offers`, description: 'List offers' },
|
|
{ method: 'GET', path: `${v1}/analytics/dashboard`, description: 'Dashboard analytics' },
|
|
{ method: 'GET', path: `${v1}/analytics/report`, description: 'Analytics report' },
|
|
{ method: 'GET', path: `${v1}/notifications/company`, description: 'Company notifications' },
|
|
{ method: 'GET', path: `${v1}/marketplace/cities`, description: 'Marketplace cities' },
|
|
{ method: 'GET', path: `${v1}/marketplace/search`, description: 'Marketplace search' },
|
|
{ method: 'GET', path: `${v1}/site/:slug/brand`, description: 'Public site brand config' },
|
|
{ method: 'GET', path: `${v1}/companies/me`, description: 'Current company profile' },
|
|
{ method: 'GET', path: `${v1}/subscriptions/plans`, description: 'Subscription plans' },
|
|
{ method: 'GET', path: `${v1}/subscriptions/features`, description: 'Subscription plan features' },
|
|
{ method: 'POST', path: `${v1}/admin/auth/login`, description: 'Admin login' },
|
|
];
|
|
function createApp() {
|
|
const app = (0, express_1.default)();
|
|
const corsMiddleware = (0, cors_1.default)({ origin: exports.corsOrigins, credentials: true });
|
|
if (process.env.NODE_ENV === 'production') {
|
|
app.set('trust proxy', 1);
|
|
}
|
|
app.use(requestId_1.requestIdMiddleware);
|
|
app.use((req, res, next) => {
|
|
if (req.headers['x-middleware-subrequest']) {
|
|
return res.status(400).json({ error: 'bad_request', message: 'Unsupported internal request header', statusCode: 400 });
|
|
}
|
|
next();
|
|
});
|
|
app.use(corsMiddleware);
|
|
// Customer identity documents must never be anonymously retrievable from the
|
|
// public storage mount, even if an older raw storage URL leaks.
|
|
app.use('/storage/companies/:companyId/customers/:customerId', (_req, res) => {
|
|
res.status(404).end();
|
|
});
|
|
// Public storage assets (logos, vehicle photos, etc.) must be loadable cross-origin
|
|
// by the browser. Without this header, helmet's default CORP: same-origin reaches
|
|
// 404 responses (when express.static calls next() on a miss) and the browser blocks
|
|
// the response even for broken-image cases, producing ERR_BLOCKED_BY_RESPONSE.
|
|
app.use('/storage', (_req, res, next) => {
|
|
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
next();
|
|
});
|
|
// Serve only explicitly public uploaded assets. Private documents are resolved
|
|
// through authenticated API routes such as /customers/:id/license-image.
|
|
app.use('/storage', express_1.default.static((0, storage_1.getPublicStorageRoot)()));
|
|
// Swagger UI — mounted before helmet so its assets are not blocked by CSP
|
|
app.use('/docs', swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(openapi_1.openApiDocument, { customSiteTitle: 'RentalDriveGo API Docs' }));
|
|
app.get('/api/v1/openapi.json', (_req, res) => res.json(openapi_1.openApiDocument));
|
|
// Webhooks must use raw body BEFORE express.json(); signature verification
|
|
// must never reconstruct the payload with JSON.stringify(req.body).
|
|
app.use(`${v1}/webhooks`, express_1.default.raw({ type: 'application/json' }), webhook_routes_1.default);
|
|
app.use(`${v1}/payments/webhooks`, express_1.default.raw({ type: 'application/json' }));
|
|
app.use(`${v1}/subscriptions/webhooks`, express_1.default.raw({ type: 'application/json' }));
|
|
// Let /storage responses manage CORP explicitly so missing files still return
|
|
// a normal cross-origin 404 instead of being blocked by Helmet's default
|
|
// same-origin policy. Keep CSP explicit instead of letting browser security
|
|
// drift into wishful thinking with headers.
|
|
app.use((0, helmet_1.default)({
|
|
crossOriginResourcePolicy: false,
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
baseUri: ["'self'"],
|
|
frameAncestors: ["'none'"],
|
|
formAction: ["'self'"],
|
|
objectSrc: ["'none'"],
|
|
scriptSrc: ["'self'"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
|
connectSrc: ["'self'", 'https:', 'wss:'],
|
|
upgradeInsecureRequests: process.env.NODE_ENV === 'production' ? [] : null,
|
|
},
|
|
},
|
|
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
|
frameguard: { action: 'deny' },
|
|
}));
|
|
if (process.env.NODE_ENV !== 'test')
|
|
app.use((0, morgan_1.default)('combined'));
|
|
app.use(express_1.default.json({ limit: '10mb' }));
|
|
// ─── API Routes ─────────────────────────────────────────────
|
|
app.use(`${v1}/auth/renter`, rateLimiter_1.authLimiter, auth_renter_routes_1.default);
|
|
app.use(`${v1}/auth/company`, rateLimiter_1.authLimiter, auth_company_routes_1.default);
|
|
app.use(`${v1}/auth/employee`, rateLimiter_1.authLimiter, auth_employee_routes_1.default);
|
|
app.use(`${v1}/admin/auth`, rateLimiter_1.authLimiter);
|
|
app.use(`${v1}/admin`, rateLimiter_1.adminLimiter, admin_routes_1.default);
|
|
app.use(`${v1}/marketplace`, rateLimiter_1.publicLimiter, marketplace_routes_1.default);
|
|
app.use(`${v1}/site`, rateLimiter_1.publicLimiter, site_routes_1.default);
|
|
app.use(`${v1}/subscriptions`, subscription_routes_1.subscriptionPublicRouter);
|
|
app.use(`${v1}/subscriptions`, subscription_routes_1.subscriptionWebhookRouter);
|
|
app.use(`${v1}/vehicles`, rateLimiter_1.apiLimiter, vehicle_routes_1.default);
|
|
app.use(`${v1}/reservations`, rateLimiter_1.apiLimiter, reservation_routes_1.default);
|
|
app.use(`${v1}/team`, rateLimiter_1.apiLimiter, team_routes_1.default);
|
|
app.use(`${v1}/customers`, rateLimiter_1.apiLimiter, customer_routes_1.default);
|
|
app.use(`${v1}/offers`, rateLimiter_1.apiLimiter, offer_routes_1.default);
|
|
app.use(`${v1}/analytics`, rateLimiter_1.apiLimiter, analytics_routes_1.default);
|
|
app.use(`${v1}/notifications`, rateLimiter_1.apiLimiter, notification_routes_1.default);
|
|
app.use(`${v1}/companies`, rateLimiter_1.apiLimiter, company_routes_1.default);
|
|
app.use(`${v1}/subscriptions`, rateLimiter_1.apiLimiter, subscription_routes_1.default);
|
|
app.use(`${v1}/payments`, rateLimiter_1.apiLimiter, payment_routes_1.default);
|
|
app.use(`${v1}/reviews`, rateLimiter_1.apiLimiter, review_routes_1.default);
|
|
app.use(`${v1}/complaints`, rateLimiter_1.apiLimiter, complaint_routes_1.default);
|
|
// ─── Health / Docs ──────────────────────────────────────────
|
|
app.get(v1, (_req, res) => {
|
|
res.json({
|
|
name: 'rentaldrivego-api',
|
|
version: '1.0.0',
|
|
baseUrl: v1,
|
|
health: '/health',
|
|
docs: `${v1}/docs`,
|
|
openApi: `${v1}/openapi.json`,
|
|
});
|
|
});
|
|
app.get('/health', (_req, res) => {
|
|
res.json({ status: 'ok', version: '1.0.0', timestamp: new Date().toISOString() });
|
|
});
|
|
app.get(`${v1}/docs`, (_req, res) => {
|
|
res.json({
|
|
name: 'rentaldrivego-api',
|
|
version: '1.0.0',
|
|
baseUrl: v1,
|
|
routes: routeDocs,
|
|
swaggerUi: '/docs',
|
|
openApi: '/api/v1/openapi.json',
|
|
});
|
|
});
|
|
// ─── Error handler ──────────────────────────────────────────
|
|
app.use(errorMiddleware_1.errorMiddleware);
|
|
return app;
|
|
}
|
|
//# sourceMappingURL=app.js.map
|