286 lines
11 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.canvassAdminRouter = exports.canvassVolunteerRouter = void 0;
const express_1 = require("express");
const client_1 = require("@prisma/client");
const canvass_service_1 = require("./canvass.service");
const canvass_schemas_1 = require("./canvass.schemas");
const locations_schemas_1 = require("../locations/locations.schemas");
const locations_service_1 = require("../locations/locations.service");
const geocoding_service_1 = require("../geocoding/geocoding.service");
const validate_1 = require("../../../middleware/validate");
const auth_middleware_1 = require("../../../middleware/auth.middleware");
const rbac_middleware_1 = require("../../../middleware/rbac.middleware");
const rate_limit_1 = require("../../../middleware/rate-limit");
const roles_1 = require("../../../utils/roles");
// ─── Volunteer Router ────────────────────────────────────────────────
const volunteerRouter = (0, express_1.Router)();
exports.canvassVolunteerRouter = volunteerRouter;
volunteerRouter.use(auth_middleware_1.authenticate);
// GET /api/map/canvass/my/assignments
volunteerRouter.get('/my/assignments', async (req, res, next) => {
try {
const assignments = await canvass_service_1.canvassService.getMyAssignments(req.user.id);
res.json(assignments);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/my/stats
volunteerRouter.get('/my/stats', async (req, res, next) => {
try {
const stats = await canvass_service_1.canvassService.getMyStats(req.user.id);
res.json(stats);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/my/visits
volunteerRouter.get('/my/visits', (0, validate_1.validate)(canvass_schemas_1.listMyVisitsSchema, 'query'), async (req, res, next) => {
try {
const result = await canvass_service_1.canvassService.getMyVisits(req.user.id, req.query);
res.json(result);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/my/session
volunteerRouter.get('/my/session', async (req, res, next) => {
try {
const session = await canvass_service_1.canvassService.getActiveSession(req.user.id);
res.json(session);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/sessions
volunteerRouter.post('/sessions', (0, validate_1.validate)(canvass_schemas_1.startSessionSchema), async (req, res, next) => {
try {
const session = await canvass_service_1.canvassService.startSession(req.user.id, req.body);
res.status(201).json(session);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/sessions/:id/end
volunteerRouter.post('/sessions/:id/end', async (req, res, next) => {
try {
const id = req.params.id;
const session = await canvass_service_1.canvassService.endSession(id, req.user.id);
res.json(session);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/cuts/:cutId/locations
volunteerRouter.get('/cuts/:cutId/locations', async (req, res, next) => {
try {
const cutId = req.params.cutId;
const bounds = req.query.minLat ? {
minLat: parseFloat(req.query.minLat),
maxLat: parseFloat(req.query.maxLat),
minLng: parseFloat(req.query.minLng),
maxLng: parseFloat(req.query.maxLng),
} : undefined;
const limit = req.query.limit ? parseInt(req.query.limit) : undefined;
const locations = await canvass_service_1.canvassService.getCutLocationsForCanvass(cutId, req.user.id, bounds, limit);
res.json(locations);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/cuts/:cutId/route
volunteerRouter.get('/cuts/:cutId/route', (0, validate_1.validate)(canvass_schemas_1.walkingRouteSchema, 'query'), async (req, res, next) => {
try {
const cutId = req.params.cutId;
const route = await canvass_service_1.canvassService.getWalkingRoute(cutId, req.user.id, req.query);
res.json(route);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/locations — all locations with visit annotations
volunteerRouter.get('/locations', async (req, res, next) => {
try {
const bounds = req.query.minLat ? {
minLat: parseFloat(req.query.minLat),
maxLat: parseFloat(req.query.maxLat),
minLng: parseFloat(req.query.minLng),
maxLng: parseFloat(req.query.maxLng),
} : undefined;
const limit = req.query.limit ? parseInt(req.query.limit) : undefined;
const locations = await canvass_service_1.canvassService.getAllLocationsForCanvass(req.user.id, bounds, limit);
res.json(locations);
}
catch (err) {
next(err);
}
});
// PUT /api/map/canvass/locations/:id — role-gated address editing (deprecated path, should be /addresses/:id)
volunteerRouter.put('/locations/:id', (0, validate_1.validate)(canvass_schemas_1.volunteerUpdateLocationSchema), async (req, res, next) => {
try {
const id = req.params.id;
const address = await canvass_service_1.canvassService.updateAddressAsVolunteer(id, req.user.id, req.user.role, req.body);
res.json(address);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/locations — create a new location (role-gated fields)
volunteerRouter.post('/locations', (0, validate_1.validate)(canvass_schemas_1.volunteerCreateLocationSchema), async (req, res, next) => {
try {
const data = { ...req.body };
// Strip fields based on role
const userRoles = req.user.roles || [req.user.role];
const isAdmin = userRoles.some((r) => r === client_1.UserRole.SUPER_ADMIN || r === client_1.UserRole.MAP_ADMIN);
if (!isAdmin) {
delete data.firstName;
delete data.lastName;
delete data.email;
delete data.phone;
}
if (userRoles.length === 1 && userRoles[0] === client_1.UserRole.TEMP) {
delete data.supportLevel;
delete data.sign;
delete data.signSize;
delete data.notes;
}
const location = await locations_service_1.locationsService.create(data, req.user.id);
res.status(201).json(location);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/reverse-geocode — reverse geocode lat/lng
volunteerRouter.post('/reverse-geocode', (0, validate_1.validate)(locations_schemas_1.reverseGeocodeSchema), async (req, res, next) => {
try {
const result = await locations_service_1.locationsService.reverseGeocode(req.body.latitude, req.body.longitude);
res.json(result);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/geocode-search — geocode an address for map flyTo
volunteerRouter.post('/geocode-search', rate_limit_1.canvassGeocodeRateLimit, (0, validate_1.validate)(locations_schemas_1.geocodeAddressSchema), async (req, res, next) => {
try {
const result = await geocoding_service_1.geocodingService.geocode(req.body.address);
if (!result) {
res.status(404).json({ error: { message: 'Address not found', code: 'GEOCODE_FAILED' } });
return;
}
res.json(result);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/visits
volunteerRouter.post('/visits', rate_limit_1.canvassVisitRateLimit, (0, validate_1.validate)(canvass_schemas_1.recordVisitSchema), async (req, res, next) => {
try {
const visit = await canvass_service_1.canvassService.recordVisit(req.user.id, req.body);
res.status(201).json(visit);
}
catch (err) {
next(err);
}
});
// POST /api/map/canvass/visits/bulk - Record visit to all unvisited units in building
volunteerRouter.post('/visits/bulk', rate_limit_1.canvassBulkVisitRateLimit, // Stricter rate limit for bulk operations
(0, validate_1.validate)(canvass_schemas_1.bulkRecordVisitSchema), async (req, res, next) => {
try {
const result = await canvass_service_1.canvassService.recordBulkVisit(req.user.id, req.body);
res.status(201).json(result);
}
catch (err) {
next(err);
}
});
// ─── Admin Router ────────────────────────────────────────────────────
const adminRouter = (0, express_1.Router)();
exports.canvassAdminRouter = adminRouter;
adminRouter.use(auth_middleware_1.authenticate);
adminRouter.use((0, rbac_middleware_1.requireRole)(...roles_1.MAP_ROLES));
// GET /api/map/canvass/stats
adminRouter.get('/stats', async (_req, res, next) => {
try {
const stats = await canvass_service_1.canvassService.getAdminStats();
res.json(stats);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/stats/cuts/:cutId
adminRouter.get('/stats/cuts/:cutId', async (req, res, next) => {
try {
const cutId = req.params.cutId;
const stats = await canvass_service_1.canvassService.getCutStats(cutId);
res.json(stats);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/activity
adminRouter.get('/activity', (0, validate_1.validate)(canvass_schemas_1.adminActivitySchema, 'query'), async (req, res, next) => {
try {
const result = await canvass_service_1.canvassService.getAdminActivity(req.query);
res.json(result);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/volunteers
adminRouter.get('/volunteers', async (_req, res, next) => {
try {
const volunteers = await canvass_service_1.canvassService.getVolunteers();
res.json(volunteers);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/volunteers/:userId
adminRouter.get('/volunteers/:userId', async (req, res, next) => {
try {
const userId = req.params.userId;
const stats = await canvass_service_1.canvassService.getVolunteerStats(userId);
res.json(stats);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/visits
adminRouter.get('/visits', (0, validate_1.validate)(canvass_schemas_1.adminVisitsSchema, 'query'), async (req, res, next) => {
try {
const result = await canvass_service_1.canvassService.getAdminVisits(req.query);
res.json(result);
}
catch (err) {
next(err);
}
});
// GET /api/map/canvass/trends
adminRouter.get('/trends', (0, validate_1.validate)(canvass_schemas_1.outcomeTrendsQuerySchema, 'query'), async (req, res, next) => {
try {
const result = await canvass_service_1.canvassService.getOutcomeTrends(req.query);
res.json(result);
}
catch (err) {
next(err);
}
});
//# sourceMappingURL=canvass.routes.js.map