feat: jira config in database
This commit is contained in:
46
clients/jira-config-client.ts
Normal file
46
clients/jira-config-client.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { httpClient } from './base/http-client';
|
||||||
|
import { JiraConfig } from '@/lib/types';
|
||||||
|
|
||||||
|
export interface JiraConfigResponse {
|
||||||
|
jiraConfig: JiraConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveJiraConfigRequest {
|
||||||
|
baseUrl: string;
|
||||||
|
email: string;
|
||||||
|
apiToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveJiraConfigResponse {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
jiraConfig: JiraConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
class JiraConfigClient {
|
||||||
|
private readonly basePath = '/user-preferences/jira-config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère la configuration Jira actuelle
|
||||||
|
*/
|
||||||
|
async getJiraConfig(): Promise<JiraConfig> {
|
||||||
|
const response = await httpClient.get<JiraConfigResponse>(this.basePath);
|
||||||
|
return response.jiraConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sauvegarde la configuration Jira
|
||||||
|
*/
|
||||||
|
async saveJiraConfig(config: SaveJiraConfigRequest): Promise<SaveJiraConfigResponse> {
|
||||||
|
return httpClient.put<SaveJiraConfigResponse>(this.basePath, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprime la configuration Jira (remet à zéro)
|
||||||
|
*/
|
||||||
|
async deleteJiraConfig(): Promise<{ success: boolean; message: string }> {
|
||||||
|
return httpClient.delete(this.basePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jiraConfigClient = new JiraConfigClient();
|
||||||
@@ -1,46 +1,100 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Badge } from '@/components/ui/Badge';
|
import { Badge } from '@/components/ui/Badge';
|
||||||
import { AppConfig } from '@/lib/config';
|
import { useJiraConfig } from '@/hooks/useJiraConfig';
|
||||||
|
|
||||||
interface JiraConfigFormProps {
|
export function JiraConfigForm() {
|
||||||
config: AppConfig;
|
const { config, isLoading: configLoading, saveConfig, deleteConfig } = useJiraConfig();
|
||||||
}
|
|
||||||
|
|
||||||
export function JiraConfigForm({ config }: JiraConfigFormProps) {
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
email: '',
|
email: '',
|
||||||
apiToken: ''
|
apiToken: ''
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
|
||||||
|
|
||||||
|
// Charger les données existantes dans le formulaire
|
||||||
|
useEffect(() => {
|
||||||
|
if (config) {
|
||||||
|
setFormData({
|
||||||
|
baseUrl: config.baseUrl || '',
|
||||||
|
email: config.email || '',
|
||||||
|
apiToken: config.apiToken || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsSubmitting(true);
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Note: Dans un vrai environnement, ces variables seraient configurées côté serveur
|
const result = await saveConfig(formData);
|
||||||
// Pour cette démo, on affiche juste un message informatif
|
|
||||||
setMessage({
|
if (result.success) {
|
||||||
type: 'success',
|
setMessage({
|
||||||
text: 'Configuration sauvegardée. Redémarrez l'application pour appliquer les changements.'
|
type: 'success',
|
||||||
});
|
text: result.message
|
||||||
} catch {
|
});
|
||||||
|
} else {
|
||||||
|
setMessage({
|
||||||
|
type: 'error',
|
||||||
|
text: result.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
setMessage({
|
setMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: 'Erreur lors de la sauvegarde de la configuration'
|
text: error instanceof Error ? error.message : 'Erreur lors de la sauvegarde de la configuration'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isJiraConfigured = config.integrations.jira.enabled;
|
const handleDelete = async () => {
|
||||||
|
if (!confirm('Êtes-vous sûr de vouloir supprimer la configuration Jira ?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setMessage(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await deleteConfig();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
setFormData({
|
||||||
|
baseUrl: '',
|
||||||
|
email: '',
|
||||||
|
apiToken: ''
|
||||||
|
});
|
||||||
|
setMessage({
|
||||||
|
type: 'success',
|
||||||
|
text: result.message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setMessage({
|
||||||
|
type: 'error',
|
||||||
|
text: result.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setMessage({
|
||||||
|
type: 'error',
|
||||||
|
text: error instanceof Error ? error.message : 'Erreur lors de la suppression de la configuration'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isJiraConfigured = config?.enabled && (config?.baseUrl || config?.email);
|
||||||
|
const isLoading = configLoading || isSubmitting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -67,19 +121,19 @@ export function JiraConfigForm({ config }: JiraConfigFormProps) {
|
|||||||
<div>
|
<div>
|
||||||
<span className="text-[var(--muted-foreground)]">URL de base:</span>{' '}
|
<span className="text-[var(--muted-foreground)]">URL de base:</span>{' '}
|
||||||
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
|
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
|
||||||
{config.integrations.jira.baseUrl || 'Non définie'}
|
{config?.baseUrl || 'Non définie'}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-[var(--muted-foreground)]">Email:</span>{' '}
|
<span className="text-[var(--muted-foreground)]">Email:</span>{' '}
|
||||||
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
|
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
|
||||||
{config.integrations.jira.email || 'Non défini'}
|
{config?.email || 'Non défini'}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-[var(--muted-foreground)]">Token API:</span>{' '}
|
<span className="text-[var(--muted-foreground)]">Token API:</span>{' '}
|
||||||
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
|
<code className="bg-[var(--background)] px-2 py-1 rounded text-xs">
|
||||||
{config.integrations.jira.apiToken ? '••••••••' : 'Non défini'}
|
{config?.apiToken ? '••••••••' : 'Non défini'}
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,13 +201,27 @@ export function JiraConfigForm({ config }: JiraConfigFormProps) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<div className="flex gap-3">
|
||||||
type="submit"
|
<Button
|
||||||
disabled={isLoading}
|
type="submit"
|
||||||
className="w-full"
|
disabled={isLoading}
|
||||||
>
|
className="flex-1"
|
||||||
{isLoading ? 'Sauvegarde...' : 'Sauvegarder la configuration'}
|
>
|
||||||
</Button>
|
{isLoading ? 'Sauvegarde...' : 'Sauvegarder la configuration'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isJiraConfigured && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="px-6"
|
||||||
|
>
|
||||||
|
Supprimer
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
|||||||
import { JiraConfigForm } from '@/components/settings/JiraConfigForm';
|
import { JiraConfigForm } from '@/components/settings/JiraConfigForm';
|
||||||
import { JiraSync } from '@/components/jira/JiraSync';
|
import { JiraSync } from '@/components/jira/JiraSync';
|
||||||
import { JiraLogs } from '@/components/jira/JiraLogs';
|
import { JiraLogs } from '@/components/jira/JiraLogs';
|
||||||
import { AppConfig } from '@/lib/config';
|
import { useJiraConfig } from '@/hooks/useJiraConfig';
|
||||||
|
|
||||||
interface SettingsPageClientProps {
|
export function SettingsPageClient() {
|
||||||
config: AppConfig;
|
const { config: jiraConfig } = useJiraConfig();
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsPageClient({ config }: SettingsPageClientProps) {
|
|
||||||
const [activeTab, setActiveTab] = useState<'general' | 'integrations' | 'advanced'>('general');
|
const [activeTab, setActiveTab] = useState<'general' | 'integrations' | 'advanced'>('general');
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
@@ -97,14 +94,14 @@ export function SettingsPageClient({ config }: SettingsPageClientProps) {
|
|||||||
</p>
|
</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<JiraConfigForm config={config} />
|
<JiraConfigForm />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Colonne 2: Actions et Logs */}
|
{/* Colonne 2: Actions et Logs */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{config.integrations.jira.enabled && (
|
{jiraConfig?.enabled && (
|
||||||
<>
|
<>
|
||||||
<JiraSync />
|
<JiraSync />
|
||||||
<JiraLogs />
|
<JiraLogs />
|
||||||
|
|||||||
80
hooks/useJiraConfig.ts
Normal file
80
hooks/useJiraConfig.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { jiraConfigClient, SaveJiraConfigRequest } from '@/clients/jira-config-client';
|
||||||
|
import { JiraConfig } from '@/lib/types';
|
||||||
|
|
||||||
|
export function useJiraConfig() {
|
||||||
|
const [config, setConfig] = useState<JiraConfig | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Charger la config au montage
|
||||||
|
useEffect(() => {
|
||||||
|
loadConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadConfig = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const jiraConfig = await jiraConfigClient.getJiraConfig();
|
||||||
|
setConfig(jiraConfig);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erreur lors du chargement de la config Jira:', err);
|
||||||
|
setError('Erreur lors du chargement de la configuration');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveConfig = async (configData: SaveJiraConfigRequest) => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
const response = await jiraConfigClient.saveJiraConfig(configData);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setConfig(response.jiraConfig);
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} else {
|
||||||
|
setError('Erreur lors de la sauvegarde');
|
||||||
|
return { success: false, message: 'Erreur lors de la sauvegarde' };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Erreur lors de la sauvegarde de la configuration';
|
||||||
|
setError(errorMessage);
|
||||||
|
return { success: false, message: errorMessage };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteConfig = async () => {
|
||||||
|
try {
|
||||||
|
setError(null);
|
||||||
|
const response = await jiraConfigClient.deleteJiraConfig();
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
setConfig({
|
||||||
|
baseUrl: '',
|
||||||
|
email: '',
|
||||||
|
apiToken: '',
|
||||||
|
enabled: false
|
||||||
|
});
|
||||||
|
return { success: true, message: response.message };
|
||||||
|
} else {
|
||||||
|
setError('Erreur lors de la suppression');
|
||||||
|
return { success: false, message: 'Erreur lors de la suppression' };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = err instanceof Error ? err.message : 'Erreur lors de la suppression de la configuration';
|
||||||
|
setError(errorMessage);
|
||||||
|
return { success: false, message: errorMessage };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
saveConfig,
|
||||||
|
deleteConfig,
|
||||||
|
refetch: loadConfig
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -81,6 +81,7 @@ export interface ColumnVisibility {
|
|||||||
export interface JiraConfig {
|
export interface JiraConfig {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
apiToken?: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "user_preferences" ADD COLUMN "jiraConfig" TEXT;
|
||||||
@@ -98,6 +98,9 @@ model UserPreferences {
|
|||||||
// Visibilité des colonnes (JSON)
|
// Visibilité des colonnes (JSON)
|
||||||
columnVisibility Json?
|
columnVisibility Json?
|
||||||
|
|
||||||
|
// Configuration Jira (JSON)
|
||||||
|
jiraConfig Json?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ const DEFAULT_PREFERENCES: UserPreferences = {
|
|||||||
hiddenStatuses: []
|
hiddenStatuses: []
|
||||||
},
|
},
|
||||||
jiraConfig: {
|
jiraConfig: {
|
||||||
enabled: false
|
enabled: false,
|
||||||
|
baseUrl: '',
|
||||||
|
email: '',
|
||||||
|
apiToken: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ class UserPreferencesService {
|
|||||||
kanbanFilters: DEFAULT_PREFERENCES.kanbanFilters,
|
kanbanFilters: DEFAULT_PREFERENCES.kanbanFilters,
|
||||||
viewPreferences: DEFAULT_PREFERENCES.viewPreferences,
|
viewPreferences: DEFAULT_PREFERENCES.viewPreferences,
|
||||||
columnVisibility: DEFAULT_PREFERENCES.columnVisibility,
|
columnVisibility: DEFAULT_PREFERENCES.columnVisibility,
|
||||||
|
jiraConfig: DEFAULT_PREFERENCES.jiraConfig as any,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,15 +169,43 @@ class UserPreferencesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === CONFIGURATION JIRA ===
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère la configuration Jira depuis les variables d'environnement
|
* Sauvegarde la configuration Jira
|
||||||
|
*/
|
||||||
|
async saveJiraConfig(config: JiraConfig): Promise<void> {
|
||||||
|
try {
|
||||||
|
const userPrefs = await this.getOrCreateUserPreferences();
|
||||||
|
await prisma.userPreferences.update({
|
||||||
|
where: { id: userPrefs.id },
|
||||||
|
data: { jiraConfig: config as 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<JiraConfig> {
|
async getJiraConfig(): Promise<JiraConfig> {
|
||||||
try {
|
try {
|
||||||
|
const userPrefs = await this.getOrCreateUserPreferences();
|
||||||
|
const dbConfig = (userPrefs as any).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();
|
const config = getConfig();
|
||||||
return {
|
return {
|
||||||
baseUrl: config.integrations.jira.baseUrl,
|
baseUrl: config.integrations.jira.baseUrl,
|
||||||
email: config.integrations.jira.email,
|
email: config.integrations.jira.email,
|
||||||
|
apiToken: '', // On ne retourne pas le token des env vars pour la sécurité
|
||||||
enabled: config.integrations.jira.enabled
|
enabled: config.integrations.jira.enabled
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -202,14 +234,14 @@ class UserPreferencesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sauvegarde toutes les préférences utilisateur (jiraConfig ignorée car elle vient des env vars)
|
* Sauvegarde toutes les préférences utilisateur
|
||||||
*/
|
*/
|
||||||
async saveAllPreferences(preferences: UserPreferences): Promise<void> {
|
async saveAllPreferences(preferences: UserPreferences): Promise<void> {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.saveKanbanFilters(preferences.kanbanFilters),
|
this.saveKanbanFilters(preferences.kanbanFilters),
|
||||||
this.saveViewPreferences(preferences.viewPreferences),
|
this.saveViewPreferences(preferences.viewPreferences),
|
||||||
this.saveColumnVisibility(preferences.columnVisibility)
|
this.saveColumnVisibility(preferences.columnVisibility),
|
||||||
// jiraConfig n'est pas sauvegardée car elle vient des variables d'environnement
|
this.saveJiraConfig(preferences.jiraConfig)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +257,7 @@ class UserPreferencesService {
|
|||||||
kanbanFilters: DEFAULT_PREFERENCES.kanbanFilters,
|
kanbanFilters: DEFAULT_PREFERENCES.kanbanFilters,
|
||||||
viewPreferences: DEFAULT_PREFERENCES.viewPreferences,
|
viewPreferences: DEFAULT_PREFERENCES.viewPreferences,
|
||||||
columnVisibility: DEFAULT_PREFERENCES.columnVisibility,
|
columnVisibility: DEFAULT_PREFERENCES.columnVisibility,
|
||||||
|
jiraConfig: DEFAULT_PREFERENCES.jiraConfig as any,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
110
src/app/api/user-preferences/jira-config/route.ts
Normal file
110
src/app/api/user-preferences/jira-config/route.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { userPreferencesService } from '@/services/user-preferences';
|
||||||
|
import { JiraConfig } from '@/lib/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/user-preferences/jira-config
|
||||||
|
* Récupère la configuration Jira
|
||||||
|
*/
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const jiraConfig = await userPreferencesService.getJiraConfig();
|
||||||
|
return NextResponse.json({ jiraConfig });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération de la config Jira:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Erreur lors de la récupération de la configuration Jira' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /api/user-preferences/jira-config
|
||||||
|
* Sauvegarde la configuration Jira
|
||||||
|
*/
|
||||||
|
export async function PUT(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { baseUrl, email, apiToken } = body;
|
||||||
|
|
||||||
|
// Validation des données requises
|
||||||
|
if (!baseUrl || !email || !apiToken) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'baseUrl, email et apiToken sont requis' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation du format URL
|
||||||
|
try {
|
||||||
|
new URL(baseUrl);
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'baseUrl doit être une URL valide' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation du format email
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'email doit être un email valide' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jiraConfig: JiraConfig = {
|
||||||
|
baseUrl: baseUrl.trim(),
|
||||||
|
email: email.trim(),
|
||||||
|
apiToken: apiToken.trim(),
|
||||||
|
enabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
await userPreferencesService.saveJiraConfig(jiraConfig);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Configuration Jira sauvegardée avec succès',
|
||||||
|
jiraConfig: {
|
||||||
|
...jiraConfig,
|
||||||
|
apiToken: '••••••••' // Masquer le token dans la réponse
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la sauvegarde de la config Jira:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Erreur lors de la sauvegarde de la configuration Jira' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE /api/user-preferences/jira-config
|
||||||
|
* Supprime la configuration Jira (remet à zéro)
|
||||||
|
*/
|
||||||
|
export async function DELETE() {
|
||||||
|
try {
|
||||||
|
const defaultConfig: JiraConfig = {
|
||||||
|
baseUrl: '',
|
||||||
|
email: '',
|
||||||
|
apiToken: '',
|
||||||
|
enabled: false
|
||||||
|
};
|
||||||
|
|
||||||
|
await userPreferencesService.saveJiraConfig(defaultConfig);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Configuration Jira réinitialisée avec succès'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la suppression de la config Jira:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Erreur lors de la suppression de la configuration Jira' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
import { SettingsPageClient } from '@/components/settings/SettingsPageClient';
|
import { SettingsPageClient } from '@/components/settings/SettingsPageClient';
|
||||||
import { getConfig } from '@/lib/config';
|
|
||||||
|
|
||||||
// Force dynamic rendering (no static generation)
|
// Force dynamic rendering (no static generation)
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
export default async function SettingsPage() {
|
export default async function SettingsPage() {
|
||||||
const config = getConfig();
|
return <SettingsPageClient />;
|
||||||
|
|
||||||
return (
|
|
||||||
<SettingsPageClient
|
|
||||||
config={config}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user