# GPX Visualizer — Project Notes for Claude ## Repository layout ``` 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) ``` ## 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/me` - `src/routes/directories.js` — folder CRUD - `src/routes/tracks.js` — track upload, list, update, delete, share - `src/routes/stats.js` — per-user and per-directory stats - `src/routes/share.js` — public share link access - `src/routes/admin.js` — user management (admin only) **Models:** `User`, `Directory` (tree via parentId), `Track`, `TrackPoint`, `ShareLink` **Key behaviours:** - First registered user becomes admin + active; subsequent users need admin activation - GPX track points are stored in the DB (`TrackPoint` table), not as files - GPX compression: skip a point if distance < 2 m OR (time < 30 s AND bearing change < 30°) - Track date = first GPX timestamp if present, else upload date - Share links use 10-char consonant-vowel alternating codes (e.g. `bodaveximu`) **Tests:** `npm test` — mocha, uses in-memory SQLite via `test/setup.js` (overrides `Module._load` for config at module level) ## Frontend (`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 storage - `js/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 actions - `js/stats.js` — stats tab rendering - `js/app.js` — global utilities, admin panel, share-page init, main entry point **`browser.js` design:** - Tree view with expand/collapse triangles; expanded state survives reloads - Clicking a folder selects it (upload context) and loads all its tracks onto the map, then zooms to fit - Tracks are draggable onto folders; drop asks for confirmation before moving - `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) ## Development notes - Do not mock the database in tests — `test/setup.js` wires real in-memory SQLite - All track geometry is stored as `TrackPoint` rows; no file storage after upload - The `Module._load` override in `test/setup.js` is required because `config.js` is loaded at module level in several files - `API.getTracks('')` returns root-level tracks (directoryId = null)