require('dotenv').config(); const express = require('express'); const cors = require('cors'); const auth = require('./middleware/auth'); const { pool } = require('./db'); const migrate = require('./db-migrate'); const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); // Health check — always 200 so Coolify doesn't kill the container app.get('/health', async (req, res) => { let db = 'connected'; try { await pool.query('SELECT 1'); } catch (err) { db = err.message; } res.json({ status: 'ok', db }); }); // Public routes app.use('/auth', require('./routes/auth')); app.use('/auth/feed', require('./routes/feed').router); // Routes — protected by Bearer token app.use('/api', auth, require('./routes/index')); app.use('/api/pictures', auth, require('./routes/pictures')); app.use('/api/words', auth, require('./routes/words')); app.use('/api/categories', auth, require('./routes/categories')); app.use('/api/objects', auth, require('./routes/objects')); app.use('/api/pairs', auth, require('./routes/pairs')); app.use('/api/questions', auth, require('./routes/questions')); app.use('/api/statements', auth, require('./routes/statements')); app.use('/api/blocklist', auth, require('./routes/blocklist')); app.use('/api/languages', auth, require('./routes/languages')); app.use('/api/user-names', auth, require('./routes/user-names')); app.use('/api/users-public', auth, require('./routes/users-public')); app.use('/api/users', auth, require('./routes/users')); app.use('/api/audios', auth, require('./routes/audios')); app.use('/api/tts-settings', auth, require('./routes/tts-settings')); app.use('/api/claude', auth, require('./routes/claude')); app.use('/api/pipeline', auth, require('./routes/pipeline')); app.use('/api/word-generative', auth, require('./routes/wordGenerative')); app.use('/api/prompt-styles', auth, require('./routes/prompt-styles')); app.use('/api/picture-jobs', auth, require('./routes/picture-jobs')); // 404 app.use((req, res) => { res.status(404).json({ error: 'Not found' }); }); // Error handler app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Internal server error' }); }); migrate() .then(() => { app.listen(PORT, '0.0.0.0', () => console.log(`snakkimo-API running on port ${PORT}`)); // Hängengebliebene Pipeline-Läufe (z.B. nach Redeploy) wieder aufnehmen require('./lib/pipeline').resumePending() .catch(err => console.error('Pipeline-Resume fehlgeschlagen:', err)); // Automatische Wort-Kategorisierung (Message Batches API): kurz nach Boot + stündlich. // Submit/Collect-Ticks, entkoppelt von generate-words und Publish. const { runCategorizationTick } = require('./lib/classifyWords'); const { runEnrichTick } = require('./lib/enrichWords'); const HOUR = 60 * 60 * 1000; const tick = () => runCategorizationTick().catch(err => console.error('Auto-Kategorisierung:', err.message)); const enrichTick = () => runEnrichTick().catch(err => console.error('Auto-Anreicherung:', err.message)); setTimeout(tick, 30_000); setTimeout(enrichTick, 60_000); setInterval(tick, HOUR); setInterval(enrichTick, HOUR); }) .catch(err => { console.error('Migration failed:', err); process.exit(1); });