bug fixes
This commit is contained in:
@@ -18,9 +18,11 @@ function getUserId(req: Request): string {
|
||||
// ── Controller ────────────────────────────────────────────────────────────────
|
||||
|
||||
class AtemschutzController {
|
||||
async list(_req: Request, res: Response): Promise<void> {
|
||||
async list(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const records = await atemschutzService.getAll();
|
||||
const userGroups: string[] = (req.user as any)?.groups ?? [];
|
||||
const userId = getUserId(req);
|
||||
const records = await atemschutzService.getAll(userGroups, userId);
|
||||
res.status(200).json({ success: true, data: records });
|
||||
} catch (error) {
|
||||
logger.error('Atemschutz list error', { error });
|
||||
@@ -47,9 +49,11 @@ class AtemschutzController {
|
||||
}
|
||||
}
|
||||
|
||||
async getStats(_req: Request, res: Response): Promise<void> {
|
||||
async getStats(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const stats = await atemschutzService.getStats();
|
||||
const userGroups: string[] = (req.user as any)?.groups ?? [];
|
||||
const userId = getUserId(req);
|
||||
const stats = await atemschutzService.getStats(userGroups, userId);
|
||||
res.status(200).json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
logger.error('Atemschutz getStats error', { error });
|
||||
|
||||
@@ -265,6 +265,24 @@ class EventsController {
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// POST /api/events/:id/delete (hard delete)
|
||||
// -------------------------------------------------------------------------
|
||||
deleteEvent = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params as Record<string, string>;
|
||||
const deleted = await eventsService.deleteEvent(id);
|
||||
if (!deleted) {
|
||||
res.status(404).json({ success: false, message: 'Veranstaltung nicht gefunden' });
|
||||
return;
|
||||
}
|
||||
res.json({ success: true, message: 'Veranstaltung wurde gelöscht' });
|
||||
} catch (error) {
|
||||
logger.error('deleteEvent error', { error });
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Löschen der Veranstaltung' });
|
||||
}
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GET /api/events/calendar-token
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -3,8 +3,8 @@ import atemschutzController from '../controllers/atemschutz.controller';
|
||||
import { authenticate } from '../middleware/auth.middleware';
|
||||
import { requireGroups } from '../middleware/rbac.middleware';
|
||||
|
||||
const ADMIN_GROUPS = ['dashboard_admin'];
|
||||
const WRITE_GROUPS = ['dashboard_admin', 'dashboard_atemschutz'];
|
||||
const ADMIN_GROUPS = ['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'];
|
||||
const WRITE_GROUPS = ['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'];
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
||||
@@ -143,4 +143,15 @@ router.delete(
|
||||
eventsController.cancelEvent.bind(eventsController)
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/events/:id/delete
|
||||
* Hard-delete an event permanently. Requires admin or moderator.
|
||||
*/
|
||||
router.post(
|
||||
'/:id/delete',
|
||||
authenticate,
|
||||
requireGroups(WRITE_GROUPS),
|
||||
eventsController.deleteEvent.bind(eventsController)
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -8,19 +8,31 @@ import {
|
||||
UpdateAtemschutzData,
|
||||
} from '../models/atemschutz.model';
|
||||
|
||||
const ATEMSCHUTZ_PRIVILEGED = ['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'];
|
||||
|
||||
class AtemschutzService {
|
||||
// =========================================================================
|
||||
// ÜBERSICHT (ALL RECORDS)
|
||||
// =========================================================================
|
||||
|
||||
async getAll(): Promise<AtemschutzUebersicht[]> {
|
||||
async getAll(userGroups: string[], userId: string): Promise<AtemschutzUebersicht[]> {
|
||||
const isPrivileged = userGroups.some(g => ATEMSCHUTZ_PRIVILEGED.includes(g));
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT *
|
||||
FROM atemschutz_uebersicht
|
||||
WHERE mitglied_status IS NULL OR mitglied_status IN ('aktiv', 'anwärter')
|
||||
ORDER BY user_family_name, user_given_name
|
||||
`);
|
||||
let result;
|
||||
if (isPrivileged) {
|
||||
result = await pool.query(`
|
||||
SELECT *
|
||||
FROM atemschutz_uebersicht
|
||||
WHERE mitglied_status IS NULL OR mitglied_status IN ('aktiv', 'anwärter')
|
||||
ORDER BY user_family_name, user_given_name
|
||||
`);
|
||||
} else {
|
||||
result = await pool.query(`
|
||||
SELECT *
|
||||
FROM atemschutz_uebersicht
|
||||
WHERE user_id = $1
|
||||
`, [userId]);
|
||||
}
|
||||
|
||||
return result.rows.map((row) => ({
|
||||
...row,
|
||||
@@ -208,7 +220,21 @@ class AtemschutzService {
|
||||
// DASHBOARD KPI / STATISTIKEN
|
||||
// =========================================================================
|
||||
|
||||
async getStats(): Promise<AtemschutzStats> {
|
||||
async getStats(userGroups: string[], userId: string): Promise<AtemschutzStats> {
|
||||
const isPrivileged = userGroups.some(g => ATEMSCHUTZ_PRIVILEGED.includes(g));
|
||||
if (!isPrivileged) {
|
||||
return {
|
||||
total: 0,
|
||||
mitLehrgang: 0,
|
||||
untersuchungGueltig: 0,
|
||||
untersuchungAbgelaufen: 0,
|
||||
untersuchungBaldFaellig: 0,
|
||||
leistungstestGueltig: 0,
|
||||
leistungstestAbgelaufen: 0,
|
||||
leistungstestBaldFaellig: 0,
|
||||
einsatzbereit: 0,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
|
||||
@@ -490,6 +490,24 @@ class EventsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-deletes an event (and any recurrence children) from the database.
|
||||
* Returns true if the event was found and deleted, false if not found.
|
||||
*/
|
||||
async deleteEvent(id: string): Promise<boolean> {
|
||||
logger.info('Hard-deleting event', { id });
|
||||
// Delete recurrence children first (wiederholung_parent_id references)
|
||||
await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE wiederholung_parent_id = $1`,
|
||||
[id]
|
||||
);
|
||||
const result = await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ICAL TOKEN
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user