feat: services database, reminders, taskprocessor init
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -10,10 +10,10 @@
|
|||||||
- [x] Setup Prisma ORM
|
- [x] Setup Prisma ORM
|
||||||
|
|
||||||
### 1.2 Architecture backend - Services de base
|
### 1.2 Architecture backend - Services de base
|
||||||
- [ ] Créer `services/database.ts` - Pool de connexion DB
|
- [x] Créer `services/database.ts` - Pool de connexion DB
|
||||||
- [ ] Créer `services/reminders.ts` - Service pour récupérer les rappels macOS
|
- [x] Créer `services/reminders.ts` - Service pour récupérer les rappels macOS
|
||||||
- [ ] Créer `lib/types.ts` - Types partagés (Task, Tag, Project, etc.)
|
- [x] Créer `lib/types.ts` - Types partagés (Task, Tag, Project, etc.)
|
||||||
- [ ] Créer `services/task-processor.ts` - Logique métier des tâches
|
- [x] Créer `services/task-processor.ts` - Logique métier des tâches
|
||||||
|
|
||||||
### 1.3 Intégration Rappels macOS (Focus principal Phase 1)
|
### 1.3 Intégration Rappels macOS (Focus principal Phase 1)
|
||||||
- [ ] Rechercher comment accéder aux rappels macOS en local (SQLite, AppleScript, ou API)
|
- [ ] Rechercher comment accéder aux rappels macOS en local (SQLite, AppleScript, ou API)
|
||||||
|
|||||||
64
services/database.ts
Normal file
64
services/database.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
// Singleton pattern pour Prisma Client
|
||||||
|
declare global {
|
||||||
|
var __prisma: PrismaClient | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer une instance unique de Prisma Client
|
||||||
|
export const prisma = globalThis.__prisma || new PrismaClient({
|
||||||
|
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// En développement, stocker l'instance globalement pour éviter les reconnexions
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
globalThis.__prisma = prisma;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour tester la connexion
|
||||||
|
export async function testDatabaseConnection(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await prisma.$connect();
|
||||||
|
console.log('✅ Database connection successful');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Database connection failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour fermer la connexion proprement
|
||||||
|
export async function closeDatabaseConnection(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
console.log('✅ Database connection closed');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error closing database connection:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction utilitaire pour les transactions
|
||||||
|
export async function withTransaction<T>(
|
||||||
|
callback: (tx: PrismaClient) => Promise<T>
|
||||||
|
): Promise<T> {
|
||||||
|
return await prisma.$transaction(async (tx) => {
|
||||||
|
return await callback(tx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour nettoyer la base (utile pour les tests)
|
||||||
|
export async function clearDatabase(): Promise<void> {
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
throw new Error('Cannot clear database in production');
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.taskTag.deleteMany();
|
||||||
|
await prisma.task.deleteMany();
|
||||||
|
await prisma.tag.deleteMany();
|
||||||
|
await prisma.syncLog.deleteMany();
|
||||||
|
|
||||||
|
console.log('🧹 Database cleared');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export par défaut
|
||||||
|
export default prisma;
|
||||||
198
services/reminders.ts
Normal file
198
services/reminders.ts
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import { exec } from 'child_process';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { MacOSReminder } from '@/lib/types';
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service pour récupérer les rappels macOS via AppleScript
|
||||||
|
* Approche sécurisée qui utilise l'API officielle d'Apple
|
||||||
|
*/
|
||||||
|
export class RemindersService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère tous les rappels depuis l'app Rappels macOS
|
||||||
|
*/
|
||||||
|
async getAllReminders(): Promise<MacOSReminder[]> {
|
||||||
|
try {
|
||||||
|
const script = `
|
||||||
|
tell application "Reminders"
|
||||||
|
set remindersList to {}
|
||||||
|
repeat with reminderList in lists
|
||||||
|
set listName to name of reminderList
|
||||||
|
repeat with reminder in reminders of reminderList
|
||||||
|
set reminderRecord to {id:(id of reminder as string), title:(name of reminder), notes:(body of reminder), completed:(completed of reminder), dueDate:missing value, completionDate:missing value, priority:(priority of reminder), list:listName}
|
||||||
|
|
||||||
|
-- Gérer la date d'échéance
|
||||||
|
try
|
||||||
|
set dueDate of reminderRecord to (due date of reminder as string)
|
||||||
|
end try
|
||||||
|
|
||||||
|
-- Gérer la date de completion
|
||||||
|
try
|
||||||
|
if completed of reminder then
|
||||||
|
set completionDate of reminderRecord to (completion date of reminder as string)
|
||||||
|
end if
|
||||||
|
end try
|
||||||
|
|
||||||
|
set end of remindersList to reminderRecord
|
||||||
|
end repeat
|
||||||
|
end repeat
|
||||||
|
|
||||||
|
return remindersList
|
||||||
|
end tell
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null || echo "[]"`);
|
||||||
|
|
||||||
|
return this.parseAppleScriptOutput(stdout);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des rappels:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les rappels d'une liste spécifique
|
||||||
|
*/
|
||||||
|
async getRemindersByList(listName: string): Promise<MacOSReminder[]> {
|
||||||
|
try {
|
||||||
|
const script = `
|
||||||
|
tell application "Reminders"
|
||||||
|
set remindersList to {}
|
||||||
|
set targetList to list "${listName}"
|
||||||
|
|
||||||
|
repeat with reminder in reminders of targetList
|
||||||
|
set reminderRecord to {id:(id of reminder as string), title:(name of reminder), notes:(body of reminder), completed:(completed of reminder), dueDate:missing value, completionDate:missing value, priority:(priority of reminder), list:"${listName}"}
|
||||||
|
|
||||||
|
-- Gérer la date d'échéance
|
||||||
|
try
|
||||||
|
set dueDate of reminderRecord to (due date of reminder as string)
|
||||||
|
end try
|
||||||
|
|
||||||
|
-- Gérer la date de completion
|
||||||
|
try
|
||||||
|
if completed of reminder then
|
||||||
|
set completionDate of reminderRecord to (completion date of reminder as string)
|
||||||
|
end if
|
||||||
|
end try
|
||||||
|
|
||||||
|
set end of remindersList to reminderRecord
|
||||||
|
end repeat
|
||||||
|
|
||||||
|
return remindersList
|
||||||
|
end tell
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null || echo "[]"`);
|
||||||
|
|
||||||
|
return this.parseAppleScriptOutput(stdout);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur lors de la récupération des rappels de la liste ${listName}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère la liste des listes de rappels
|
||||||
|
*/
|
||||||
|
async getReminderLists(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const script = `
|
||||||
|
tell application "Reminders"
|
||||||
|
set listNames to {}
|
||||||
|
repeat with reminderList in lists
|
||||||
|
set end of listNames to name of reminderList
|
||||||
|
end repeat
|
||||||
|
return listNames
|
||||||
|
end tell
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null || echo ""`);
|
||||||
|
|
||||||
|
// Parse la sortie AppleScript pour extraire les noms de listes
|
||||||
|
const lists = stdout.trim().split(', ').filter(list => list.length > 0);
|
||||||
|
return lists;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des listes:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test si l'app Rappels est accessible
|
||||||
|
*/
|
||||||
|
async testRemindersAccess(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const script = `
|
||||||
|
tell application "Reminders"
|
||||||
|
return count of lists
|
||||||
|
end tell
|
||||||
|
`;
|
||||||
|
|
||||||
|
await execAsync(`osascript -e '${script.replace(/'/g, "'\\''")}' 2>/dev/null`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Impossible d\'accéder à l\'app Rappels:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse la sortie AppleScript en objets MacOSReminder
|
||||||
|
*/
|
||||||
|
private parseAppleScriptOutput(output: string): MacOSReminder[] {
|
||||||
|
try {
|
||||||
|
// AppleScript retourne un format spécial, on doit le parser manuellement
|
||||||
|
// Pour l'instant, on retourne un tableau vide et on implémentera le parsing plus tard
|
||||||
|
console.log('Sortie AppleScript brute:', output);
|
||||||
|
|
||||||
|
// TODO: Implémenter le parsing complet de la sortie AppleScript
|
||||||
|
// Pour l'instant, on retourne des données de test
|
||||||
|
return this.getMockReminders();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du parsing AppleScript:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Données de test pour le développement
|
||||||
|
*/
|
||||||
|
private getMockReminders(): MacOSReminder[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'mock-1',
|
||||||
|
title: 'Finir le service reminders',
|
||||||
|
notes: 'Implémenter la récupération des rappels macOS',
|
||||||
|
completed: false,
|
||||||
|
dueDate: new Date('2025-01-16'),
|
||||||
|
priority: 5,
|
||||||
|
list: 'Travail',
|
||||||
|
tags: ['dev', 'backend']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-2',
|
||||||
|
title: 'Tester l\'intégration Jira',
|
||||||
|
notes: 'Configurer l\'API Jira pour récupérer les tâches',
|
||||||
|
completed: false,
|
||||||
|
dueDate: new Date('2025-01-18'),
|
||||||
|
priority: 9,
|
||||||
|
list: 'Projets',
|
||||||
|
tags: ['jira', 'api']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mock-3',
|
||||||
|
title: 'Créer le Kanban board',
|
||||||
|
completed: true,
|
||||||
|
completionDate: new Date('2025-01-10'),
|
||||||
|
priority: 5,
|
||||||
|
list: 'Travail',
|
||||||
|
tags: ['ui', 'frontend']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance singleton
|
||||||
|
export const remindersService = new RemindersService();
|
||||||
310
services/task-processor.ts
Normal file
310
services/task-processor.ts
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
import { prisma } from './database';
|
||||||
|
import { remindersService } from './reminders';
|
||||||
|
import { Task, TaskStatus, TaskPriority, MacOSReminder, SyncLog, BusinessError } from '@/lib/types';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service pour traiter et synchroniser les tâches
|
||||||
|
* Contient toute la logique métier pour les tâches
|
||||||
|
*/
|
||||||
|
export class TaskProcessorService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronise les rappels macOS avec la base de données
|
||||||
|
*/
|
||||||
|
async syncRemindersToDatabase(): Promise<SyncLog> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
let tasksSync = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Récupérer les rappels depuis macOS
|
||||||
|
const reminders = await remindersService.getAllReminders();
|
||||||
|
|
||||||
|
// Traiter chaque rappel
|
||||||
|
for (const reminder of reminders) {
|
||||||
|
await this.processReminder(reminder);
|
||||||
|
tasksSync++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le log de synchronisation
|
||||||
|
const syncLog = await prisma.syncLog.create({
|
||||||
|
data: {
|
||||||
|
source: 'reminders',
|
||||||
|
status: 'success',
|
||||||
|
message: `Synchronisé ${tasksSync} rappels en ${Date.now() - startTime}ms`,
|
||||||
|
tasksSync
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Sync reminders terminée: ${tasksSync} tâches`);
|
||||||
|
return syncLog;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Erreur inconnue';
|
||||||
|
|
||||||
|
const syncLog = await prisma.syncLog.create({
|
||||||
|
data: {
|
||||||
|
source: 'reminders',
|
||||||
|
status: 'error',
|
||||||
|
message: `Erreur de sync: ${errorMessage}`,
|
||||||
|
tasksSync
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.error('❌ Erreur sync reminders:', error);
|
||||||
|
return syncLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traite un rappel macOS et le sauvegarde/met à jour en base
|
||||||
|
*/
|
||||||
|
private async processReminder(reminder: MacOSReminder): Promise<void> {
|
||||||
|
const taskData = this.mapReminderToTask(reminder);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Upsert (insert ou update) de la tâche
|
||||||
|
await prisma.task.upsert({
|
||||||
|
where: {
|
||||||
|
source_sourceId: {
|
||||||
|
source: 'reminders',
|
||||||
|
sourceId: reminder.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
title: taskData.title,
|
||||||
|
description: taskData.description,
|
||||||
|
status: taskData.status,
|
||||||
|
priority: taskData.priority,
|
||||||
|
tagsJson: JSON.stringify(taskData.tags || []),
|
||||||
|
dueDate: taskData.dueDate,
|
||||||
|
completedAt: taskData.completedAt,
|
||||||
|
updatedAt: new Date()
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
title: taskData.title,
|
||||||
|
description: taskData.description,
|
||||||
|
status: taskData.status,
|
||||||
|
priority: taskData.priority,
|
||||||
|
source: 'reminders',
|
||||||
|
sourceId: reminder.id,
|
||||||
|
tagsJson: JSON.stringify(taskData.tags || []),
|
||||||
|
dueDate: taskData.dueDate,
|
||||||
|
completedAt: taskData.completedAt
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gérer les tags
|
||||||
|
if (taskData.tags && taskData.tags.length > 0) {
|
||||||
|
await this.processTags(taskData.tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur lors du traitement du rappel ${reminder.id}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit un rappel macOS en objet Task
|
||||||
|
*/
|
||||||
|
private mapReminderToTask(reminder: MacOSReminder): Partial<Task> {
|
||||||
|
return {
|
||||||
|
title: reminder.title,
|
||||||
|
description: reminder.notes || undefined,
|
||||||
|
status: this.mapReminderStatus(reminder),
|
||||||
|
priority: this.mapReminderPriority(reminder.priority),
|
||||||
|
tags: reminder.tags || [],
|
||||||
|
dueDate: reminder.dueDate || undefined,
|
||||||
|
completedAt: reminder.completionDate || undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit le statut d'un rappel macOS en TaskStatus
|
||||||
|
*/
|
||||||
|
private mapReminderStatus(reminder: MacOSReminder): TaskStatus {
|
||||||
|
if (reminder.completed) {
|
||||||
|
return 'done';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si la tâche a une date d'échéance passée, elle est en retard
|
||||||
|
if (reminder.dueDate && reminder.dueDate < new Date()) {
|
||||||
|
return 'todo'; // On garde 'todo' mais on pourrait ajouter un statut 'overdue'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'todo';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit la priorité macOS (0-9) en TaskPriority
|
||||||
|
*/
|
||||||
|
private mapReminderPriority(macosPriority: number): TaskPriority {
|
||||||
|
switch (macosPriority) {
|
||||||
|
case 0: return 'low';
|
||||||
|
case 1: return 'low';
|
||||||
|
case 5: return 'medium';
|
||||||
|
case 9: return 'high';
|
||||||
|
default: return 'medium';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traite et crée les tags s'ils n'existent pas
|
||||||
|
*/
|
||||||
|
private async processTags(tagNames: string[]): Promise<void> {
|
||||||
|
for (const tagName of tagNames) {
|
||||||
|
try {
|
||||||
|
await prisma.tag.upsert({
|
||||||
|
where: { name: tagName },
|
||||||
|
update: {}, // Pas de mise à jour nécessaire
|
||||||
|
create: {
|
||||||
|
name: tagName,
|
||||||
|
color: this.generateTagColor(tagName)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erreur lors de la création du tag ${tagName}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Génère une couleur pour un tag basée sur son nom
|
||||||
|
*/
|
||||||
|
private generateTagColor(tagName: string): string {
|
||||||
|
const colors = [
|
||||||
|
'#ef4444', '#f97316', '#f59e0b', '#eab308',
|
||||||
|
'#84cc16', '#22c55e', '#10b981', '#14b8a6',
|
||||||
|
'#06b6d4', '#0ea5e9', '#3b82f6', '#6366f1',
|
||||||
|
'#8b5cf6', '#a855f7', '#d946ef', '#ec4899'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Hash simple du nom pour choisir une couleur
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < tagName.length; i++) {
|
||||||
|
hash = tagName.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors[Math.abs(hash) % colors.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère toutes les tâches avec filtres optionnels
|
||||||
|
*/
|
||||||
|
async getTasks(filters?: {
|
||||||
|
status?: TaskStatus[];
|
||||||
|
source?: string[];
|
||||||
|
search?: string;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}): Promise<Task[]> {
|
||||||
|
const where: Prisma.TaskWhereInput = {};
|
||||||
|
|
||||||
|
if (filters?.status) {
|
||||||
|
where.status = { in: filters.status };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.source) {
|
||||||
|
where.source = { in: filters.source };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.search) {
|
||||||
|
where.OR = [
|
||||||
|
{ title: { contains: filters.search, mode: 'insensitive' } },
|
||||||
|
{ description: { contains: filters.search, mode: 'insensitive' } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const tasks = await prisma.task.findMany({
|
||||||
|
where,
|
||||||
|
take: filters?.limit || 100,
|
||||||
|
skip: filters?.offset || 0,
|
||||||
|
orderBy: [
|
||||||
|
{ completedAt: 'desc' },
|
||||||
|
{ dueDate: 'asc' },
|
||||||
|
{ createdAt: 'desc' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return tasks.map(this.mapPrismaTaskToTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le statut d'une tâche
|
||||||
|
*/
|
||||||
|
async updateTaskStatus(taskId: string, newStatus: TaskStatus): Promise<Task> {
|
||||||
|
const task = await prisma.task.findUnique({
|
||||||
|
where: { id: taskId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new BusinessError(`Tâche ${taskId} introuvable`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logique métier : si on marque comme terminé, on ajoute la date
|
||||||
|
const updateData: Prisma.TaskUpdateInput = {
|
||||||
|
status: newStatus,
|
||||||
|
updatedAt: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (newStatus === 'done' && !task.completedAt) {
|
||||||
|
updateData.completedAt = new Date();
|
||||||
|
} else if (newStatus !== 'done' && task.completedAt) {
|
||||||
|
updateData.completedAt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedTask = await prisma.task.update({
|
||||||
|
where: { id: taskId },
|
||||||
|
data: updateData
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapPrismaTaskToTask(updatedTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convertit une tâche Prisma en objet Task
|
||||||
|
*/
|
||||||
|
private mapPrismaTaskToTask(prismaTask: any): Task {
|
||||||
|
return {
|
||||||
|
id: prismaTask.id,
|
||||||
|
title: prismaTask.title,
|
||||||
|
description: prismaTask.description,
|
||||||
|
status: prismaTask.status as TaskStatus,
|
||||||
|
priority: prismaTask.priority as TaskPriority,
|
||||||
|
source: prismaTask.source,
|
||||||
|
sourceId: prismaTask.sourceId,
|
||||||
|
tags: JSON.parse(prismaTask.tagsJson || '[]'),
|
||||||
|
dueDate: prismaTask.dueDate,
|
||||||
|
completedAt: prismaTask.completedAt,
|
||||||
|
createdAt: prismaTask.createdAt,
|
||||||
|
updatedAt: prismaTask.updatedAt,
|
||||||
|
jiraProject: prismaTask.jiraProject,
|
||||||
|
jiraKey: prismaTask.jiraKey,
|
||||||
|
assignee: prismaTask.assignee
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les statistiques des tâches
|
||||||
|
*/
|
||||||
|
async getTaskStats() {
|
||||||
|
const [total, completed, inProgress, todo] = await Promise.all([
|
||||||
|
prisma.task.count(),
|
||||||
|
prisma.task.count({ where: { status: 'done' } }),
|
||||||
|
prisma.task.count({ where: { status: 'in_progress' } }),
|
||||||
|
prisma.task.count({ where: { status: 'todo' } })
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
completed,
|
||||||
|
inProgress,
|
||||||
|
todo,
|
||||||
|
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance singleton
|
||||||
|
export const taskProcessorService = new TaskProcessorService();
|
||||||
59
src/app/api/test/route.ts
Normal file
59
src/app/api/test/route.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { testDatabaseConnection } from '@/services/database';
|
||||||
|
import { remindersService } from '@/services/reminders';
|
||||||
|
import { taskProcessorService } from '@/services/task-processor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API route de test pour vérifier que tous les services fonctionnent
|
||||||
|
*/
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const results = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
database: false,
|
||||||
|
reminders: false,
|
||||||
|
taskProcessor: false,
|
||||||
|
reminderLists: [] as string[],
|
||||||
|
taskStats: null as any
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test de la base de données
|
||||||
|
try {
|
||||||
|
results.database = await testDatabaseConnection();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Test DB failed:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test de l'accès aux rappels
|
||||||
|
try {
|
||||||
|
results.reminders = await remindersService.testRemindersAccess();
|
||||||
|
if (results.reminders) {
|
||||||
|
results.reminderLists = await remindersService.getReminderLists();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Test Reminders failed:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test du service de traitement des tâches
|
||||||
|
try {
|
||||||
|
results.taskStats = await taskProcessorService.getTaskStats();
|
||||||
|
results.taskProcessor = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Test TaskProcessor failed:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Tests des services terminés',
|
||||||
|
results
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur dans l\'API de test:', error);
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"],
|
||||||
|
"@/services/*": ["./services/*"],
|
||||||
|
"@/lib/*": ["./lib/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user