286 lines
11 KiB
JavaScript
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
|