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: [] } }; /** * 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 } }); return userPrefs; } // === 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; } } /** * Récupère toutes les préférences utilisateur */ async getAllPreferences(): Promise { const [kanbanFilters, viewPreferences, columnVisibility, jiraConfig] = await Promise.all([ this.getKanbanFilters(), this.getViewPreferences(), this.getColumnVisibility(), this.getJiraConfig() ]); return { kanbanFilters, viewPreferences, columnVisibility, jiraConfig }; } /** * 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) ]); } /** * 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();