add features
This commit is contained in:
302
backend/src/controllers/incident.controller.ts
Normal file
302
backend/src/controllers/incident.controller.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
import { Request, Response } from 'express';
|
||||
import incidentService from '../services/incident.service';
|
||||
import logger from '../utils/logger';
|
||||
import { AppError } from '../middleware/error.middleware';
|
||||
import { AppRole, hasPermission } from '../middleware/rbac.middleware';
|
||||
import {
|
||||
CreateEinsatzSchema,
|
||||
UpdateEinsatzSchema,
|
||||
AssignPersonnelSchema,
|
||||
AssignVehicleSchema,
|
||||
IncidentFiltersSchema,
|
||||
} from '../models/incident.model';
|
||||
|
||||
// Extend Request type to carry the resolved role (set by requirePermission)
|
||||
interface AuthenticatedRequest extends Request {
|
||||
userRole?: AppRole;
|
||||
}
|
||||
|
||||
class IncidentController {
|
||||
// -------------------------------------------------------------------------
|
||||
// GET /api/incidents
|
||||
// -------------------------------------------------------------------------
|
||||
async listIncidents(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const parseResult = IncidentFiltersSchema.safeParse(req.query);
|
||||
if (!parseResult.success) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Ungültige Filter-Parameter',
|
||||
errors: parseResult.error.issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { items, total } = await incidentService.getAllIncidents(parseResult.data);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {
|
||||
items,
|
||||
total,
|
||||
limit: parseResult.data.limit,
|
||||
offset: parseResult.data.offset,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('List incidents error', { error });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Einsätze' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GET /api/incidents/stats
|
||||
// -------------------------------------------------------------------------
|
||||
async getStats(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const year = req.query.year ? parseInt(req.query.year as string, 10) : undefined;
|
||||
|
||||
if (year !== undefined && (isNaN(year) || year < 2000 || year > 2100)) {
|
||||
res.status(400).json({ success: false, message: 'Ungültiges Jahr' });
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = await incidentService.getIncidentStats(year);
|
||||
res.status(200).json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
logger.error('Get incident stats error', { error });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Statistik' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GET /api/incidents/:id
|
||||
// -------------------------------------------------------------------------
|
||||
async getIncident(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const incident = await incidentService.getIncidentById(id);
|
||||
|
||||
if (!incident) {
|
||||
throw new AppError('Einsatz nicht gefunden', 404);
|
||||
}
|
||||
|
||||
// Role-based redaction: only Kommandant+ can see full bericht_text
|
||||
const canReadBerichtText =
|
||||
req.userRole !== undefined &&
|
||||
hasPermission(req.userRole, 'incidents:read_bericht_text');
|
||||
|
||||
const responseData = {
|
||||
...incident,
|
||||
bericht_text: canReadBerichtText ? incident.bericht_text : undefined,
|
||||
};
|
||||
|
||||
res.status(200).json({ success: true, data: responseData });
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
res.status(error.statusCode).json({ success: false, message: error.message });
|
||||
return;
|
||||
}
|
||||
logger.error('Get incident error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden des Einsatzes' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// POST /api/incidents
|
||||
// -------------------------------------------------------------------------
|
||||
async createIncident(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, message: 'Nicht authentifiziert' });
|
||||
return;
|
||||
}
|
||||
|
||||
const parseResult = CreateEinsatzSchema.safeParse(req.body);
|
||||
if (!parseResult.success) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Validierungsfehler',
|
||||
errors: parseResult.error.issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const einsatz = await incidentService.createIncident(parseResult.data, req.user.id);
|
||||
|
||||
logger.info('Incident created via API', {
|
||||
einsatzId: einsatz.id,
|
||||
einsatz_nr: einsatz.einsatz_nr,
|
||||
createdBy: req.user.id,
|
||||
});
|
||||
|
||||
res.status(201).json({ success: true, data: einsatz });
|
||||
} catch (error) {
|
||||
logger.error('Create incident error', { error });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Erstellen des Einsatzes' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PATCH /api/incidents/:id
|
||||
// -------------------------------------------------------------------------
|
||||
async updateIncident(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, message: 'Nicht authentifiziert' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
const parseResult = UpdateEinsatzSchema.safeParse(req.body);
|
||||
if (!parseResult.success) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Validierungsfehler',
|
||||
errors: parseResult.error.issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const einsatz = await incidentService.updateIncident(id, parseResult.data, req.user.id);
|
||||
|
||||
res.status(200).json({ success: true, data: einsatz });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Incident not found') {
|
||||
res.status(404).json({ success: false, message: 'Einsatz nicht gefunden' });
|
||||
return;
|
||||
}
|
||||
logger.error('Update incident error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Aktualisieren des Einsatzes' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DELETE /api/incidents/:id (soft delete)
|
||||
// -------------------------------------------------------------------------
|
||||
async deleteIncident(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, message: 'Nicht authentifiziert' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { id } = req.params;
|
||||
await incidentService.deleteIncident(id, req.user.id);
|
||||
|
||||
res.status(200).json({ success: true, message: 'Einsatz archiviert' });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
res.status(404).json({ success: false, message: 'Einsatz nicht gefunden' });
|
||||
return;
|
||||
}
|
||||
logger.error('Delete incident error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Archivieren des Einsatzes' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// POST /api/incidents/:id/personnel
|
||||
// -------------------------------------------------------------------------
|
||||
async assignPersonnel(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const parseResult = AssignPersonnelSchema.safeParse(req.body);
|
||||
if (!parseResult.success) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Validierungsfehler',
|
||||
errors: parseResult.error.issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await incidentService.assignPersonnel(id, parseResult.data);
|
||||
|
||||
res.status(200).json({ success: true, message: 'Person zugewiesen' });
|
||||
} catch (error) {
|
||||
logger.error('Assign personnel error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Zuweisen der Person' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DELETE /api/incidents/:id/personnel/:userId
|
||||
// -------------------------------------------------------------------------
|
||||
async removePersonnel(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id, userId } = req.params;
|
||||
await incidentService.removePersonnel(id, userId);
|
||||
|
||||
res.status(200).json({ success: true, message: 'Person entfernt' });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
res.status(404).json({ success: false, message: 'Zuweisung nicht gefunden' });
|
||||
return;
|
||||
}
|
||||
logger.error('Remove personnel error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Entfernen der Person' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// POST /api/incidents/:id/vehicles
|
||||
// -------------------------------------------------------------------------
|
||||
async assignVehicle(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const parseResult = AssignVehicleSchema.safeParse(req.body);
|
||||
if (!parseResult.success) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: 'Validierungsfehler',
|
||||
errors: parseResult.error.issues,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await incidentService.assignVehicle(id, parseResult.data);
|
||||
|
||||
res.status(200).json({ success: true, message: 'Fahrzeug zugewiesen' });
|
||||
} catch (error) {
|
||||
logger.error('Assign vehicle error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Zuweisen des Fahrzeugs' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DELETE /api/incidents/:id/vehicles/:fahrzeugId
|
||||
// -------------------------------------------------------------------------
|
||||
async removeVehicle(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id, fahrzeugId } = req.params;
|
||||
await incidentService.removeVehicle(id, fahrzeugId);
|
||||
|
||||
res.status(200).json({ success: true, message: 'Fahrzeug entfernt' });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
res.status(404).json({ success: false, message: 'Zuweisung nicht gefunden' });
|
||||
return;
|
||||
}
|
||||
logger.error('Remove vehicle error', { error, id: req.params.id });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Entfernen des Fahrzeugs' });
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// POST /api/incidents/refresh-stats (admin utility)
|
||||
// -------------------------------------------------------------------------
|
||||
async refreshStats(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
await incidentService.refreshStatistikView();
|
||||
res.status(200).json({ success: true, message: 'Statistik-View aktualisiert' });
|
||||
} catch (error) {
|
||||
logger.error('Refresh stats view error', { error });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Aktualisieren der Statistik' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new IncidentController();
|
||||
Reference in New Issue
Block a user