diff --git a/app/api/auth/route.ts b/app/api/auth/route.ts index 77a58c4..dc4f812 100644 --- a/app/api/auth/route.ts +++ b/app/api/auth/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; -import { EvaluationService } from "@/services/evaluation-service"; +import { userService } from "@/services/user-service"; import { UserProfile } from "@/lib/types"; const COOKIE_NAME = "peakSkills_userId"; @@ -18,8 +18,7 @@ export async function GET() { return NextResponse.json({ user: null }, { status: 200 }); } - const evaluationService = new EvaluationService(); - const userProfile = await evaluationService.getUserByUuid(userUuid); + const userProfile = await userService.getUserByUuid(userUuid); if (!userProfile) { // Cookie invalide, le supprimer @@ -52,8 +51,6 @@ export async function POST(request: NextRequest) { ); } - const evaluationService = new EvaluationService(); - // Vérifier s'il y a déjà un cookie d'authentification const cookieStore = await cookies(); const existingUserUuid = cookieStore.get(COOKIE_NAME)?.value; @@ -62,11 +59,11 @@ export async function POST(request: NextRequest) { if (existingUserUuid) { // Mettre à jour l'utilisateur existant - await evaluationService.updateUserByUuid(existingUserUuid, profile); + await userService.updateUserByUuid(existingUserUuid, profile); userUuid = existingUserUuid; } else { // Créer un nouvel utilisateur - userUuid = await evaluationService.upsertUserUuid(profile); + userUuid = await userService.upsertUserUuid(profile); } // Créer la réponse avec le cookie diff --git a/app/api/evaluations/route.ts b/app/api/evaluations/route.ts index 1e06dfd..5c60eb6 100644 --- a/app/api/evaluations/route.ts +++ b/app/api/evaluations/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; import { evaluationService } from "@/services/evaluation-service"; +import { userService } from "@/services/user-service"; import { UserEvaluation, UserProfile } from "@/lib/types"; import { COOKIE_NAME } from "@/lib/auth-utils"; @@ -29,7 +30,7 @@ export async function GET(request: NextRequest) { } // Mode authentifié par cookie UUID - const userProfile = await evaluationService.getUserByUuid(userUuid); + const userProfile = await userService.getUserByUuid(userUuid); if (!userProfile) { return NextResponse.json( { error: "Utilisateur non trouvé" }, diff --git a/app/api/evaluations/skills/route.ts b/app/api/evaluations/skills/route.ts index f034c09..d6be691 100644 --- a/app/api/evaluations/skills/route.ts +++ b/app/api/evaluations/skills/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; import { evaluationService } from "@/services/evaluation-service"; +import { userService } from "@/services/user-service"; import { UserProfile, SkillLevel } from "@/lib/types"; const COOKIE_NAME = "peakSkills_userId"; @@ -18,7 +19,7 @@ export async function PUT(request: NextRequest) { ); } - const userProfile = await evaluationService.getUserByUuid(userUuid); + const userProfile = await userService.getUserByUuid(userUuid); if (!userProfile) { return NextResponse.json( { error: "Utilisateur introuvable" }, diff --git a/lib/server-auth.ts b/lib/server-auth.ts index 8aad113..ee5222b 100644 --- a/lib/server-auth.ts +++ b/lib/server-auth.ts @@ -1,6 +1,6 @@ import { cookies } from "next/headers"; import { COOKIE_NAME } from "./auth-utils"; -import { EvaluationService } from "@/services/evaluation-service"; +import { evaluationService } from "@/services/evaluation-service"; import { TeamsService } from "@/services/teams-service"; import { SkillsService } from "@/services/skills-service"; import { SkillCategory, Team } from "./types"; @@ -46,8 +46,6 @@ export async function getServerUserEvaluation() { } try { - const evaluationService = new EvaluationService(); - // Charger directement l'évaluation par UUID const userEvaluation = await evaluationService.loadUserEvaluationByUuid( userUuid diff --git a/services/evaluation-service.ts b/services/evaluation-service.ts index 0d191f4..b7e95ff 100644 --- a/services/evaluation-service.ts +++ b/services/evaluation-service.ts @@ -1,4 +1,5 @@ import { getPool } from "./database"; +import { userService } from "./user-service"; import { UserEvaluation, UserProfile, @@ -8,133 +9,6 @@ import { } from "../lib/types"; export class EvaluationService { - /** - * Crée ou met à jour un utilisateur et retourne son UUID - */ - async upsertUserUuid(profile: UserProfile): Promise { - const pool = getPool(); - const client = await pool.connect(); - - try { - // Créer un nouvel utilisateur avec UUID auto-généré - const insertQuery = ` - INSERT INTO users (first_name, last_name, team_id, uuid_id) - VALUES ($1, $2, $3, uuid_generate_v4()) - RETURNING uuid_id - `; - - const result = await client.query(insertQuery, [ - profile.firstName, - profile.lastName, - profile.teamId, - ]); - - return result.rows[0].uuid_id; - } catch (error: any) { - // Si erreur de contrainte unique, l'utilisateur existe déjà - if ( - error.code === "23505" && - error.constraint === "users_first_name_last_name_team_id_key" - ) { - // Récupérer l'utilisateur existant - const existingUserQuery = ` - SELECT uuid_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, - ]); - - return existingUser.rows[0].uuid_id; - } - throw error; - } finally { - client.release(); - } - } - - /** - * Met à jour un utilisateur existant par son UUID - */ - async updateUserByUuid( - userUuid: string, - profile: UserProfile - ): Promise { - 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 - */ - async getUserByUuid(userUuid: string): Promise { - 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(); - } - } - /** * Charge une évaluation utilisateur directement par UUID */ @@ -233,95 +107,6 @@ export class EvaluationService { } } - /** - * Crée ou met à jour un utilisateur (legacy - retourne l'ID numérique) - */ - async upsertUser(profile: UserProfile): Promise { - 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(); - } - } - - /** - * Récupère un utilisateur par son ID - */ - async getUserById(userId: number): Promise { - 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.id = $1 - `; - - const result = await client.query(query, [userId]); - - 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(); - } - } - /** * Sauvegarde une évaluation utilisateur complète (version UUID) */ @@ -333,7 +118,7 @@ export class EvaluationService { await client.query("BEGIN"); // 1. Upsert user avec UUID - const userUuid = await this.upsertUserUuid(evaluation.profile); + const userUuid = await userService.upsertUserUuid(evaluation.profile); // 2. Upsert user_evaluation avec user_uuid const userEvalQuery = ` @@ -376,7 +161,7 @@ export class EvaluationService { await client.query("BEGIN"); // 1. Upsert user - const userId = await this.upsertUser(evaluation.profile); + const userId = await userService.upsertUser(evaluation.profile); // 2. Upsert user_evaluation - d'abord supprimer l'ancienne si elle existe await client.query("DELETE FROM user_evaluations WHERE user_id = $1", [ @@ -625,7 +410,7 @@ export class EvaluationService { try { await client.query("BEGIN"); - const userUuid = await this.upsertUserUuid(profile); + const userUuid = await userService.upsertUserUuid(profile); // Upsert user_evaluation avec user_uuid const userEvalResult = await client.query( @@ -731,7 +516,7 @@ export class EvaluationService { try { await client.query("BEGIN"); - const userId = await this.upsertUser(profile); + const userId = await userService.upsertUser(profile); // Upsert user_evaluation const userEvalResult = await client.query( @@ -821,7 +606,7 @@ export class EvaluationService { try { await client.query("BEGIN"); - const userUuid = await this.upsertUserUuid(profile); + const userUuid = await userService.upsertUserUuid(profile); // Supprimer directement la skill evaluation const deleteQuery = ` @@ -871,7 +656,7 @@ export class EvaluationService { try { await client.query("BEGIN"); - const userId = await this.upsertUser(profile); + const userId = await userService.upsertUser(profile); // Supprimer directement la skill evaluation const deleteQuery = ` diff --git a/services/index.ts b/services/index.ts index 6a5d4c3..c3ead87 100644 --- a/services/index.ts +++ b/services/index.ts @@ -5,6 +5,9 @@ // Database services (server-only) export { getPool, closePool } from "./database"; +// User services (server-only) +export { UserService, userService } from "./user-service"; + // Evaluation services (server-only) export { EvaluationService, evaluationService } from "./evaluation-service"; diff --git a/services/user-service.ts b/services/user-service.ts new file mode 100644 index 0000000..28ad1e4 --- /dev/null +++ b/services/user-service.ts @@ -0,0 +1,192 @@ +import { getPool } from "./database"; +import { UserProfile } from "../lib/types"; + +export class UserService { + /** + * Crée ou met à jour un utilisateur et retourne son UUID + */ + async upsertUserUuid(profile: UserProfile): Promise { + const pool = getPool(); + const client = await pool.connect(); + + try { + // Créer un nouvel utilisateur avec UUID auto-généré + const insertQuery = ` + INSERT INTO users (first_name, last_name, team_id, uuid_id) + VALUES ($1, $2, $3, uuid_generate_v4()) + RETURNING uuid_id + `; + + const result = await client.query(insertQuery, [ + profile.firstName, + profile.lastName, + profile.teamId, + ]); + + return result.rows[0].uuid_id; + } catch (error: any) { + // Si erreur de contrainte unique, l'utilisateur existe déjà + if ( + error.code === "23505" && + error.constraint === "users_first_name_last_name_team_id_key" + ) { + // Récupérer l'utilisateur existant + const existingUserQuery = ` + SELECT uuid_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, + ]); + + return existingUser.rows[0].uuid_id; + } + throw error; + } finally { + client.release(); + } + } + + /** + * Met à jour un utilisateur existant par son UUID + */ + async updateUserByUuid( + userUuid: string, + profile: UserProfile + ): Promise { + 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 + */ + async getUserByUuid(userUuid: string): Promise { + 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 { + 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(); + } + } +} + +// Instance singleton +export const userService = new UserService();