Files
seaguesser/server.js
T
ronan c3cc578d1c
Build and Push Docker Image / build (push) Failing after 5m2s
Initial commit: saint-malo navigation quiz
2026-06-11 10:04:44 +02:00

128 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const express = require('express');
const https = require('https');
const path = require('path');
const fs = require('fs');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
const POINTS_FILE = path.join(__dirname, 'data', 'points.json');
const TILES_DIR = path.join(__dirname, 'tiles');
const SHOM_BASE = 'https://services.data.shom.fr/clevisu/wmts?layer=RASTER_MARINE_3857_WMTS&style=normal&tilematrixset=3857&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fpng';
const SHOM_HEADERS = {
'Referer': 'https://data.shom.fr',
'Origin': 'https://data.shom.fr',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/114.0',
'Accept': 'image/png'
};
let db = null;
let mbtilesMeta = {};
let tileStmt = null;
async function initMbtiles() {
try {
const files = fs.readdirSync(TILES_DIR).filter(f => f.endsWith('.mbtiles'));
if (files.length === 0) return;
const initSqlJs = require('sql.js');
const SQL = await initSqlJs();
const buf = fs.readFileSync(path.join(TILES_DIR, files[0]));
db = new SQL.Database(buf);
db.each('SELECT name, value FROM metadata', [], (row) => {
mbtilesMeta[row.name] = row.value;
});
tileStmt = db.prepare(
'SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?'
);
console.log(`✓ MBTiles : ${files[0]} (zoom ${mbtilesMeta.minzoom}${mbtilesMeta.maxzoom})`);
} catch (e) {
console.log('Pas de MBTiles local, utilisation du flux SHOM en ligne.');
}
}
function proxyShom(z, x, y, res) {
const url = `${SHOM_BASE}&TileMatrix=${z}&TileCol=${x}&TileRow=${y}`;
https.get(url, { headers: SHOM_HEADERS }, (upstream) => {
if (upstream.statusCode !== 200) return res.status(upstream.statusCode).end();
res.set('Content-Type', 'image/png');
res.set('Cache-Control', 'public, max-age=86400');
upstream.pipe(res);
}).on('error', () => res.status(502).end());
}
initMbtiles().then(startServer);
app.get('/api/status', (req, res) => {
res.json({
hasMbtiles: !!db,
name: mbtilesMeta.name || null,
minZoom: parseInt(mbtilesMeta.minzoom) || 6,
maxZoom: parseInt(mbtilesMeta.maxzoom) || 17
});
});
app.get('/tiles/:z/:x/:y', (req, res) => {
const z = parseInt(req.params.z);
const x = parseInt(req.params.x);
const y = parseInt(req.params.y);
if (tileStmt) {
const tmsY = (1 << z) - 1 - y;
try {
tileStmt.bind([z, x, tmsY]);
if (!tileStmt.step()) { tileStmt.reset(); return proxyShom(z, x, y, res); }
const row = tileStmt.getAsObject();
tileStmt.reset();
const fmt = mbtilesMeta.format || 'png';
res.set('Content-Type', fmt === 'jpg' || fmt === 'jpeg' ? 'image/jpeg' : 'image/png');
res.set('Cache-Control', 'public, max-age=86400');
res.send(Buffer.from(row.tile_data));
} catch (e) {
tileStmt.reset();
proxyShom(z, x, y, res);
}
} else {
proxyShom(z, x, y, res);
}
});
function loadPoints() {
if (!fs.existsSync(POINTS_FILE)) return [];
try { return JSON.parse(fs.readFileSync(POINTS_FILE, 'utf8')); }
catch { return []; }
}
function savePoints(pts) {
fs.writeFileSync(POINTS_FILE, JSON.stringify(pts, null, 2));
}
app.get('/api/points', (req, res) => res.json(loadPoints()));
app.post('/api/points', (req, res) => {
const pts = loadPoints();
const pt = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, ...req.body };
pts.push(pt);
savePoints(pts);
res.json(pt);
});
app.put('/api/points/:id', (req, res) => {
const pts = loadPoints();
const i = pts.findIndex(p => p.id === req.params.id);
if (i === -1) return res.status(404).json({ error: 'Not found' });
pts[i] = { ...pts[i], ...req.body };
savePoints(pts);
res.json(pts[i]);
});
app.delete('/api/points/:id', (req, res) => {
savePoints(loadPoints().filter(p => p.id !== req.params.id));
res.json({ ok: true });
});
function startServer() {
app.listen(4242, () => console.log('\n⚓ Saint-Malo Quiz → http://localhost:4242\n'));
}