- Changed COOKIE_NAME from "peakSkills_userId" to "session_token" for better clarity. - Updated AuthClient to handle login and registration with new data structures. - Enhanced AuthWrapper to manage user sessions and display appropriate messages. - Added error handling in LoginForm and RegisterForm for better user feedback. - Refactored user service methods to streamline user creation and verification processes.
405 lines
9.7 KiB
TypeScript
405 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];
|
|
|
|
// Vérifier que l'utilisateur n'est pas dans une équipe
|
|
if (user.team_id) {
|
|
throw new Error(
|
|
"Impossible de supprimer un utilisateur qui appartient à une équipe. Retirez-le d'abord de son équipe."
|
|
);
|
|
}
|
|
|
|
// 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();
|