This commit is contained in:
Matthias Hochmeister
2026-03-13 15:42:15 +01:00
parent 3dda069611
commit 75c919c063
13 changed files with 926 additions and 30 deletions

View File

@@ -120,6 +120,29 @@ class AtemschutzController {
}
}
async getByUserId(req: Request, res: Response): Promise<void> {
try {
const { userId } = req.params as Record<string, string>;
if (!isValidUUID(userId)) {
res.status(400).json({ success: false, message: 'Ungültige Benutzer-ID' });
return;
}
const callerId = getUserId(req);
const callerGroups: string[] = (req.user as any)?.groups ?? [];
const privileged = ['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'];
const isPrivileged = callerGroups.some((g) => privileged.includes(g));
if (userId !== callerId && !isPrivileged) {
res.status(403).json({ success: false, message: 'Keine Berechtigung' });
return;
}
const record = await atemschutzService.getByUserId(userId);
res.status(200).json({ success: true, data: record ?? null });
} catch (error) {
logger.error('Atemschutz getByUserId error', { error, userId: req.params.userId });
res.status(500).json({ success: false, message: 'Atemschutzstatus konnte nicht geladen werden' });
}
}
async getMyStatus(req: Request, res: Response): Promise<void> {
try {
const userId = getUserId(req);

View File

@@ -16,7 +16,9 @@ import {
type AppRole = 'admin' | 'kommandant' | 'mitglied';
function getRole(req: Request): AppRole {
return (req.user as any)?.role ?? 'mitglied';
// req.userRole is set by requirePermission() for non-owner paths.
// Fall back to req.user.role (JWT claim) and finally to 'mitglied'.
return (req as any).userRole ?? (req.user as any)?.role ?? 'mitglied';
}
function canWrite(req: Request): boolean {
@@ -209,14 +211,16 @@ class MemberController {
return;
}
const profile = await memberService.updateMemberProfile(
await memberService.updateMemberProfile(
userId,
parseResult.data as any,
updaterId
);
// Return full MemberWithProfile so the frontend state stays consistent
const fullMember = await memberService.getMemberById(userId);
logger.info('updateMember', { userId, updatedBy: updaterId });
res.status(200).json({ success: true, data: profile });
res.status(200).json({ success: true, data: fullMember });
} catch (error: any) {
if (error?.message === 'Mitgliedsprofil nicht gefunden.') {
res.status(404).json({ success: false, message: error.message });

View File

@@ -145,7 +145,7 @@ class NextcloudController {
return;
}
const token = req.params.token as string;
const { message } = req.body;
const { message, replyTo } = req.body;
if (!token || !message || typeof message !== 'string' || message.trim().length === 0) {
res.status(400).json({ success: false, message: 'Token und Nachricht erforderlich' });
return;
@@ -154,7 +154,8 @@ class NextcloudController {
res.status(400).json({ success: false, message: 'Nachricht zu lang' });
return;
}
await nextcloudService.sendMessage(token, message.trim(), credentials.loginName, credentials.appPassword);
const replyToNum = (typeof replyTo === 'number' && replyTo > 0) ? replyTo : undefined;
await nextcloudService.sendMessage(token, message.trim(), credentials.loginName, credentials.appPassword, replyToNum);
res.status(200).json({ success: true, data: null });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
@@ -302,6 +303,118 @@ class NextcloudController {
res.status(500).json({ success: false, message: 'Raum konnte nicht als gelesen markiert werden' });
}
}
async searchUsers(req: Request, res: Response): Promise<void> {
try {
const credentials = await userService.getNextcloudCredentials(req.user!.id);
if (!credentials) {
res.status(200).json({ success: true, data: [] });
return;
}
const query = (req.query.search as string) ?? '';
const results = await nextcloudService.searchUsers(query, credentials.loginName, credentials.appPassword);
res.status(200).json({ success: true, data: results });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
await userService.clearNextcloudCredentials(req.user!.id);
res.status(200).json({ success: true, data: [] });
return;
}
logger.error('searchUsers error', { error });
res.status(500).json({ success: false, message: 'Benutzersuche fehlgeschlagen' });
}
}
async createRoom(req: Request, res: Response): Promise<void> {
try {
const credentials = await userService.getNextcloudCredentials(req.user!.id);
if (!credentials) {
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
return;
}
const { roomType, invite, roomName } = req.body;
if (typeof roomType !== 'number' || !invite || typeof invite !== 'string') {
res.status(400).json({ success: false, message: 'roomType und invite erforderlich' });
return;
}
const result = await nextcloudService.createRoom(roomType, invite, roomName, credentials.loginName, credentials.appPassword);
res.status(200).json({ success: true, data: result });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') {
await userService.clearNextcloudCredentials(req.user!.id);
res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' });
return;
}
logger.error('createRoom error', { error });
res.status(500).json({ success: false, message: 'Raum konnte nicht erstellt werden' });
}
}
async addReaction(req: Request, res: Response): Promise<void> {
try {
const credentials = await userService.getNextcloudCredentials(req.user!.id);
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
const token = req.params.token;
const messageId = parseInt(req.params.messageId, 10);
const { reaction } = req.body;
if (!token || isNaN(messageId) || !reaction) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
await nextcloudService.addReaction(token, messageId, reaction, credentials.loginName, credentials.appPassword);
res.status(200).json({ success: true, data: null });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
logger.error('addReaction error', { error });
res.status(500).json({ success: false, message: 'Reaktion konnte nicht hinzugefügt werden' });
}
}
async removeReaction(req: Request, res: Response): Promise<void> {
try {
const credentials = await userService.getNextcloudCredentials(req.user!.id);
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
const token = req.params.token;
const messageId = parseInt(req.params.messageId, 10);
const reaction = req.query.reaction as string;
if (!token || isNaN(messageId) || !reaction) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
await nextcloudService.removeReaction(token, messageId, reaction, credentials.loginName, credentials.appPassword);
res.status(200).json({ success: true, data: null });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
logger.error('removeReaction error', { error });
res.status(500).json({ success: false, message: 'Reaktion konnte nicht entfernt werden' });
}
}
async getReactions(req: Request, res: Response): Promise<void> {
try {
const credentials = await userService.getNextcloudCredentials(req.user!.id);
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
const token = req.params.token;
const messageId = parseInt(req.params.messageId, 10);
if (!token || isNaN(messageId)) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
const data = await nextcloudService.getReactions(token, messageId, credentials.loginName, credentials.appPassword);
res.status(200).json({ success: true, data });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
logger.error('getReactions error', { error });
res.status(500).json({ success: false, message: 'Reaktionen konnten nicht geladen werden' });
}
}
async getPoll(req: Request, res: Response): Promise<void> {
try {
const credentials = await userService.getNextcloudCredentials(req.user!.id);
if (!credentials) { res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
const token = req.params.token;
const pollId = parseInt(req.params.pollId, 10);
if (!token || isNaN(pollId)) { res.status(400).json({ success: false, message: 'Parameter fehlen' }); return; }
const data = await nextcloudService.getPollDetails(token, pollId, credentials.loginName, credentials.appPassword);
res.status(200).json({ success: true, data });
} catch (error: any) {
if (error?.code === 'NEXTCLOUD_AUTH_INVALID') { await userService.clearNextcloudCredentials(req.user!.id); res.status(401).json({ success: false, message: 'Nextcloud nicht verbunden' }); return; }
logger.error('getPoll error', { error });
res.status(500).json({ success: false, message: 'Abstimmung konnte nicht geladen werden' });
}
}
}
export default new NextcloudController();