import { Router, Request, Response, NextFunction } from 'express'; import { UserRole } from '@prisma/client'; import { randomUUID } from 'crypto'; import { authenticate } from '../../../middleware/auth.middleware'; import { requireRole } from '../../../middleware/rbac.middleware'; import { validate } from '../../../middleware/validate'; import { areaImportPreviewSchema, areaImportStartSchema } from './area-import.schemas'; import { areaImportService, type AreaImportProgress } from './area-import.service'; import { redis } from '../../../config/redis'; import { logger } from '../../../utils/logger'; const MAP_ADMIN_ROLES: UserRole[] = [UserRole.SUPER_ADMIN, UserRole.MAP_ADMIN]; const areaImportRouter = Router(); areaImportRouter.use(authenticate); areaImportRouter.use(requireRole(...MAP_ADMIN_ROLES)); // POST /api/map/area-import/preview — get bounds, estimates, and existing count areaImportRouter.post( '/preview', validate(areaImportPreviewSchema), async (req: Request, res: Response, next: NextFunction) => { try { const result = await areaImportService.previewAreaImport(req.body); res.json(result); } catch (err) { next(err); } }, ); // POST /api/map/area-import — start import (fire-and-forget, returns importId) areaImportRouter.post( '/', validate(areaImportStartSchema), async (req: Request, res: Response, next: NextFunction) => { try { const importId = randomUUID(); const userId = req.user!.id; // Write initial progress so status endpoint works immediately const initialProgress: AreaImportProgress = { status: 'initializing', bounds: null, areaSqKm: 0, sources: { osm: { status: 'pending', candidatesFound: 0 }, nar: { status: 'pending', candidatesFound: 0 }, reverseGeocode: { status: 'pending', candidatesFound: 0 }, }, locationsCreated: 0, addressesCreated: 0, skippedDuplicate: 0, totalCandidates: 0, }; await redis.set(`area-import:${importId}`, JSON.stringify(initialProgress), 'EX', 3600); // Fire and forget areaImportService.runAreaImport(userId, req.body, importId).catch((err) => { const errorMsg = err instanceof Error ? err.message : 'Unknown error'; logger.error(`Area import ${importId} failed: ${errorMsg}`); }); res.json({ importId }); } catch (err) { next(err); } }, ); // GET /api/map/area-import/status/:importId — poll import progress areaImportRouter.get( '/status/:importId', async (req: Request, res: Response, next: NextFunction) => { try { const importId = req.params.importId as string; const progress = await areaImportService.getProgress(importId); if (!progress) { res.status(404).json({ error: { message: 'Import not found or expired', code: 'NOT_FOUND' } }); return; } res.json(progress); } catch (err) { next(err); } }, ); export { areaImportRouter };