Ich habe soweit automatisiert the Emails sortieren aber ich muss noch schauen was es fur bugs es gibt wenn die app online ist deswegen wurde ich mit diesen Commit die website veroffentlichen obwohjl es sein konnte das es noch nicht fertig ist und verkaufs bereit
169 lines
4.5 KiB
JavaScript
169 lines
4.5 KiB
JavaScript
/**
|
|
* EmailSorter Backend Server
|
|
* Main entry point
|
|
*/
|
|
|
|
import 'dotenv/config'
|
|
import express from 'express'
|
|
import cors from 'cors'
|
|
import { fileURLToPath } from 'url'
|
|
import { dirname, join } from 'path'
|
|
|
|
// Config & Middleware
|
|
import { config, validateConfig } from './config/index.mjs'
|
|
import { errorHandler, asyncHandler, NotFoundError, ValidationError } from './middleware/errorHandler.mjs'
|
|
import { respond } from './utils/response.mjs'
|
|
import { logger, log } from './middleware/logger.mjs'
|
|
import { limiters } from './middleware/rateLimit.mjs'
|
|
|
|
// Routes
|
|
import oauthRoutes from './routes/oauth.mjs'
|
|
import emailRoutes from './routes/email.mjs'
|
|
import stripeRoutes from './routes/stripe.mjs'
|
|
import apiRoutes from './routes/api.mjs'
|
|
import analyticsRoutes from './routes/analytics.mjs'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
|
|
// Validate configuration
|
|
validateConfig()
|
|
|
|
// Create Express app
|
|
const app = express()
|
|
|
|
// Trust proxy (for rate limiting behind reverse proxy)
|
|
app.set('trust proxy', 1)
|
|
|
|
// Request ID middleware
|
|
app.use((req, res, next) => {
|
|
req.id = Math.random().toString(36).substring(2, 15)
|
|
res.setHeader('X-Request-ID', req.id)
|
|
next()
|
|
})
|
|
|
|
// CORS
|
|
app.use(cors(config.cors))
|
|
|
|
// Request logging
|
|
app.use(logger({
|
|
skip: (req) => req.path === '/api/health' || req.path.startsWith('/assets'),
|
|
}))
|
|
|
|
// Rate limiting
|
|
app.use('/api', limiters.api)
|
|
|
|
// Static files
|
|
app.use(express.static(join(__dirname, '..', 'public')))
|
|
|
|
// Body parsing (BEFORE routes, AFTER static)
|
|
// Note: Stripe webhook needs raw body, handled in stripe routes
|
|
app.use('/api', express.json({ limit: '1mb' }))
|
|
app.use('/api', express.urlencoded({ extended: true }))
|
|
|
|
// Health check (no rate limit)
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
version: process.env.npm_package_version || '1.0.0',
|
|
environment: config.nodeEnv,
|
|
uptime: Math.floor(process.uptime()),
|
|
},
|
|
})
|
|
})
|
|
|
|
// API Routes
|
|
app.use('/api/oauth', oauthRoutes)
|
|
app.use('/api/email', emailRoutes)
|
|
app.use('/api/subscription', stripeRoutes)
|
|
app.use('/api/analytics', analyticsRoutes)
|
|
app.use('/api', apiRoutes)
|
|
|
|
// Preferences endpoints (inline for simplicity)
|
|
import { userPreferences } from './services/database.mjs'
|
|
|
|
app.get('/api/preferences', asyncHandler(async (req, res) => {
|
|
const { userId } = req.query
|
|
if (!userId) throw new ValidationError('userId ist erforderlich')
|
|
|
|
const prefs = await userPreferences.getByUser(userId)
|
|
respond.success(res, prefs?.preferences || {
|
|
vipSenders: [],
|
|
blockedSenders: [],
|
|
customRules: [],
|
|
priorityTopics: [],
|
|
})
|
|
}))
|
|
|
|
app.post('/api/preferences', asyncHandler(async (req, res) => {
|
|
const { userId, ...preferences } = req.body
|
|
if (!userId) throw new ValidationError('userId ist erforderlich')
|
|
|
|
await userPreferences.upsert(userId, preferences)
|
|
respond.success(res, null, 'Einstellungen gespeichert')
|
|
}))
|
|
|
|
// Legacy Stripe webhook endpoint
|
|
app.use('/stripe', stripeRoutes)
|
|
|
|
// 404 handler for API routes
|
|
app.use('/api/*', (req, res, next) => {
|
|
next(new NotFoundError('Endpoint'))
|
|
})
|
|
|
|
// SPA fallback for non-API routes
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(join(__dirname, '..', 'public', 'index.html'))
|
|
})
|
|
|
|
// Global error handler (must be last)
|
|
app.use(errorHandler)
|
|
|
|
// Graceful shutdown
|
|
let server
|
|
|
|
function gracefulShutdown(signal) {
|
|
log.info(`${signal} empfangen, Server wird heruntergefahren...`)
|
|
|
|
server.close(() => {
|
|
log.info('HTTP Server geschlossen')
|
|
process.exit(0)
|
|
})
|
|
|
|
// Force close after 10 seconds
|
|
setTimeout(() => {
|
|
log.error('Erzwungenes Herunterfahren')
|
|
process.exit(1)
|
|
}, 10000)
|
|
}
|
|
|
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
|
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'))
|
|
|
|
// Handle uncaught errors
|
|
process.on('uncaughtException', (err) => {
|
|
log.error('Uncaught Exception:', { error: err.message, stack: err.stack })
|
|
process.exit(1)
|
|
})
|
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
log.error('Unhandled Rejection:', { reason, promise })
|
|
})
|
|
|
|
// Start server
|
|
server = app.listen(config.port, () => {
|
|
console.log('')
|
|
log.success(`Server gestartet auf Port ${config.port}`)
|
|
log.info(`Frontend URL: ${config.frontendUrl}`)
|
|
log.info(`Environment: ${config.nodeEnv}`)
|
|
console.log('')
|
|
console.log(` 🌐 API: http://localhost:${config.port}/api`)
|
|
console.log(` 💚 Health: http://localhost:${config.port}/api/health`)
|
|
console.log('')
|
|
})
|
|
|
|
export default app
|