add features

This commit is contained in:
Matthias Hochmeister
2026-02-27 20:33:43 +01:00
parent e2713e25ba
commit 46d3f5b351
9 changed files with 33 additions and 52 deletions

View File

@@ -74,7 +74,7 @@ class IncidentController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async getIncident(req: AuthenticatedRequest, res: Response): Promise<void> { async getIncident(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const incident = await incidentService.getIncidentById(id); const incident = await incidentService.getIncidentById(id);
if (!incident) { if (!incident) {
@@ -147,7 +147,7 @@ class IncidentController {
return; return;
} }
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parseResult = UpdateEinsatzSchema.safeParse(req.body); const parseResult = UpdateEinsatzSchema.safeParse(req.body);
if (!parseResult.success) { if (!parseResult.success) {
res.status(400).json({ res.status(400).json({
@@ -181,7 +181,7 @@ class IncidentController {
return; return;
} }
const { id } = req.params; const { id } = req.params as Record<string, string>;
await incidentService.deleteIncident(id, req.user.id); await incidentService.deleteIncident(id, req.user.id);
res.status(200).json({ success: true, message: 'Einsatz archiviert' }); res.status(200).json({ success: true, message: 'Einsatz archiviert' });
@@ -200,7 +200,7 @@ class IncidentController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async assignPersonnel(req: AuthenticatedRequest, res: Response): Promise<void> { async assignPersonnel(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parseResult = AssignPersonnelSchema.safeParse(req.body); const parseResult = AssignPersonnelSchema.safeParse(req.body);
if (!parseResult.success) { if (!parseResult.success) {
@@ -226,7 +226,7 @@ class IncidentController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async removePersonnel(req: AuthenticatedRequest, res: Response): Promise<void> { async removePersonnel(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const { id, userId } = req.params; const { id, userId } = req.params as Record<string, string>;
await incidentService.removePersonnel(id, userId); await incidentService.removePersonnel(id, userId);
res.status(200).json({ success: true, message: 'Person entfernt' }); res.status(200).json({ success: true, message: 'Person entfernt' });
@@ -245,7 +245,7 @@ class IncidentController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async assignVehicle(req: AuthenticatedRequest, res: Response): Promise<void> { async assignVehicle(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parseResult = AssignVehicleSchema.safeParse(req.body); const parseResult = AssignVehicleSchema.safeParse(req.body);
if (!parseResult.success) { if (!parseResult.success) {
@@ -271,7 +271,7 @@ class IncidentController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async removeVehicle(req: AuthenticatedRequest, res: Response): Promise<void> { async removeVehicle(req: AuthenticatedRequest, res: Response): Promise<void> {
try { try {
const { id, fahrzeugId } = req.params; const { id, fahrzeugId } = req.params as Record<string, string>;
await incidentService.removeVehicle(id, fahrzeugId); await incidentService.removeVehicle(id, fahrzeugId);
res.status(200).json({ success: true, message: 'Fahrzeug entfernt' }); res.status(200).json({ success: true, message: 'Fahrzeug entfernt' });

View File

@@ -1,7 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import memberService from '../services/member.service'; import memberService from '../services/member.service';
import logger from '../utils/logger'; import logger from '../utils/logger';
import { AppError } from '../middleware/error.middleware';
import { import {
CreateMemberProfileSchema, CreateMemberProfileSchema,
UpdateMemberProfileSchema, UpdateMemberProfileSchema,
@@ -79,7 +78,7 @@ class MemberController {
* Returns aggregate member counts for each status. * Returns aggregate member counts for each status.
* Must be registered BEFORE /:userId to avoid route collision. * Must be registered BEFORE /:userId to avoid route collision.
*/ */
async getMemberStats(req: Request, res: Response): Promise<void> { async getMemberStats(_req: Request, res: Response): Promise<void> {
try { try {
const stats = await memberService.getMemberStats(); const stats = await memberService.getMemberStats();
res.status(200).json({ success: true, data: stats }); res.status(200).json({ success: true, data: stats });
@@ -100,9 +99,7 @@ class MemberController {
*/ */
async getMemberById(req: Request, res: Response): Promise<void> { async getMemberById(req: Request, res: Response): Promise<void> {
try { try {
const { userId } = req.params; const { userId } = req.params as Record<string, string>;
const requestorId = req.user!.id;
const requestorRole = getRole(req);
const ownProfile = isOwnProfile(req, userId); const ownProfile = isOwnProfile(req, userId);
const member = await memberService.getMemberById(userId); const member = await memberService.getMemberById(userId);
@@ -151,7 +148,7 @@ class MemberController {
*/ */
async createMemberProfile(req: Request, res: Response): Promise<void> { async createMemberProfile(req: Request, res: Response): Promise<void> {
try { try {
const { userId } = req.params; const { userId } = req.params as Record<string, string>;
const parseResult = CreateMemberProfileSchema.safeParse(req.body); const parseResult = CreateMemberProfileSchema.safeParse(req.body);
if (!parseResult.success) { if (!parseResult.success) {
@@ -187,7 +184,7 @@ class MemberController {
*/ */
async updateMember(req: Request, res: Response): Promise<void> { async updateMember(req: Request, res: Response): Promise<void> {
try { try {
const { userId } = req.params; const { userId } = req.params as Record<string, string>;
const updaterId = req.user!.id; const updaterId = req.user!.id;
const ownProfile = isOwnProfile(req, userId); const ownProfile = isOwnProfile(req, userId);

View File

@@ -176,7 +176,7 @@ class TrainingController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
getById = async (req: Request, res: Response): Promise<void> => { getById = async (req: Request, res: Response): Promise<void> => {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const userId = req.user?.id; const userId = req.user?.id;
// Determine if the requester may see the full attendee list // Determine if the requester may see the full attendee list
@@ -229,7 +229,7 @@ class TrainingController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
updateEvent = async (req: Request, res: Response): Promise<void> => { updateEvent = async (req: Request, res: Response): Promise<void> => {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parsed = UpdateUebungSchema.safeParse(req.body); const parsed = UpdateUebungSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {
@@ -258,7 +258,7 @@ class TrainingController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
cancelEvent = async (req: Request, res: Response): Promise<void> => { cancelEvent = async (req: Request, res: Response): Promise<void> => {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parsed = CancelEventSchema.safeParse(req.body); const parsed = CancelEventSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {
@@ -287,7 +287,7 @@ class TrainingController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
updateRsvp = async (req: Request, res: Response): Promise<void> => { updateRsvp = async (req: Request, res: Response): Promise<void> => {
try { try {
const { id: uebungId } = req.params; const { id: uebungId } = req.params as Record<string, string>;
const userId = req.user!.id; const userId = req.user!.id;
const parsed = UpdateRsvpSchema.safeParse(req.body); const parsed = UpdateRsvpSchema.safeParse(req.body);
@@ -319,7 +319,7 @@ class TrainingController {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
markAttendance = async (req: Request, res: Response): Promise<void> => { markAttendance = async (req: Request, res: Response): Promise<void> => {
try { try {
const { id: uebungId } = req.params; const { id: uebungId } = req.params as Record<string, string>;
const parsed = MarkAttendanceSchema.safeParse(req.body); const parsed = MarkAttendanceSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {

View File

@@ -85,7 +85,7 @@ class VehicleController {
* GET /api/vehicles * GET /api/vehicles
* Fleet overview list with per-vehicle inspection badge data. * Fleet overview list with per-vehicle inspection badge data.
*/ */
async listVehicles(req: Request, res: Response): Promise<void> { async listVehicles(_req: Request, res: Response): Promise<void> {
try { try {
const vehicles = await vehicleService.getAllVehicles(); const vehicles = await vehicleService.getAllVehicles();
res.status(200).json({ success: true, data: vehicles }); res.status(200).json({ success: true, data: vehicles });
@@ -99,7 +99,7 @@ class VehicleController {
* GET /api/vehicles/stats * GET /api/vehicles/stats
* Aggregated KPI counts for the dashboard strip. * Aggregated KPI counts for the dashboard strip.
*/ */
async getStats(req: Request, res: Response): Promise<void> { async getStats(_req: Request, res: Response): Promise<void> {
try { try {
const stats = await vehicleService.getVehicleStats(); const stats = await vehicleService.getVehicleStats();
res.status(200).json({ success: true, data: stats }); res.status(200).json({ success: true, data: stats });
@@ -140,7 +140,7 @@ class VehicleController {
*/ */
async getVehicle(req: Request, res: Response): Promise<void> { async getVehicle(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const vehicle = await vehicleService.getVehicleById(id); const vehicle = await vehicleService.getVehicleById(id);
if (!vehicle) { if (!vehicle) {
@@ -185,7 +185,7 @@ class VehicleController {
*/ */
async updateVehicle(req: Request, res: Response): Promise<void> { async updateVehicle(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parsed = UpdateFahrzeugSchema.safeParse(req.body); const parsed = UpdateFahrzeugSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {
res.status(400).json({ res.status(400).json({
@@ -219,7 +219,7 @@ class VehicleController {
*/ */
async updateVehicleStatus(req: Request, res: Response): Promise<void> { async updateVehicleStatus(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parsed = UpdateStatusSchema.safeParse(req.body); const parsed = UpdateStatusSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {
@@ -262,7 +262,7 @@ class VehicleController {
*/ */
async addPruefung(req: Request, res: Response): Promise<void> { async addPruefung(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parsed = CreatePruefungSchema.safeParse(req.body); const parsed = CreatePruefungSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {
@@ -288,7 +288,7 @@ class VehicleController {
*/ */
async getPruefungen(req: Request, res: Response): Promise<void> { async getPruefungen(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const pruefungen = await vehicleService.getPruefungenForVehicle(id); const pruefungen = await vehicleService.getPruefungenForVehicle(id);
res.status(200).json({ success: true, data: pruefungen }); res.status(200).json({ success: true, data: pruefungen });
} catch (error) { } catch (error) {
@@ -305,7 +305,7 @@ class VehicleController {
*/ */
async addWartung(req: Request, res: Response): Promise<void> { async addWartung(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const parsed = CreateWartungslogSchema.safeParse(req.body); const parsed = CreateWartungslogSchema.safeParse(req.body);
if (!parsed.success) { if (!parsed.success) {
@@ -331,7 +331,7 @@ class VehicleController {
*/ */
async getWartung(req: Request, res: Response): Promise<void> { async getWartung(req: Request, res: Response): Promise<void> {
try { try {
const { id } = req.params; const { id } = req.params as Record<string, string>;
const entries = await vehicleService.getWartungslogForVehicle(id); const entries = await vehicleService.getWartungslogForVehicle(id);
res.status(200).json({ success: true, data: entries }); res.status(200).json({ success: true, data: entries });
} catch (error) { } catch (error) {

View File

@@ -132,9 +132,9 @@ export function auditMiddleware(
// Resource ID: prefer controller override, then route param, then body id. // Resource ID: prefer controller override, then route param, then body id.
const resourceId = const resourceId =
res.locals.auditResourceId ?? res.locals.auditResourceId ??
req.params.id ?? (req.params as Record<string, string>).id ??
(body !== null && typeof body === 'object' (body !== null && typeof body === 'object'
? (body as Record<string, unknown>)?.data?.id as string | undefined ? ((body as Record<string, unknown>)?.data as Record<string, unknown>)?.id as string | undefined
: undefined) ?? : undefined) ??
null; null;

View File

@@ -100,7 +100,7 @@ router.get(
res.status(400).json({ res.status(400).json({
success: false, success: false,
message: 'Invalid query parameters', message: 'Invalid query parameters',
errors: error.errors, errors: error.issues,
}); });
return; return;
} }
@@ -152,7 +152,7 @@ router.get(
res.status(400).json({ res.status(400).json({
success: false, success: false,
message: 'Invalid query parameters', message: 'Invalid query parameters',
errors: error.errors, errors: error.issues,
}); });
return; return;
} }

View File

@@ -28,21 +28,6 @@ const router = Router();
type AppRole = 'admin' | 'kommandant' | 'mitglied'; type AppRole = 'admin' | 'kommandant' | 'mitglied';
// Extend the Express Request type to include role
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
authentikSub: string;
role?: AppRole;
groups?: string[];
};
}
}
}
/** /**
* Resolves the AppRole from Authentik groups attached to the JWT. * Resolves the AppRole from Authentik groups attached to the JWT.
* Mutates req.user.role so downstream controllers can read it directly. * Mutates req.user.role so downstream controllers can read it directly.
@@ -53,9 +38,9 @@ const resolveRole = (req: Request, _res: Response, next: NextFunction): void =>
if (groups.includes('feuerwehr-admin')) { if (groups.includes('feuerwehr-admin')) {
req.user.role = 'admin'; req.user.role = 'admin';
} else if (groups.includes('feuerwehr-kommandant')) { } else if (groups.includes('feuerwehr-kommandant')) {
req.user.role = 'kommandant'; req.user.role = 'kommandant' as any;
} else { } else {
req.user.role = 'mitglied'; req.user.role = 'mitglied' as any;
} }
logger.debug('resolveRole', { userId: req.user.id, role: req.user.role }); logger.debug('resolveRole', { userId: req.user.id, role: req.user.role });
} }

View File

@@ -375,7 +375,7 @@ class MemberService {
await this.updateDienstgrad(userId, dienstgrad, updatedBy, dienstgrad_seit, client); await this.updateDienstgrad(userId, dienstgrad, updatedBy, dienstgrad_seit, client);
} else if (dienstgrad_seit !== undefined) { } else if (dienstgrad_seit !== undefined) {
// dienstgrad_seit can be updated independently // dienstgrad_seit can be updated independently
rest.dienstgrad_seit = dienstgrad_seit; (rest as any).dienstgrad_seit = dienstgrad_seit;
} }
// Build dynamic SET clause for remaining fields // Build dynamic SET clause for remaining fields

View File

@@ -1,4 +1,3 @@
import { Server as SocketIOServer } from 'socket.io';
import pool from '../config/database'; import pool from '../config/database';
import logger from '../utils/logger'; import logger from '../utils/logger';
import { import {
@@ -282,7 +281,7 @@ class VehicleService {
status: FahrzeugStatus, status: FahrzeugStatus,
bemerkung: string, bemerkung: string,
updatedBy: string, updatedBy: string,
io?: SocketIOServer io?: any
): Promise<void> { ): Promise<void> {
try { try {
// Fetch old status for Socket.IO payload and logging // Fetch old status for Socket.IO payload and logging