import { TaskStatus, KanbanFilters, ViewPreferences, ColumnVisibility, UserPreferences, JiraConfig } from '@/lib/types'; import { prisma } from './database'; import { getConfig } from '@/lib/config'; // Valeurs par défaut const DEFAULT_PREFERENCES: UserPreferences = { kanbanFilters: { search: '', tags: [], priorities: [], showCompleted: true, sortBy: '' }, viewPreferences: { compactView: false, swimlanesByTags: false, swimlanesMode: 'tags', showObjectives: true, showFilters: true, objectivesCollapsed: false, theme: 'dark', fontSize: 'medium' }, columnVisibility: { hiddenStatuses: [] }, jiraConfig: { enabled: false, baseUrl: '', email: '', apiToken: '', ignoredProjects: [] }, jiraAutoSync: false, jiraSyncInterval: 'daily' }; /** * Service pour gérer les préférences utilisateur en base de données */ class UserPreferencesService { private readonly USER_ID = 'default'; // Pour l'instant, un seul utilisateur /** * Récupère ou crée l'entrée user preferences (avec upsert pour éviter les doublons) */ private async getOrCreateUserPreferences() { // Utiliser upsert pour éviter les conditions de course const userPrefs = await prisma.userPreferences.upsert({ where: { id: 'default' }, // ID fixe pour l'utilisateur unique update: {}, // Ne rien mettre à jour si existe create: { id: 'default', kanbanFilters: DEFAULT_PREFERENCES.kanbanFilters, viewPreferences: DEFAULT_PREFERENCES.viewPreferences, columnVisibility: DEFAULT_PREFERENCES.columnVisibility, jiraConfig: DEFAULT_PREFERENCES.jiraConfig as any, // eslint-disable-line @typescript-eslint/no-explicit-any } }); // S'assurer que les nouveaux champs existent (migration douce) await this.ensureJiraSchedulerFields(); return userPrefs; } /** * S'assure que les champs jiraAutoSync et jiraSyncInterval existent */ private async ensureJiraSchedulerFields(): Promise { try { await prisma.$executeRaw` UPDATE user_preferences SET jiraAutoSync = COALESCE(jiraAutoSync, ${DEFAULT_PREFERENCES.jiraAutoSync}), jiraSyncInterval = COALESCE(jiraSyncInterval, ${DEFAULT_PREFERENCES.jiraSyncInterval}) WHERE id = 'default' `; } catch (error) { // Ignorer les erreurs si les colonnes n'existent pas encore console.debug('Migration douce des champs scheduler Jira:', error); } } // === FILTRES KANBAN === /** * Sauvegarde les filtres Kanban */ async saveKanbanFilters(filters: KanbanFilters): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); await prisma.userPreferences.update({ where: { id: userPrefs.id }, data: { kanbanFilters: filters } }); } catch (error) { console.warn('Erreur lors de la sauvegarde des filtres Kanban:', error); throw error; } } /** * Récupère les filtres Kanban */ async getKanbanFilters(): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); const filters = userPrefs.kanbanFilters as KanbanFilters | null; return { ...DEFAULT_PREFERENCES.kanbanFilters, ...(filters || {}) }; } catch (error) { console.warn('Erreur lors de la récupération des filtres Kanban:', error); return DEFAULT_PREFERENCES.kanbanFilters; } } // === PRÉFÉRENCES DE VUE === /** * Sauvegarde les préférences de vue */ async saveViewPreferences(preferences: ViewPreferences): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); await prisma.userPreferences.update({ where: { id: userPrefs.id }, data: { viewPreferences: preferences } }); } catch (error) { console.warn('Erreur lors de la sauvegarde des préférences de vue:', error); throw error; } } /** * Récupère les préférences de vue */ async getViewPreferences(): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); const preferences = userPrefs.viewPreferences as ViewPreferences | null; return { ...DEFAULT_PREFERENCES.viewPreferences, ...(preferences || {}) }; } catch (error) { console.warn('Erreur lors de la récupération des préférences de vue:', error); return DEFAULT_PREFERENCES.viewPreferences; } } // === VISIBILITÉ DES COLONNES === /** * Sauvegarde la visibilité des colonnes */ async saveColumnVisibility(visibility: ColumnVisibility): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); await prisma.userPreferences.update({ where: { id: userPrefs.id }, data: { columnVisibility: visibility } }); } catch (error) { console.warn('Erreur lors de la sauvegarde de la visibilité des colonnes:', error); throw error; } } /** * Récupère la visibilité des colonnes */ async getColumnVisibility(): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); const visibility = userPrefs.columnVisibility as ColumnVisibility | null; return { ...DEFAULT_PREFERENCES.columnVisibility, ...(visibility || {}) }; } catch (error) { console.warn('Erreur lors de la récupération de la visibilité des colonnes:', error); return DEFAULT_PREFERENCES.columnVisibility; } } // === MÉTHODES GLOBALES === /** * Récupère uniquement le thème pour le SSR (optimisé) */ async getTheme(): Promise<'light' | 'dark'> { try { const userPrefs = await this.getOrCreateUserPreferences(); const viewPrefs = userPrefs.viewPreferences as ViewPreferences; return viewPrefs.theme; } catch (error) { console.error('Erreur lors de la récupération du thème:', error); return DEFAULT_PREFERENCES.viewPreferences.theme; // Fallback } } // === CONFIGURATION JIRA === /** * Sauvegarde la configuration Jira */ async saveJiraConfig(config: JiraConfig): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); await prisma.userPreferences.update({ where: { id: userPrefs.id }, data: { jiraConfig: config as any } // eslint-disable-line @typescript-eslint/no-explicit-any }); } catch (error) { console.warn('Erreur lors de la sauvegarde de la config Jira:', error); throw error; } } /** * Récupère la configuration Jira depuis la base de données avec fallback sur les variables d'environnement */ async getJiraConfig(): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); const dbConfig = userPrefs.jiraConfig as JiraConfig | null; // Si config en DB, l'utiliser if (dbConfig && (dbConfig.baseUrl || dbConfig.email || dbConfig.apiToken)) { return { ...DEFAULT_PREFERENCES.jiraConfig, ...dbConfig }; } // Sinon fallback sur les variables d'environnement (existant) const config = getConfig(); return { baseUrl: config.integrations.jira.baseUrl, email: config.integrations.jira.email, apiToken: '', // On ne retourne pas le token des env vars pour la sécurité enabled: config.integrations.jira.enabled }; } catch (error) { console.warn('Erreur lors de la récupération de la config Jira:', error); return DEFAULT_PREFERENCES.jiraConfig; } } // === CONFIGURATION SCHEDULER JIRA === /** * Sauvegarde les préférences du scheduler Jira */ async saveJiraSchedulerConfig(jiraAutoSync: boolean, jiraSyncInterval: 'hourly' | 'daily' | 'weekly'): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); // Utiliser une requête SQL brute temporairement pour éviter les problèmes de types await prisma.$executeRaw` UPDATE user_preferences SET jiraAutoSync = ${jiraAutoSync}, jiraSyncInterval = ${jiraSyncInterval} WHERE id = ${userPrefs.id} `; } catch (error) { console.warn('Erreur lors de la sauvegarde de la config scheduler Jira:', error); throw error; } } /** * Récupère les préférences du scheduler Jira */ async getJiraSchedulerConfig(): Promise<{ jiraAutoSync: boolean; jiraSyncInterval: 'hourly' | 'daily' | 'weekly' }> { try { const userPrefs = await this.getOrCreateUserPreferences(); // Utiliser une requête SQL brute pour récupérer les nouveaux champs const result = await prisma.$queryRaw>` SELECT jiraAutoSync, jiraSyncInterval FROM user_preferences WHERE id = ${userPrefs.id} `; if (result.length > 0) { return { jiraAutoSync: Boolean(result[0].jiraAutoSync), jiraSyncInterval: (result[0].jiraSyncInterval as 'hourly' | 'daily' | 'weekly') || DEFAULT_PREFERENCES.jiraSyncInterval }; } return { jiraAutoSync: DEFAULT_PREFERENCES.jiraAutoSync, jiraSyncInterval: DEFAULT_PREFERENCES.jiraSyncInterval }; } catch (error) { console.warn('Erreur lors de la récupération de la config scheduler Jira:', error); return { jiraAutoSync: DEFAULT_PREFERENCES.jiraAutoSync, jiraSyncInterval: DEFAULT_PREFERENCES.jiraSyncInterval }; } } /** * Récupère toutes les préférences utilisateur */ async getAllPreferences(): Promise { const [kanbanFilters, viewPreferences, columnVisibility, jiraConfig, jiraSchedulerConfig] = await Promise.all([ this.getKanbanFilters(), this.getViewPreferences(), this.getColumnVisibility(), this.getJiraConfig(), this.getJiraSchedulerConfig() ]); return { kanbanFilters, viewPreferences, columnVisibility, jiraConfig, jiraAutoSync: jiraSchedulerConfig.jiraAutoSync, jiraSyncInterval: jiraSchedulerConfig.jiraSyncInterval }; } /** * Sauvegarde toutes les préférences utilisateur */ async saveAllPreferences(preferences: UserPreferences): Promise { await Promise.all([ this.saveKanbanFilters(preferences.kanbanFilters), this.saveViewPreferences(preferences.viewPreferences), this.saveColumnVisibility(preferences.columnVisibility), this.saveJiraConfig(preferences.jiraConfig), this.saveJiraSchedulerConfig(preferences.jiraAutoSync, preferences.jiraSyncInterval) ]); } /** * Remet à zéro toutes les préférences */ async resetAllPreferences(): Promise { try { const userPrefs = await this.getOrCreateUserPreferences(); await prisma.userPreferences.update({ where: { id: userPrefs.id }, data: { kanbanFilters: DEFAULT_PREFERENCES.kanbanFilters, viewPreferences: DEFAULT_PREFERENCES.viewPreferences, columnVisibility: DEFAULT_PREFERENCES.columnVisibility, jiraConfig: DEFAULT_PREFERENCES.jiraConfig as any, // eslint-disable-line @typescript-eslint/no-explicit-any } }); } catch (error) { console.warn('Erreur lors de la remise à zéro des préférences:', error); throw error; } } // === MÉTHODES UTILITAIRES === /** * Met à jour partiellement les filtres Kanban */ async updateKanbanFilters(updates: Partial): Promise { const current = await this.getKanbanFilters(); await this.saveKanbanFilters({ ...current, ...updates }); } /** * Met à jour partiellement les préférences de vue */ async updateViewPreferences(updates: Partial): Promise { const current = await this.getViewPreferences(); await this.saveViewPreferences({ ...current, ...updates }); } /** * Met à jour la visibilité d'une colonne spécifique */ async toggleColumnVisibility(status: TaskStatus): Promise { const current = await this.getColumnVisibility(); const hiddenStatuses = new Set(current.hiddenStatuses); if (hiddenStatuses.has(status)) { hiddenStatuses.delete(status); } else { hiddenStatuses.add(status); } await this.saveColumnVisibility({ hiddenStatuses: Array.from(hiddenStatuses) }); } } // Export de l'instance singleton export const userPreferencesService = new UserPreferencesService();