Initial commit: saint-malo navigation quiz
Build and Push Docker Image / build (push) Failing after 5m2s
Build and Push Docker Image / build (push) Failing after 5m2s
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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'));
|
||||
}
|
||||
Reference in New Issue
Block a user