fixing platform admin

This commit is contained in:
root
2026-05-06 22:58:23 -04:00
parent 695a7f7cc7
commit 750ae56a29
175 changed files with 31249 additions and 328 deletions
+149 -16
View File
@@ -5,11 +5,15 @@ import morgan from 'morgan'
import http from 'http'
import { Server as SocketIOServer } from 'socket.io'
import cron from 'node-cron'
import jwt from 'jsonwebtoken'
import { z } from 'zod'
import { redis } from './lib/redis'
import { prisma } from './lib/prisma'
import { authLimiter, apiLimiter, publicLimiter, adminLimiter } from './middleware/rateLimiter'
// ─── Routes ───────────────────────────────────────────────────
import webhookRouter from './routes/webhooks'
import companyAuthRouter from './routes/auth.company'
import renterAuthRouter from './routes/auth.renter'
import vehiclesRouter from './routes/vehicles'
import reservationsRouter from './routes/reservations'
@@ -20,17 +24,74 @@ import analyticsRouter from './routes/analytics'
import notificationsRouter from './routes/notifications'
import marketplaceRouter from './routes/marketplace'
import adminRouter from './routes/admin'
import companiesRouter from './routes/companies'
import subscriptionsRouter from './routes/subscriptions'
import siteRouter from './routes/site'
import paymentsRouter from './routes/payments'
const app = express()
const server = http.createServer(app)
const v1 = '/api/v1'
const defaultCorsOrigins = [
'http://localhost:3000',
'http://localhost:3001',
'http://localhost:3002',
'http://localhost:3003',
'http://127.0.0.1:3000',
'http://127.0.0.1:3001',
'http://127.0.0.1:3002',
'http://127.0.0.1:3003',
]
const corsOrigins = process.env.CORS_ORIGINS
? process.env.CORS_ORIGINS.split(',').map((origin) => origin.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/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: 'POST', path: `${v1}/admin/auth/login`, description: 'Admin login' },
]
// ─── Socket.io ────────────────────────────────────────────────
const io = new SocketIOServer(server, {
cors: { origin: '*', methods: ['GET', 'POST'] },
cors: { origin: corsOrigins, credentials: true, methods: ['GET', 'POST'] },
})
const redisMessageSchema = z.object({
type: z.string(),
payload: z.unknown(),
})
// Authenticate socket connections via JWT before joining user rooms
io.use((socket, next) => {
const token = socket.handshake.auth?.token as string | undefined
if (!token) return next() // unauthenticated connections allowed; they just don't join rooms
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
;(socket as any).authenticatedUserId = payload.sub
next()
} catch {
next(new Error('invalid_token'))
}
})
io.on('connection', (socket) => {
const userId = socket.handshake.auth?.userId as string | undefined
const userId = (socket as any).authenticatedUserId as string | undefined
if (userId) {
socket.join(`user:${userId}`)
}
@@ -42,8 +103,14 @@ subscriber.psubscribe('notifications:*', (err) => {
if (err) console.error('[Redis] Subscribe error:', err)
})
subscriber.on('pmessage', (_pattern, channel, message) => {
const userId = channel.replace('notifications:', '')
io.to(`user:${userId}`).emit('notification', JSON.parse(message))
try {
const parsed = JSON.parse(message)
const validated = redisMessageSchema.parse(parsed)
const userId = channel.replace('notifications:', '')
io.to(`user:${userId}`).emit('notification', validated)
} catch (err) {
console.error('[Redis] Invalid notification message:', err)
}
})
// ─── Middleware ────────────────────────────────────────────────
@@ -51,29 +118,95 @@ subscriber.on('pmessage', (_pattern, channel, message) => {
app.use('/api/v1/webhooks', express.raw({ type: 'application/json' }), webhookRouter)
app.use(helmet())
app.use(cors({ origin: process.env.CORS_ORIGINS?.split(',') ?? '*', credentials: true }))
app.use(cors({ origin: corsOrigins, credentials: true }))
app.use(morgan('combined'))
app.use(express.json({ limit: '10mb' }))
// ─── API Routes ───────────────────────────────────────────────
const v1 = '/api/v1'
// Auth routes: strict brute-force protection
app.use(`${v1}/auth/renter`, authLimiter, renterAuthRouter)
app.use(`${v1}/auth/company`, authLimiter, companyAuthRouter)
app.use(`${v1}/auth/renter`, renterAuthRouter)
app.use(`${v1}/vehicles`, vehiclesRouter)
app.use(`${v1}/reservations`, reservationsRouter)
app.use(`${v1}/team`, teamRouter)
app.use(`${v1}/customers`, customersRouter)
app.use(`${v1}/offers`, offersRouter)
app.use(`${v1}/analytics`, analyticsRouter)
app.use(`${v1}/notifications`, notificationsRouter)
app.use(`${v1}/marketplace`, marketplaceRouter)
app.use(`${v1}/admin`, adminRouter)
// Admin routes: dedicated limit
app.use(`${v1}/admin`, adminLimiter, adminRouter)
// Public unauthenticated routes
app.use(`${v1}/marketplace`, publicLimiter, marketplaceRouter)
app.use(`${v1}/site`, publicLimiter, siteRouter)
// Authenticated company/renter routes
app.use(`${v1}/vehicles`, apiLimiter, vehiclesRouter)
app.use(`${v1}/reservations`, apiLimiter, reservationsRouter)
app.use(`${v1}/team`, apiLimiter, teamRouter)
app.use(`${v1}/customers`, apiLimiter, customersRouter)
app.use(`${v1}/offers`, apiLimiter, offersRouter)
app.use(`${v1}/analytics`, apiLimiter, analyticsRouter)
app.use(`${v1}/notifications`, apiLimiter, notificationsRouter)
app.use(`${v1}/companies`, apiLimiter, companiesRouter)
app.use(`${v1}/subscriptions`, apiLimiter, subscriptionsRouter)
app.use(`${v1}/payments`, apiLimiter, paymentsRouter)
// ─── Health check ─────────────────────────────────────────────
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,
docsUrl: '/docs',
routes: routeDocs,
})
})
app.get('/docs', (_req, res) => {
const rows = routeDocs
.map(
(route) => `
<tr>
<td>${route.method}</td>
<td><code>${route.path}</code></td>
<td>${route.description}</td>
</tr>`,
)
.join('')
res.type('html').send(`<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>RentalDriveGo API Docs</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; color: #0f172a; background: #f8fafc; }
h1 { margin-bottom: 8px; }
p { color: #475569; }
.meta { margin-bottom: 24px; }
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 12px; overflow: hidden; }
th, td { text-align: left; padding: 12px 16px; border-bottom: 1px solid #e2e8f0; }
th { background: #e2e8f0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
code { background: #f1f5f9; padding: 2px 6px; border-radius: 6px; }
</style>
</head>
<body>
<h1>RentalDriveGo API</h1>
<p class="meta">Base URL: <code>${v1}</code> · JSON index: <code>${v1}/docs</code></p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Path</th>
<th>Description</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</body>
</html>`)
})
// ─── Error handler ────────────────────────────────────────────
app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
const statusCode = err.statusCode ?? 500