featur add fahrmeister

This commit is contained in:
Matthias Hochmeister
2026-02-27 21:46:50 +01:00
parent da4a56ba6b
commit dbe4f52871
17 changed files with 426 additions and 152 deletions

View File

@@ -14,6 +14,7 @@ class TokenService {
userId: payload.userId,
email: payload.email,
authentikSub: payload.authentikSub,
groups: payload.groups ?? [],
},
environment.jwt.secret,
{

View File

@@ -11,7 +11,7 @@ class UserService {
const query = `
SELECT id, email, authentik_sub, name, preferred_username, given_name,
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
is_active, last_login_at, created_at, updated_at, preferences
is_active, last_login_at, created_at, updated_at, preferences, authentik_groups
FROM users
WHERE authentik_sub = $1
`;
@@ -39,7 +39,7 @@ class UserService {
const query = `
SELECT id, email, authentik_sub, name, preferred_username, given_name,
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
is_active, last_login_at, created_at, updated_at, preferences
is_active, last_login_at, created_at, updated_at, preferences, authentik_groups
FROM users
WHERE email = $1
`;
@@ -67,7 +67,7 @@ class UserService {
const query = `
SELECT id, email, authentik_sub, name, preferred_username, given_name,
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
is_active, last_login_at, created_at, updated_at, preferences
is_active, last_login_at, created_at, updated_at, preferences, authentik_groups
FROM users
WHERE id = $1
`;
@@ -101,12 +101,13 @@ class UserService {
given_name,
family_name,
profile_picture_url,
is_active
is_active,
authentik_groups
)
VALUES ($1, $2, $3, $4, $5, $6, $7, true)
VALUES ($1, $2, $3, $4, $5, $6, $7, true, $8)
RETURNING id, email, authentik_sub, name, preferred_username, given_name,
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
is_active, last_login_at, created_at, updated_at, preferences
is_active, last_login_at, created_at, updated_at, preferences, authentik_groups
`;
const values = [
@@ -117,6 +118,7 @@ class UserService {
userData.given_name || null,
userData.family_name || null,
userData.profile_picture_url || null,
userData.authentik_groups ?? [],
];
const result = await pool.query(query, values);
@@ -185,7 +187,7 @@ class UserService {
WHERE id = $${paramCount}
RETURNING id, email, authentik_sub, name, preferred_username, given_name,
family_name, profile_picture_url, refresh_token, refresh_token_expires_at,
is_active, last_login_at, created_at, updated_at, preferences
is_active, last_login_at, created_at, updated_at, preferences, authentik_groups
`;
const result = await pool.query(query, values);
@@ -270,6 +272,22 @@ class UserService {
return false;
}
}
/**
* Sync Authentik groups for a user
*/
async updateGroups(id: string, groups: string[]): Promise<void> {
try {
await pool.query(
`UPDATE users SET authentik_groups = $1 WHERE id = $2`,
[groups, id]
);
logger.debug('Updated authentik_groups', { userId: id });
} catch (error) {
logger.error('Error updating authentik_groups', { error, userId: id });
throw new Error('Failed to update user groups');
}
}
}
export default new UserService();

View File

@@ -63,6 +63,10 @@ class VehicleService {
status,
status_bemerkung,
bild_url,
paragraph57a_faellig_am,
paragraph57a_tage_bis_faelligkeit,
naechste_wartung_am,
wartung_tage_bis_faelligkeit,
hu_faellig_am,
hu_tage_bis_faelligkeit,
au_faellig_am,
@@ -78,6 +82,10 @@ class VehicleService {
return result.rows.map((row) => ({
...row,
paragraph57a_tage_bis_faelligkeit: row.paragraph57a_tage_bis_faelligkeit != null
? parseInt(row.paragraph57a_tage_bis_faelligkeit, 10) : null,
wartung_tage_bis_faelligkeit: row.wartung_tage_bis_faelligkeit != null
? parseInt(row.wartung_tage_bis_faelligkeit, 10) : null,
hu_tage_bis_faelligkeit: row.hu_tage_bis_faelligkeit != null
? parseInt(row.hu_tage_bis_faelligkeit, 10) : null,
au_tage_bis_faelligkeit: row.au_tage_bis_faelligkeit != null
@@ -145,6 +153,8 @@ class VehicleService {
status_bemerkung: row.status_bemerkung,
standort: row.standort,
bild_url: row.bild_url,
paragraph57a_faellig_am: row.paragraph57a_faellig_am ?? null,
naechste_wartung_am: row.naechste_wartung_am ?? null,
created_at: row.created_at,
updated_at: row.updated_at,
pruefstatus: {
@@ -153,6 +163,10 @@ class VehicleService {
uvv: mapPruefungStatus(row, 'uvv'),
leiter: mapPruefungStatus(row, 'leiter'),
},
paragraph57a_tage_bis_faelligkeit: row.paragraph57a_tage_bis_faelligkeit != null
? parseInt(row.paragraph57a_tage_bis_faelligkeit, 10) : null,
wartung_tage_bis_faelligkeit: row.wartung_tage_bis_faelligkeit != null
? parseInt(row.wartung_tage_bis_faelligkeit, 10) : null,
naechste_pruefung_tage: row.naechste_pruefung_tage != null
? parseInt(row.naechste_pruefung_tage, 10) : null,
pruefungen: pruefungenResult.rows as FahrzeugPruefung[],
@@ -179,8 +193,9 @@ class VehicleService {
`INSERT INTO fahrzeuge (
bezeichnung, kurzname, amtliches_kennzeichen, fahrgestellnummer,
baujahr, hersteller, typ_schluessel, besatzung_soll,
status, status_bemerkung, standort, bild_url
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
status, status_bemerkung, standort, bild_url,
paragraph57a_faellig_am, naechste_wartung_am
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
RETURNING *`,
[
data.bezeichnung,
@@ -195,6 +210,8 @@ class VehicleService {
data.status_bemerkung ?? null,
data.standort ?? 'Feuerwehrhaus',
data.bild_url ?? null,
data.paragraph57a_faellig_am ?? null,
data.naechste_wartung_am ?? null,
]
);
@@ -234,6 +251,8 @@ class VehicleService {
if (data.status_bemerkung !== undefined) addField('status_bemerkung', data.status_bemerkung);
if (data.standort !== undefined) addField('standort', data.standort);
if (data.bild_url !== undefined) addField('bild_url', data.bild_url);
if (data.paragraph57a_faellig_am !== undefined) addField('paragraph57a_faellig_am', data.paragraph57a_faellig_am);
if (data.naechste_wartung_am !== undefined) addField('naechste_wartung_am', data.naechste_wartung_am);
if (fields.length === 0) {
throw new Error('No fields to update');
@@ -258,6 +277,24 @@ class VehicleService {
}
}
async deleteVehicle(id: string, deletedBy: string): Promise<void> {
try {
const result = await pool.query(
`DELETE FROM fahrzeuge WHERE id = $1 RETURNING id`,
[id]
);
if (result.rows.length === 0) {
throw new Error('Vehicle not found');
}
logger.info('Vehicle deleted', { id, by: deletedBy });
} catch (error) {
logger.error('VehicleService.deleteVehicle failed', { error, id });
throw error;
}
}
// =========================================================================
// STATUS MANAGEMENT
// Socket.io-ready: accepts optional `io` parameter.