Public Access
- Added AI routes for generating questions, providing hints, and tutor chat functionality. - Implemented AI client setup and model retrieval in aiClient.ts. - Created aiAssist.ts for handling AI-related operations including question generation and hint retrieval. - Updated services to utilize the new AI client and assist functions. - Enhanced frontend components to support AI question generation and tutor chat, including UI for generating questions and displaying hints. - Added localization strings for new AI features in English and Dutch. - Updated exam review process to include AI-generated hints and tutor chat functionality.
129 lines
3.4 KiB
TypeScript
129 lines
3.4 KiB
TypeScript
import 'dotenv/config'
|
|
import express from 'express'
|
|
import cors from 'cors'
|
|
import helmet from 'helmet'
|
|
import rateLimit from 'express-rate-limit'
|
|
import { getDb, closeDb } from './db/database.js'
|
|
import authRoutes from './routes/auth.js'
|
|
import examRoutes from './routes/exams.js'
|
|
import resultRoutes from './routes/results.js'
|
|
import adminRoutes from './routes/admin.js'
|
|
import setupRoutes from './routes/setup.js'
|
|
import gradeRoutes from './routes/grade.js'
|
|
import shareRoutes from './routes/share.js'
|
|
import collectionRoutes from './routes/collections.js'
|
|
import aiRoutes from './routes/ai.js'
|
|
import { errorHandler } from './middleware/error.js'
|
|
import { config } from './config.js'
|
|
import path from 'path'
|
|
import fs from 'fs'
|
|
import { fileURLToPath } from 'url'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = path.dirname(__filename)
|
|
|
|
const PORT = config.port
|
|
|
|
const app = express()
|
|
|
|
// Security headers
|
|
app.use(helmet())
|
|
|
|
// CORS
|
|
app.use(cors({ origin: config.corsOrigin }))
|
|
|
|
// Body parsing with reasonable limit
|
|
app.use(express.json({ limit: config.bodyLimit }))
|
|
|
|
// Global API rate limiting
|
|
const globalLimiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 200,
|
|
message: { error: 'Too many requests, please try again later' },
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
})
|
|
app.use('/api/', globalLimiter)
|
|
|
|
// Request logging
|
|
app.use((req, res, next) => {
|
|
const start = Date.now()
|
|
res.on('finish', () => {
|
|
const duration = Date.now() - start
|
|
console.log(`${req.method} ${req.path} ${res.statusCode} - ${duration}ms`)
|
|
})
|
|
next()
|
|
})
|
|
|
|
// Initialize database on startup
|
|
getDb()
|
|
console.log('✓ Database initialized')
|
|
|
|
// Routes
|
|
app.use('/api/setup', setupRoutes)
|
|
app.use('/api/auth', authRoutes)
|
|
app.use('/api/exams', examRoutes)
|
|
app.use('/api/results', resultRoutes)
|
|
app.use('/api/grade', gradeRoutes)
|
|
app.use('/api/ai', aiRoutes)
|
|
app.use('/api/admin', adminRoutes)
|
|
app.use('/api/share', shareRoutes)
|
|
app.use('/api/collections', collectionRoutes)
|
|
|
|
// Health check with subsystem status
|
|
app.get('/api/health', (_req, res) => {
|
|
let dbStatus: 'connected' | 'error' = 'connected'
|
|
try {
|
|
getDb().prepare('SELECT 1').get()
|
|
} catch {
|
|
dbStatus = 'error'
|
|
}
|
|
|
|
const checks = {
|
|
database: dbStatus,
|
|
email: config.email.smtpHost ? 'configured' : 'not_configured',
|
|
}
|
|
|
|
const overall = dbStatus === 'connected' ? 'healthy' : 'unhealthy'
|
|
|
|
res.status(overall === 'healthy' ? 200 : 503).json({
|
|
status: overall,
|
|
timestamp: Date.now(),
|
|
checks,
|
|
})
|
|
})
|
|
|
|
// Error handler (must be after routes)
|
|
app.use(errorHandler)
|
|
|
|
// Serve static frontend files if they exist
|
|
const frontendDistPath = path.join(__dirname, '../../frontend/dist')
|
|
if (fs.existsSync(frontendDistPath)) {
|
|
console.log(`Serving frontend from: ${frontendDistPath}`)
|
|
app.use(express.static(frontendDistPath))
|
|
app.get('*path', (req, res) => {
|
|
if (!req.path.startsWith('/api')) {
|
|
res.sendFile(path.join(frontendDistPath, 'index.html'))
|
|
} else {
|
|
res.status(404).json({ error: 'API route not found' })
|
|
}
|
|
})
|
|
} else {
|
|
console.log('Frontend build not found, skipping static serving.')
|
|
}
|
|
|
|
const server = app.listen(PORT, () => {
|
|
console.log(`🧠 BrainWave API running on http://localhost:${PORT}`)
|
|
})
|
|
|
|
// Graceful shutdown
|
|
function shutdown() {
|
|
console.log('\nShutting down...')
|
|
server.close()
|
|
closeDb()
|
|
process.exit(0)
|
|
}
|
|
|
|
process.on('SIGINT', shutdown)
|
|
process.on('SIGTERM', shutdown)
|