Files
TREK/server/src/routes/atlas.ts
T
jubnl 71c1683bb3 feat(atlas): mark sub-national regions as visited with cascade behavior
- Add visited_regions table migration
- Mark/unmark region endpoints with auto-mark parent country
- Unmark country cascades to its regions; unmark last region cascades to country
- Region modal with mark/unmark flow and bucket list shortcut
- Viewport-based lazy loading of region GeoJSON at zoom >= 6
- i18n: add atlas.markRegionVisitedHint and atlas.confirmUnmarkRegion across all 13 locales
2026-04-05 03:17:59 +02:00

106 lines
3.9 KiB
TypeScript

import express, { Request, Response } from 'express';
import { authenticate } from '../middleware/auth';
import { AuthRequest } from '../types';
import {
getStats,
getCountryPlaces,
markCountryVisited,
unmarkCountryVisited,
markRegionVisited,
unmarkRegionVisited,
getVisitedRegions,
getRegionGeo,
listBucketList,
createBucketItem,
updateBucketItem,
deleteBucketItem,
} from '../services/atlasService';
const router = express.Router();
router.use(authenticate);
router.get('/stats', async (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
const data = await getStats(userId);
res.json(data);
});
router.get('/regions', async (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
res.setHeader('Cache-Control', 'no-cache, no-store');
const data = await getVisitedRegions(userId);
res.json(data);
});
router.get('/regions/geo', async (req: Request, res: Response) => {
const countries = (req.query.countries as string || '').split(',').filter(Boolean);
if (countries.length === 0) return res.json({ type: 'FeatureCollection', features: [] });
const geo = await getRegionGeo(countries);
res.setHeader('Cache-Control', 'public, max-age=86400');
res.json(geo);
});
router.get('/country/:code', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
const code = req.params.code.toUpperCase();
res.json(getCountryPlaces(userId, code));
});
router.post('/country/:code/mark', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
markCountryVisited(userId, req.params.code.toUpperCase());
res.json({ success: true });
});
router.delete('/country/:code/mark', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
unmarkCountryVisited(userId, req.params.code.toUpperCase());
res.json({ success: true });
});
router.post('/region/:code/mark', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
const { name, country_code } = req.body;
if (!name || !country_code) return res.status(400).json({ error: 'name and country_code are required' });
markRegionVisited(userId, req.params.code.toUpperCase(), name, country_code.toUpperCase());
res.json({ success: true });
});
router.delete('/region/:code/mark', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
unmarkRegionVisited(userId, req.params.code.toUpperCase());
res.json({ success: true });
});
// ── Bucket List ─────────────────────────────────────────────────────────────
router.get('/bucket-list', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
res.json({ items: listBucketList(userId) });
});
router.post('/bucket-list', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
const { name, lat, lng, country_code, notes, target_date } = req.body;
if (!name?.trim()) return res.status(400).json({ error: 'Name is required' });
const item = createBucketItem(userId, { name, lat, lng, country_code, notes, target_date });
res.status(201).json({ item });
});
router.put('/bucket-list/:id', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
const { name, notes, lat, lng, country_code, target_date } = req.body;
const item = updateBucketItem(userId, req.params.id, { name, notes, lat, lng, country_code, target_date });
if (!item) return res.status(404).json({ error: 'Item not found' });
res.json({ item });
});
router.delete('/bucket-list/:id', (req: Request, res: Response) => {
const userId = (req as AuthRequest).user.id;
const deleted = deleteBucketItem(userId, req.params.id);
if (!deleted) return res.status(404).json({ error: 'Item not found' });
res.json({ success: true });
});
export default router;