import pool from '../config/database'; import logger from '../utils/logger'; import { User, CreateUserData, UpdateUserData } from '../models/user.model'; class UserService { /** * Find user by Authentik sub (subject identifier) */ async findByAuthentikSub(sub: string): Promise { try { 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, authentik_groups FROM users WHERE authentik_sub = $1 `; const result = await pool.query(query, [sub]); if (result.rows.length === 0) { logger.debug('User not found by Authentik sub', { sub }); return null; } logger.debug('User found by Authentik sub', { sub, userId: result.rows[0].id }); return result.rows[0] as User; } catch (error) { logger.error('Error finding user by Authentik sub', { error, sub }); throw new Error('Database query failed'); } } /** * Find user by email */ async findByEmail(email: string): Promise { try { 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, authentik_groups FROM users WHERE email = $1 `; const result = await pool.query(query, [email]); if (result.rows.length === 0) { logger.debug('User not found by email', { email }); return null; } logger.debug('User found by email', { email, userId: result.rows[0].id }); return result.rows[0] as User; } catch (error) { logger.error('Error finding user by email', { error, email }); throw new Error('Database query failed'); } } /** * Find user by ID */ async findById(id: string): Promise { try { 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, authentik_groups FROM users WHERE id = $1 `; const result = await pool.query(query, [id]); if (result.rows.length === 0) { logger.debug('User not found by ID', { id }); return null; } logger.debug('User found by ID', { id }); return result.rows[0] as User; } catch (error) { logger.error('Error finding user by ID', { error, id }); throw new Error('Database query failed'); } } /** * Create a new user */ async createUser(userData: CreateUserData): Promise { try { const query = ` INSERT INTO users ( email, authentik_sub, name, preferred_username, given_name, family_name, profile_picture_url, is_active, authentik_groups ) 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, authentik_groups `; const values = [ userData.email, userData.authentik_sub, userData.name || null, userData.preferred_username || null, userData.given_name || null, userData.family_name || null, userData.profile_picture_url || null, userData.authentik_groups ?? [], ]; const result = await pool.query(query, values); const user = result.rows[0] as User; logger.info('User created successfully', { userId: user.id, email: user.email, }); return user; } catch (error) { logger.error('Error creating user', { error, email: userData.email }); throw new Error('Failed to create user'); } } /** * Update user information */ async updateUser(id: string, data: UpdateUserData): Promise { try { const updateFields: string[] = []; const values: any[] = []; let paramCount = 1; if (data.name !== undefined) { updateFields.push(`name = $${paramCount++}`); values.push(data.name); } if (data.preferred_username !== undefined) { updateFields.push(`preferred_username = $${paramCount++}`); values.push(data.preferred_username); } if (data.given_name !== undefined) { updateFields.push(`given_name = $${paramCount++}`); values.push(data.given_name); } if (data.family_name !== undefined) { updateFields.push(`family_name = $${paramCount++}`); values.push(data.family_name); } if (data.profile_picture_url !== undefined) { updateFields.push(`profile_picture_url = $${paramCount++}`); values.push(data.profile_picture_url); } if (data.is_active !== undefined) { updateFields.push(`is_active = $${paramCount++}`); values.push(data.is_active); } if (data.preferences !== undefined) { updateFields.push(`preferences = $${paramCount++}`); values.push(JSON.stringify(data.preferences)); } if (updateFields.length === 0) { throw new Error('No fields to update'); } updateFields.push(`updated_at = CURRENT_TIMESTAMP`); values.push(id); const query = ` UPDATE users SET ${updateFields.join(', ')} 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, authentik_groups `; const result = await pool.query(query, values); if (result.rows.length === 0) { throw new Error('User not found'); } const user = result.rows[0] as User; logger.info('User updated successfully', { userId: user.id }); return user; } catch (error) { logger.error('Error updating user', { error, userId: id }); throw new Error('Failed to update user'); } } /** * Update last login timestamp */ async updateLastLogin(id: string): Promise { try { const query = ` UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = $1 `; await pool.query(query, [id]); logger.debug('Updated last login timestamp', { userId: id }); } catch (error) { logger.error('Error updating last login', { error, userId: id }); // Don't throw - this is not critical } } /** * Update refresh token */ async updateRefreshToken( id: string, refreshToken: string | null, expiresAt: Date | null ): Promise { try { const query = ` UPDATE users SET refresh_token = $1, refresh_token_expires_at = $2 WHERE id = $3 `; await pool.query(query, [refreshToken, expiresAt, id]); logger.debug('Updated refresh token', { userId: id }); } catch (error) { logger.error('Error updating refresh token', { error, userId: id }); throw new Error('Failed to update refresh token'); } } /** * Check if user is active */ async isUserActive(id: string): Promise { try { const query = ` SELECT is_active FROM users WHERE id = $1 `; const result = await pool.query(query, [id]); if (result.rows.length === 0) { return false; } return result.rows[0].is_active; } catch (error) { logger.error('Error checking user active status', { error, userId: id }); return false; } } /** * Sync Authentik groups for a user */ async updateGroups(id: string, groups: string[]): Promise { 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();