Files
peakskills/services/user-service.ts
2025-08-25 22:04:39 +02:00

407 lines
9.7 KiB
TypeScript

import { getPool } from "./database";
import { UserProfile } from "../lib/types";
export class UserService {
/**
* Crée ou met à jour un utilisateur et retourne son UUID
* Note: Cette méthode est pour la compatibilité avec l'ancien système
* Les nouveaux utilisateurs doivent utiliser createUser avec email/password
*/
/**
* Met à jour un utilisateur existant par son UUID
*/
async updateUserByUuid(
userUuid: string,
profile: UserProfile
): Promise<void> {
const pool = getPool();
const client = await pool.connect();
try {
// Vérifier s'il existe déjà un utilisateur avec ce nom/prénom dans la nouvelle équipe
const conflictCheckQuery = `
SELECT uuid_id FROM users
WHERE first_name = $1 AND last_name = $2 AND team_id = $3 AND uuid_id != $4
`;
const conflictResult = await client.query(conflictCheckQuery, [
profile.firstName,
profile.lastName,
profile.teamId,
userUuid,
]);
if (conflictResult.rows.length > 0) {
throw new Error(
"Un utilisateur avec ce nom et prénom existe déjà dans cette équipe"
);
}
// Mettre à jour l'utilisateur existant
const updateQuery = `
UPDATE users
SET first_name = $1, last_name = $2, team_id = $3, updated_at = CURRENT_TIMESTAMP
WHERE uuid_id = $4
`;
await client.query(updateQuery, [
profile.firstName,
profile.lastName,
profile.teamId,
userUuid,
]);
} finally {
client.release();
}
}
/**
* Récupère un utilisateur par son UUID
*/
/**
* Trouve un utilisateur par son profil (firstName, lastName)
*/
async findUserByProfile(profile: UserProfile): Promise<{
uuid: string;
teamId: string;
} | null> {
const pool = getPool();
const client = await pool.connect();
try {
const query = `
SELECT uuid_id, team_id
FROM users
WHERE first_name = $1 AND last_name = $2
`;
const result = await client.query(query, [
profile.firstName,
profile.lastName,
]);
if (result.rows.length === 0) {
return null;
}
return {
uuid: result.rows[0].uuid_id,
teamId: result.rows[0].team_id,
};
} finally {
client.release();
}
}
async getUserByUuid(userUuid: string): Promise<UserProfile | null> {
const pool = getPool();
const client = await pool.connect();
try {
const query = `
SELECT u.first_name, u.last_name, u.team_id
FROM users u
WHERE u.uuid_id = $1
`;
const result = await client.query(query, [userUuid]);
if (result.rows.length === 0) {
return null;
}
const user = result.rows[0];
return {
firstName: user.first_name,
lastName: user.last_name,
teamId: user.team_id,
};
} finally {
client.release();
}
}
/**
* Crée ou met à jour un utilisateur (legacy - retourne l'ID numérique)
*/
async upsertUser(profile: UserProfile): Promise<number> {
const pool = getPool();
const client = await pool.connect();
try {
// Vérifier si l'utilisateur existe déjà (par firstName + lastName + teamId)
const existingUserQuery = `
SELECT id FROM users
WHERE first_name = $1 AND last_name = $2 AND team_id = $3
`;
const existingUser = await client.query(existingUserQuery, [
profile.firstName,
profile.lastName,
profile.teamId,
]);
if (existingUser.rows.length > 0) {
// Mettre à jour l'utilisateur existant
const updateQuery = `
UPDATE users
SET first_name = $1, last_name = $2, team_id = $3, updated_at = CURRENT_TIMESTAMP
WHERE id = $4
RETURNING id
`;
const result = await client.query(updateQuery, [
profile.firstName,
profile.lastName,
profile.teamId,
existingUser.rows[0].id,
]);
return result.rows[0].id;
} else {
// Créer un nouvel utilisateur
const insertQuery = `
INSERT INTO users (first_name, last_name, team_id)
VALUES ($1, $2, $3)
RETURNING id
`;
const result = await client.query(insertQuery, [
profile.firstName,
profile.lastName,
profile.teamId,
]);
return result.rows[0].id;
}
} finally {
client.release();
}
}
/**
* Delete user for admin
*/
static async deleteUserForAdmin(
userId: string
): Promise<{ firstName: string; lastName: string }> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
// Vérifier que l'utilisateur existe
const userCheck = await client.query(
"SELECT uuid_id, first_name, last_name, team_id FROM users WHERE uuid_id = $1",
[userId]
);
if (userCheck.rows.length === 0) {
throw new Error("Utilisateur non trouvé");
}
const user = userCheck.rows[0];
// Si l'utilisateur est dans une équipe, le retirer automatiquement
if (user.team_id) {
// Retirer l'utilisateur de son équipe
await client.query(
"UPDATE users SET team_id = NULL WHERE uuid_id = $1",
[userId]
);
}
// Supprimer l'utilisateur (les évaluations par skills seront supprimées automatiquement grâce aux contraintes CASCADE)
await client.query("DELETE FROM users WHERE uuid_id = $1", [userId]);
await client.query("COMMIT");
return {
firstName: user.first_name,
lastName: user.last_name,
};
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
}
/**
* Get all users with team info and evaluations status for admin
*/
static async getAllUsersForAdmin(): Promise<
Array<{
uuid: string;
firstName: string;
lastName: string;
teamName: string | null;
hasEvaluations: boolean;
}>
> {
const pool = getPool();
const query = `
SELECT
u.uuid_id,
u.first_name,
u.last_name,
t.name as team_name,
CASE
WHEN ue.id IS NOT NULL THEN true
ELSE false
END as has_evaluations
FROM users u
LEFT JOIN teams t ON u.team_id = t.id
LEFT JOIN user_evaluations ue ON u.uuid_id = ue.user_uuid
ORDER BY u.first_name, u.last_name
`;
try {
const result = await pool.query(query);
return result.rows.map((row) => ({
uuid: row.uuid_id,
firstName: row.first_name,
lastName: row.last_name,
teamName: row.team_name,
hasEvaluations: row.has_evaluations,
}));
} catch (error) {
console.error("Error fetching users:", error);
throw new Error("Failed to fetch users");
}
}
/**
* Récupère un utilisateur par son email
*/
async getUserByEmail(email: string): Promise<{
uuid_id: string;
first_name: string;
last_name: string;
email: string;
team_id: string;
} | null> {
const pool = getPool();
const client = await pool.connect();
try {
const query = `
SELECT uuid_id, first_name, last_name, email, team_id
FROM users
WHERE email = $1
`;
const result = await client.query(query, [email]);
if (result.rows.length === 0) {
return null;
}
return result.rows[0];
} finally {
client.release();
}
}
/**
* Crée un nouvel utilisateur avec email et mot de passe hashé
*/
async createUser(data: {
firstName: string;
lastName: string;
email: string;
passwordHash: string;
teamId: string;
}): Promise<{
uuid_id: string;
first_name: string;
last_name: string;
email: string;
team_id: string;
} | null> {
const pool = getPool();
const client = await pool.connect();
try {
const query = `
INSERT INTO users (first_name, last_name, email, password_hash, team_id, uuid_id)
VALUES ($1, $2, $3, $4, $5, uuid_generate_v4())
RETURNING uuid_id, first_name, last_name, email, team_id
`;
const result = await client.query(query, [
data.firstName,
data.lastName,
data.email,
data.passwordHash,
data.teamId,
]);
if (result.rows.length === 0) {
return null;
}
return result.rows[0];
} finally {
client.release();
}
}
/**
* Vérifie les identifiants de connexion
*/
async verifyCredentials(
email: string,
password: string
): Promise<{
uuid_id: string;
first_name: string;
last_name: string;
email: string;
team_id: string;
} | null> {
const pool = getPool();
const client = await pool.connect();
try {
const query = `
SELECT uuid_id, first_name, last_name, email, team_id, password_hash
FROM users
WHERE email = $1
`;
const result = await client.query(query, [email]);
if (result.rows.length === 0) {
return null;
}
const user = result.rows[0];
// Vérifier le mot de passe
const bcrypt = require("bcryptjs");
const isValidPassword = await bcrypt.compare(
password,
user.password_hash
);
if (!isValidPassword) {
return null;
}
// Retourner l'utilisateur sans le hash du mot de passe
const { password_hash, ...userWithoutPassword } = user;
return userWithoutPassword;
} finally {
client.release();
}
}
}
// Instance singleton
export const userService = new UserService();