diff --git a/display/middleware/src/api/index.ts b/display/middleware/src/api/index.ts new file mode 100644 index 0000000..635ca15 --- /dev/null +++ b/display/middleware/src/api/index.ts @@ -0,0 +1,45 @@ +import express from 'express'; +import cors from 'cors'; +import path from 'path'; +import authRouter from './routes/auth'; +import displayApiRouter from './routes/display-api'; +import mqttBrokersRouter from './routes/mqtt-brokers'; +import mqttTopicsRouter from './routes/mqtt-topics'; +import pgSourcesRouter from './routes/pg-sources'; +import datapointsRouter from './routes/datapoints'; +import displaysRouter from './routes/displays'; +import layoutsRouter from './routes/layouts'; +import { authMiddleware } from './middleware/auth'; + +export function createApp(): express.Application { + const app = express(); + + app.use(cors()); + app.use(express.json()); + + // Health check + app.get('/health', (_req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); + }); + + // Public routes + app.use('/api/auth', authRouter); + app.use('/api/display', displayApiRouter); + + // Protected management routes + app.use('/api/mqtt/brokers/:brokerId/topics', authMiddleware, mqttTopicsRouter); + app.use('/api/mqtt/brokers', authMiddleware, mqttBrokersRouter); + app.use('/api/pg/sources', authMiddleware, pgSourcesRouter); + app.use('/api/datapoints', authMiddleware, datapointsRouter); + app.use('/api/displays', authMiddleware, displaysRouter); + app.use('/api/layouts', authMiddleware, layoutsRouter); + + // Serve frontend/dist as static files with SPA fallback + const frontendDist = path.join(__dirname, '..', '..', '..', 'frontend', 'dist'); + app.use(express.static(frontendDist)); + app.get('*', (_req, res) => { + res.sendFile(path.join(frontendDist, 'index.html')); + }); + + return app; +} diff --git a/display/middleware/src/index.ts b/display/middleware/src/index.ts index 764e607..b4b0d49 100644 --- a/display/middleware/src/index.ts +++ b/display/middleware/src/index.ts @@ -1,23 +1,54 @@ -import express from 'express'; -import cors from 'cors'; -import authRouter from './api/routes/auth'; +import bcrypt from 'bcryptjs'; +import { initDb } from './db/index'; +import { createApp } from './api/index'; +import { connectAllEnabledBrokers } from './modules/mqtt/broker-manager'; +import { connectAllEnabledSources } from './modules/postgres/source-manager'; +import { startAllQueryTimers } from './modules/postgres/query-scheduler'; -const app = express(); const PORT = process.env.PORT ? parseInt(process.env.PORT) : 4000; +const DB_PATH = process.env.DB_PATH ?? './display.db'; -app.use(cors()); -app.use(express.json()); +async function main(): Promise { + // Initialize database + const db = initDb(DB_PATH); -app.get('/health', (_req, res) => { - res.json({ status: 'ok', timestamp: new Date().toISOString() }); -}); + // Create default admin user if no users exist + const userCount = db + .prepare('SELECT COUNT(*) as count FROM users') + .get() as { count: number }; -app.use('/api/auth', authRouter); + if (userCount.count === 0) { + const hash = bcrypt.hashSync('admin', 10); + db.run( + `INSERT INTO users (username, password_hash) VALUES (?, ?)`, + ['admin', hash] + ); + console.log('Created default admin user (admin/admin)'); + } + + // Connect all enabled MQTT brokers + await connectAllEnabledBrokers(); + + // Connect all enabled PostgreSQL sources and start query timers + await connectAllEnabledSources(); + startAllQueryTimers(); + + // Create and start Express app + const app = createApp(); -if (require.main === module) { app.listen(PORT, () => { console.log(`Middleware server running on port ${PORT}`); }); } -export default app; +// Only run when executed directly (not during tests) +if (require.main === module) { + main().catch((err) => { + console.error('Fatal startup error:', err); + process.exit(1); + }); +} + +// Export createApp for tests that import from index +export { createApp }; +export default createApp();