gpx-vis/
├── gpx-vis-backend/ Node.js/Express REST API (default port 3000)
├── gpx-vis-frontend/ Vanilla HTML/CSS/JS SPA (served by any web server)
└── sample/ Standalone drag-and-drop GPX viewer (no backend)
gpx-vis-backend/)Stack: Express 5, Sequelize ORM, SQLite (default) or MySQL/MariaDB, bcryptjs, JWT, multer, xml2js
Entry point: index.js
Config: Copy config.example.js → config.js
Routes:
src/routes/auth.js — login, register, /api/auth/mesrc/routes/directories.js — folder CRUDsrc/routes/tracks.js — track upload, list, update, delete, sharesrc/routes/stats.js — per-user and per-directory statssrc/routes/share.js — public share link accesssrc/routes/admin.js — user management (admin only)Models: User, Directory (tree via parentId), Track, TrackPoint, ShareLink
Key behaviours:
TrackPoint table), not as filesbodaveximu)Tests: npm test — mocha, uses in-memory SQLite via test/setup.js (overrides Module._load for config at module level)
gpx-vis-frontend/)Stack: Vanilla JS, Leaflet.js (CDN), no build step
Config: Copy config.example.js → config.js
JS modules (loaded in order):
js/api.js — all fetch wrappers (API.*)js/auth.js — login/register forms, JWT storagejs/map.js — Leaflet map, track layers, URL hash state (#map=lat,lng,zoom&tracks=id1,id2&open=id)js/browser.js — file/folder tree browser, drag-and-drop, track actionsjs/stats.js — stats tab renderingjs/app.js — global utilities, admin panel, share-page init, main entry pointbrowser.js design:
selectedDirId is the current upload context (new folder / file upload go here)dirContents cache: key 'root' or numeric dir id → {dirs, tracks}dirMeta cache: dir id → {id, name, parentId} (used for breadcrumb path)test/setup.js wires real in-memory SQLiteTrackPoint rows; no file storage after uploadModule._load override in test/setup.js is required because config.js is loaded at module level in several filesAPI.getTracks('') returns root-level tracks (directoryId = null)