1a12fb870b
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
113 lines
3.9 KiB
JavaScript
113 lines
3.9 KiB
JavaScript
/**
|
|
* quiXzoom Frilans Payout Server
|
|
* Express backend for multi-market payout
|
|
*
|
|
* Built per REXO standards:
|
|
* - Apple iPhone Native UI
|
|
* - Raw SQL via pg
|
|
* - JWT auth
|
|
* - pino logging
|
|
* - systemd deployment
|
|
*/
|
|
|
|
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import rateLimit from 'express-rate-limit';
|
|
import pino from 'pino';
|
|
import routes from './routes.mjs';
|
|
|
|
const logger = pino({
|
|
level: process.env.LOG_LEVEL || 'info',
|
|
transport: process.env.NODE_ENV !== 'production'
|
|
? { target: 'pino-pretty', options: { colorize: true } }
|
|
: undefined,
|
|
});
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3450;
|
|
|
|
// ── Middleware ──────────────────────────────────────────────────────────────
|
|
|
|
app.use(helmet());
|
|
app.use(cors({
|
|
origin: process.env.CORS_ORIGIN || '*',
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
}));
|
|
|
|
app.use(express.json());
|
|
|
|
// Rate limiting
|
|
const limiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000,
|
|
max: 100,
|
|
message: { error: 'Too many requests, please try again later' },
|
|
});
|
|
app.use('/api/', limiter);
|
|
|
|
// Request logging
|
|
app.use((req, res, next) => {
|
|
logger.info({ method: req.method, path: req.path, ip: req.ip }, 'Request');
|
|
next();
|
|
});
|
|
|
|
// ── Auth Middleware (JWT) ───────────────────────────────────────────────────
|
|
|
|
app.use('/api/aamos/frilans', (req, res, next) => {
|
|
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
if (!token) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
// TODO: Validate JWT token
|
|
// For now, mock user
|
|
req.user = { id: token };
|
|
next();
|
|
});
|
|
|
|
// ── Routes ──────────────────────────────────────────────────────────────────
|
|
|
|
app.use('/api/aamos/frilans', routes);
|
|
|
|
// ── Health Check ────────────────────────────────────────────────────────────
|
|
|
|
app.get('/health', async (req, res) => {
|
|
try {
|
|
const { healthCheck } = await import('./db.mjs');
|
|
await healthCheck();
|
|
res.json({
|
|
status: 'healthy',
|
|
version: '2.0.0',
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
} catch (error) {
|
|
logger.error(error, 'Health check failed');
|
|
res.status(503).json({ status: 'unhealthy', error: error.message });
|
|
}
|
|
});
|
|
|
|
// ── Error Handling ──────────────────────────────────────────────────────────
|
|
|
|
app.use((err, req, res, next) => {
|
|
logger.error(err, 'Unhandled error');
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
|
|
});
|
|
});
|
|
|
|
// ── Start ───────────────────────────────────────────────────────────────────
|
|
|
|
app.listen(PORT, () => {
|
|
logger.info(`
|
|
╔══════════════════════════════════════════════════════════╗
|
|
║ quiXzoom Frilans Payout Server v2.0.0 ║
|
|
║ Port: ${PORT} ║
|
|
║ Environment: ${process.env.NODE_ENV || 'development'} ║
|
|
╚══════════════════════════════════════════════════════════╝
|
|
`);
|
|
});
|
|
|
|
export default app;
|