This commit is contained in:
Matthias Hochmeister
2026-03-13 15:09:43 +01:00
parent 7833dca29c
commit 20d2c9093a
4 changed files with 52 additions and 6 deletions

View File

@@ -3,6 +3,7 @@ import { z } from 'zod';
import authentikService from '../services/authentik.service'; import authentikService from '../services/authentik.service';
import tokenService from '../services/token.service'; import tokenService from '../services/token.service';
import userService from '../services/user.service'; import userService from '../services/user.service';
import memberService from '../services/member.service';
import logger from '../utils/logger'; import logger from '../utils/logger';
import auditService, { AuditAction, AuditResourceType } from '../services/audit.service'; import auditService, { AuditAction, AuditResourceType } from '../services/audit.service';
import { extractIp, extractUserAgent } from '../middleware/audit.middleware'; import { extractIp, extractUserAgent } from '../middleware/audit.middleware';
@@ -119,6 +120,7 @@ class AuthController {
}); });
await userService.updateGroups(user.id, groups); await userService.updateGroups(user.id, groups);
await memberService.ensureProfileExists(user.id);
// Audit: first-ever login (user record creation) // Audit: first-ever login (user record creation)
auditService.logAudit({ auditService.logAudit({
@@ -142,6 +144,7 @@ class AuthController {
await userService.updateLastLogin(user.id); await userService.updateLastLogin(user.id);
await userService.updateGroups(user.id, groups); await userService.updateGroups(user.id, groups);
await memberService.ensureProfileExists(user.id);
const { given_name: updatedGivenName, family_name: updatedFamilyName } = extractNames(userInfo); const { given_name: updatedGivenName, family_name: updatedFamilyName } = extractNames(userInfo);

View File

@@ -544,6 +544,24 @@ class MemberService {
} }
} }
/**
* Ensures a mitglieder_profile row exists for the given user.
* Safe to call on every login — idempotent via ON CONFLICT DO NOTHING.
* Errors are caught and logged without throwing.
*/
async ensureProfileExists(userId: string): Promise<void> {
try {
await pool.query(
`INSERT INTO mitglieder_profile (user_id, status)
VALUES ($1, 'aktiv')
ON CONFLICT (user_id) DO NOTHING`,
[userId]
);
} catch (error) {
logger.warn('ensureProfileExists failed (non-fatal)', { error, userId });
}
}
/** /**
* Returns aggregate member counts, used by the dashboard KPI cards. * Returns aggregate member counts, used by the dashboard KPI cards.
* Optionally scoped to a single status value. * Optionally scoped to a single status value.

View File

@@ -61,7 +61,7 @@ import {
function useCanWrite(): boolean { function useCanWrite(): boolean {
const { user } = useAuth(); const { user } = useAuth();
const groups: string[] = (user as any)?.groups ?? []; const groups: string[] = (user as any)?.groups ?? [];
return groups.includes('feuerwehr-admin') || groups.includes('feuerwehr-kommandant'); return groups.includes('dashboard_admin') || groups.includes('dashboard_kommando');
} }
function useCurrentUserId(): string | undefined { function useCurrentUserId(): string | undefined {
@@ -473,11 +473,36 @@ function MitgliedDetail() {
</Box> </Box>
{!profile && ( {!profile && (
<Alert severity="info" sx={{ mt: 2 }}> <Alert
severity="info"
sx={{ mt: 2 }}
action={
canWrite ? (
<Button
color="inherit"
size="small"
disabled={saving}
onClick={async () => {
if (!userId) return;
setSaving(true);
setSaveError(null);
try {
await membersService.createMemberProfile(userId, { status: 'aktiv' });
await loadMember();
} catch {
setSaveError('Profil konnte nicht erstellt werden.');
} finally {
setSaving(false);
}
}}
>
{saving ? <CircularProgress size={16} /> : 'Profil erstellen'}
</Button>
) : undefined
}
>
Für dieses Mitglied wurde noch kein Profil angelegt. Für dieses Mitglied wurde noch kein Profil angelegt.
{canWrite {!canWrite && ' Wende dich an einen Administrator, um dein Profil anlegen zu lassen.'}
? ' Ein Kommandant kann das Profil unter "Stammdaten" erstellen.'
: ' Wende dich an einen Administrator, um dein Profil anlegen zu lassen.'}
</Alert> </Alert>
)} )}

View File

@@ -53,7 +53,7 @@ import {
function useCanWrite(): boolean { function useCanWrite(): boolean {
const { user } = useAuth(); const { user } = useAuth();
const groups: string[] = (user as any)?.groups ?? []; const groups: string[] = (user as any)?.groups ?? [];
return groups.includes('feuerwehr-admin') || groups.includes('feuerwehr-kommandant'); return groups.includes('dashboard_admin') || groups.includes('dashboard_kommando');
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------