dfssdfsfdsf
This commit is contained in:
2026-04-09 21:00:04 +02:00
parent 983b67e6fc
commit 89bc86b615
27 changed files with 2921 additions and 408 deletions

View File

@@ -11,20 +11,24 @@ import { dirname, join } from 'path'
// Config & Middleware
import { config, validateConfig } from './config/index.mjs'
import { errorHandler, asyncHandler, AppError, NotFoundError, ValidationError, AuthorizationError } from './middleware/errorHandler.mjs'
import { errorHandler, asyncHandler, AppError, ValidationError, AuthorizationError } from './middleware/errorHandler.mjs'
import { respond } from './utils/response.mjs'
import { logger, log } from './middleware/logger.mjs'
import { limiters } from './middleware/rateLimit.mjs'
import { requireAuth } from './middleware/auth.mjs'
import { requireAuth, requireAuthUnlessEmailWebhook } from './middleware/auth.mjs'
// Routes
import oauthRoutes from './routes/oauth.mjs'
import emailRoutes from './routes/email.mjs'
import { handleGetDigest } from './routes/email.mjs'
import stripeRoutes from './routes/stripe.mjs'
import { handleGetSubscriptionStatus } from './routes/stripe.mjs'
import apiRoutes from './routes/api.mjs'
import { handleGetReferralCode } from './routes/api.mjs'
import analyticsRoutes from './routes/analytics.mjs'
import webhookRoutes from './routes/webhook.mjs'
import { startCounterJobs } from './jobs/reset-counters.mjs'
import { startAutoSortJob } from './jobs/auto-sort.mjs'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
@@ -45,6 +49,21 @@ app.use((req, res, next) => {
next()
})
// Safety net (before all routers): collapse /api/api → /api in path until stable — old clients / bad env
app.use((req, _res, next) => {
const [pathPart, ...q] = req.originalUrl.split('?')
let p = pathPart
let prev = ''
while (p !== prev && p.includes('/api/api')) {
prev = p
p = p.replace(/\/api\/api/g, '/api')
}
if (p !== pathPart) {
req.url = q.length ? `${p}?${q.join('?')}` : p
}
next()
})
// CORS
app.use(cors(config.cors))
@@ -74,7 +93,9 @@ app.get('/api/health', (req, res) => {
res.json({
success: true,
data: {
service: 'mailflow-api',
status: 'healthy',
port: config.port,
timestamp: new Date().toISOString(),
version: process.env.npm_package_version || '1.0.0',
environment: config.nodeEnv,
@@ -83,6 +104,18 @@ app.get('/api/health', (req, res) => {
})
})
/*
* Route index — these three are implemented in routes/*.mjs and registered here FIRST
* (before app.use mounts) so GET always matches real handlers, not the JSON 404 catch-all.
*
* GET /api/email/digest → handleGetDigest (routes/email.mjs)
* GET /api/subscription/status → handleGetSubscriptionStatus (routes/stripe.mjs)
* GET /api/referrals/code → handleGetReferralCode (routes/api.mjs)
*/
app.get('/api/email/digest', requireAuthUnlessEmailWebhook, asyncHandler(handleGetDigest))
app.get('/api/subscription/status', requireAuth, asyncHandler(handleGetSubscriptionStatus))
app.get('/api/referrals/code', requireAuth, asyncHandler(handleGetReferralCode))
// API Routes
app.use('/api/oauth', oauthRoutes)
app.use('/api/email', emailRoutes)
@@ -302,20 +335,12 @@ app.delete('/api/preferences/name-labels/:id', requireAuth, asyncHandler(async (
// Legacy Stripe webhook endpoint
app.use('/stripe', stripeRoutes)
// Unmatched /api → JSON 404 (Express 4 treats '/api/*' as a literal path, not a wildcard)
app.use((req, res, next) => {
const pathOnly = req.originalUrl.split('?')[0]
if (!pathOnly.startsWith('/api')) {
return next()
}
next(new NotFoundError('Endpoint'))
})
// SPA fallback: never send index.html for /api (avoids 404/HTML when public/index.html is missing)
app.get('*', (req, res, next) => {
const pathOnly = req.originalUrl.split('?')[0]
if (pathOnly.startsWith('/api')) {
return next(new NotFoundError('Endpoint'))
console.warn('[404] Unmatched route:', req.method, req.originalUrl)
return res.status(404).json({ error: 'Endpoint not found', path: req.originalUrl })
}
const indexPath = join(__dirname, '..', 'public', 'index.html')
res.sendFile(indexPath, (err) => {
@@ -331,6 +356,15 @@ app.get('*', (req, res, next) => {
})
})
// Catch-all: any method/path that did not send a response (e.g. POST /unknown)
app.use((req, res, next) => {
if (res.headersSent) {
return next()
}
console.warn('[404] Unmatched route:', req.method, req.originalUrl)
res.status(404).json({ error: 'Endpoint not found', path: req.originalUrl })
})
// Global error handler (must be last)
app.use(errorHandler)
@@ -376,6 +410,7 @@ server = app.listen(config.port, () => {
console.log(` 💚 Health: http://localhost:${config.port}/api/health`)
console.log('')
startCounterJobs()
startAutoSortJob()
})
export default app