Genesis
This commit is contained in:
136
src/api/index.js
Normal file
136
src/api/index.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import { fileURLToPath, pathToFileURL } from 'url';
|
||||
import express from 'express';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { loggerMiddleware } from './middleware/logger.js';
|
||||
import env from '../config/env.js';
|
||||
import { authMiddleware } from './middleware/auth.js';
|
||||
import { ensureConnection } from '../db/client.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
async function loadRoutes(app, basePath = '') {
|
||||
const routesDir = path.join(__dirname, 'routes');
|
||||
|
||||
async function walk(dir) {
|
||||
for (const file of fs.readdirSync(dir)) {
|
||||
const fullPath = path.join(dir, file);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
await walk(fullPath);
|
||||
} else if (file.endsWith('.js')) {
|
||||
const routeModule = await import(pathToFileURL(fullPath).href);
|
||||
if (routeModule.register) {
|
||||
routeModule.register(app, basePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await walk(routesDir);
|
||||
}
|
||||
|
||||
let server;
|
||||
let starting;
|
||||
let startedAt;
|
||||
|
||||
function corsMiddleware(req, res, next) {
|
||||
const origin = req.headers.origin;
|
||||
|
||||
if (origin && env.corsOrigins.includes(origin)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Vary', 'Origin');
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
||||
}
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async function createApi() {
|
||||
const app = express();
|
||||
|
||||
app.use(corsMiddleware);
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(loggerMiddleware);
|
||||
app.use(authMiddleware);
|
||||
|
||||
if (startedAt) {
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Server-Started-At', startedAt);
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
await loadRoutes(app, '/api');
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
export async function startApi() {
|
||||
if (server) {
|
||||
return server;
|
||||
}
|
||||
if (starting) {
|
||||
return starting;
|
||||
}
|
||||
|
||||
starting = (async () => {
|
||||
await ensureConnection();
|
||||
|
||||
startedAt = new Date().toISOString();
|
||||
const app = await createApi();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const instance = app.listen(env.port);
|
||||
|
||||
instance.once('listening', () => {
|
||||
server = instance;
|
||||
console.log(`API listening on port ${env.port}`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
instance.once('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
let detail = '';
|
||||
try {
|
||||
const pids = execSync(`lsof -ti :${env.port}`, { encoding: 'utf8' }).trim();
|
||||
if (pids) detail = ` (PID ${pids.replace(/\n/g, ', ')})`;
|
||||
} catch {
|
||||
// lsof unavailable or no match
|
||||
}
|
||||
console.error(`Port ${env.port} is already in use${detail}. Check with: lsof -i :${env.port}`);
|
||||
} else {
|
||||
console.error('Server error:', err.message);
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
return server;
|
||||
})();
|
||||
|
||||
try {
|
||||
return await starting;
|
||||
} finally {
|
||||
starting = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopApi() {
|
||||
if (!server) return;
|
||||
await new Promise((resolve, reject) => {
|
||||
server.close((err) => (err ? reject(err) : resolve()));
|
||||
});
|
||||
server = undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user