feat(app): wire up Express app factory and production entry point (Task 13)

Initializes DB, creates default admin user, connects brokers/PG sources,
starts query timers, and serves frontend/dist as SPA.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Mueller 2026-04-06 12:56:43 +02:00
parent 44f4b6d010
commit 9e96ecc231
2 changed files with 88 additions and 12 deletions

View File

@ -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;
}

View File

@ -1,23 +1,54 @@
import express from 'express'; import bcrypt from 'bcryptjs';
import cors from 'cors'; import { initDb } from './db/index';
import authRouter from './api/routes/auth'; 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 PORT = process.env.PORT ? parseInt(process.env.PORT) : 4000;
const DB_PATH = process.env.DB_PATH ?? './display.db';
app.use(cors()); async function main(): Promise<void> {
app.use(express.json()); // Initialize database
const db = initDb(DB_PATH);
app.get('/health', (_req, res) => { // Create default admin user if no users exist
res.json({ status: 'ok', timestamp: new Date().toISOString() }); 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, () => { app.listen(PORT, () => {
console.log(`Middleware server running on port ${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();