import { Router } from 'express'; import { upload } from '../middleware/upload.js'; import { config } from '../config.js'; import { listRoutes, getRoute, getRouteGpxFile, createRoute, updateRoute, appendRoute, listPois, getPoi, createPoi, updatePoi, softDeleteRoute, restoreRoute } from '../services/routes-service.js'; import { listPictures, getPicture, getPictureFile, createPicture, updatePicture, deletePicture, listAudio, getAudio, getAudioFile, createAudio, updateAudio, deleteAudio } from '../services/media-service.js'; const numberOrUndefined = value => { if (value == null || value === '') return undefined; const parsed = Number.parseFloat(value); return Number.isFinite(parsed) ? parsed : undefined; }; function sendFileResource(res, file, contentType = null) { if (contentType) res.type(contentType); res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.filename)}`); res.setHeader('X-Content-Type-Options', 'nosniff'); return res.sendFile(file.absolutePath, { acceptRanges: true, cacheControl: true, immutable: false, maxAge: '1h' }); } export function createApiRouter(db) { const api = Router(); api.get('/health', (req, res) => { const sqliteVersion = db.prepare('SELECT sqlite_version() AS version').get().version; res.json({ ok: true, service: 'wegwichtel', socket: `${config.host}:${config.port}`, sqliteVersion, timestamp: new Date().toISOString() }); }); api.get('/routes', (req, res) => { res.json({ routes: listRoutes(db, { lat: numberOrUndefined(req.query.lat), lon: numberOrUndefined(req.query.lon), radiusKm: numberOrUndefined(req.query.radiusKm), includeDeleted: req.query.includeDeleted === 'true' }) }); }); api.get('/routes/:id', (req, res) => { res.json(getRoute(db, Number(req.params.id), req.query.includeDeleted === 'true')); }); api.get('/routes/:id/gpx', (req, res) => { sendFileResource(res, getRouteGpxFile(db, Number(req.params.id)), 'application/gpx+xml'); }); api.get('/routes/:id/pois', (req, res) => { getRoute(db, Number(req.params.id)); res.json({ pois: listPois(db, Number(req.params.id)) }); }); api.get('/pois/:id', (req, res) => { res.json(getPoi(db, Number(req.params.id))); }); api.post('/routes', upload.single('gpx'), (req, res) => { const route = createRoute(db, req.body, req.file); res.status(201).location(`/api/routes/${route.id}`).json(route); }); api.put('/routes/:id', upload.single('gpx'), (req, res) => { res.json(updateRoute(db, Number(req.params.id), req.body, req.file)); }); api.post('/routes/:id/append', upload.single('gpx'), (req, res) => { res.json(appendRoute(db, Number(req.params.id), req.file)); }); api.post('/routes/:id/pois', upload.none(), (req, res) => { const poi = createPoi(db, Number(req.params.id), req.body); res.status(201).location(`/api/pois/${poi.id}`).json(poi); }); api.put('/pois/:id', upload.none(), (req, res) => { res.json(updatePoi(db, Number(req.params.id), req.body)); }); api.get('/routes/:id/pictures', (req, res) => { res.json({ pictures: listPictures(db, Number(req.params.id), { poiId: req.query.poiId }) }); }); api.get('/routes/:id/pictures/:pictureId', (req, res) => { const routeId = Number(req.params.id); const pictureId = Number(req.params.pictureId); if (req.query.metadata === 'true') { res.json(getPicture(db, routeId, pictureId)); return; } sendFileResource(res, getPictureFile(db, routeId, pictureId)); }); api.post('/routes/:id/pictures', upload.single('picture'), (req, res) => { const picture = createPicture(db, Number(req.params.id), req.body, req.file); res.status(201) .location(`/api/routes/${req.params.id}/pictures/${picture.id}`) .json(picture); }); api.put('/routes/:id/pictures/:pictureId', upload.single('picture'), (req, res) => { res.json(updatePicture(db, Number(req.params.id), Number(req.params.pictureId), req.body, req.file)); }); api.delete('/routes/:id/pictures/:pictureId', (req, res) => { res.json(deletePicture(db, Number(req.params.id), Number(req.params.pictureId))); }); api.get('/routes/:id/audio', (req, res) => { res.json({ audio: listAudio(db, Number(req.params.id), { poiId: req.query.poiId }) }); }); api.get('/routes/:id/audio/:poiId', (req, res) => { const routeId = Number(req.params.id); const poiId = Number(req.params.poiId); if (req.query.metadata === 'true') { res.json(getAudio(db, routeId, poiId)); return; } sendFileResource(res, getAudioFile(db, routeId, poiId)); }); api.post('/routes/:id/audio', upload.single('audio'), (req, res) => { const audio = createAudio(db, Number(req.params.id), req.body, req.file); res.status(201) .location(`/api/routes/${req.params.id}/audio/${audio.poiId}`) .json(audio); }); api.put('/routes/:id/audio/:poiId', upload.single('audio'), (req, res) => { res.json(updateAudio(db, Number(req.params.id), Number(req.params.poiId), req.file)); }); api.delete('/routes/:id/audio/:poiId', (req, res) => { res.json(deleteAudio(db, Number(req.params.id), Number(req.params.poiId))); }); api.delete('/routes/:id', (req, res) => { res.json({ route: softDeleteRoute(db, Number(req.params.id)), softDeleted: true }); }); api.post('/routes/:id/restore', (req, res) => { res.json({ route: restoreRoute(db, Number(req.params.id)), restored: true }); }); return api; }