feat(Tags): implement user-specific tag management and enhance related services
- Added ownerId field to Tag model to associate tags with users. - Updated tagsService methods to enforce user ownership in tag operations. - Enhanced API routes to include user authentication and ownership checks for tag retrieval and management. - Modified seeding script to assign tags to the first user found in the database. - Updated various components and services to ensure user-specific tag handling throughout the application.
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
-- Migration pour ajouter ownerId aux tags
|
||||||
|
-- Les tags existants seront assignés au premier utilisateur
|
||||||
|
-- Cette version préserve les relations TaskTag existantes
|
||||||
|
|
||||||
|
-- Étape 1: Ajouter la colonne ownerId temporairement nullable
|
||||||
|
ALTER TABLE "tags" ADD COLUMN "ownerId" TEXT;
|
||||||
|
|
||||||
|
-- Étape 2: Assigner tous les tags existants au premier utilisateur
|
||||||
|
UPDATE "tags"
|
||||||
|
SET "ownerId" = (
|
||||||
|
SELECT "id" FROM "users"
|
||||||
|
ORDER BY "createdAt" ASC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE "ownerId" IS NULL;
|
||||||
|
|
||||||
|
-- Étape 3: Sauvegarder les relations TaskTag existantes avec les noms des tags
|
||||||
|
CREATE TEMPORARY TABLE "temp_task_tag_names" AS
|
||||||
|
SELECT tt."taskId", t."name" as "tagName"
|
||||||
|
FROM "task_tags" tt
|
||||||
|
JOIN "tags" t ON tt."tagId" = t."id";
|
||||||
|
|
||||||
|
-- Étape 4: Supprimer les anciennes relations TaskTag
|
||||||
|
DELETE FROM "task_tags";
|
||||||
|
|
||||||
|
-- Étape 5: Créer la nouvelle table avec ownerId non-nullable
|
||||||
|
CREATE TABLE "new_tags" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"color" TEXT NOT NULL DEFAULT '#6b7280',
|
||||||
|
"isPinned" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"ownerId" TEXT NOT NULL,
|
||||||
|
CONSTRAINT "new_tags_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Étape 6: Copier les données des tags
|
||||||
|
INSERT INTO "new_tags" ("id", "name", "color", "isPinned", "ownerId")
|
||||||
|
SELECT "id", "name", "color", "isPinned", "ownerId" FROM "tags";
|
||||||
|
|
||||||
|
-- Étape 7: Supprimer l'ancienne table
|
||||||
|
DROP TABLE "tags";
|
||||||
|
|
||||||
|
-- Étape 8: Renommer la nouvelle table
|
||||||
|
ALTER TABLE "new_tags" RENAME TO "tags";
|
||||||
|
|
||||||
|
-- Étape 9: Créer l'index unique pour (name, ownerId)
|
||||||
|
CREATE UNIQUE INDEX "tags_name_ownerId_key" ON "tags"("name", "ownerId");
|
||||||
|
|
||||||
|
-- Étape 10: Restaurer les relations TaskTag en utilisant les noms des tags
|
||||||
|
INSERT INTO "task_tags" ("taskId", "tagId")
|
||||||
|
SELECT tt."taskId", t."id" as "tagId"
|
||||||
|
FROM "temp_task_tag_names" tt
|
||||||
|
JOIN "tags" t ON tt."tagName" = t."name"
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM "tasks" WHERE "tasks"."id" = tt."taskId"
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Étape 11: Nettoyer la table temporaire
|
||||||
|
DROP TABLE "temp_task_tag_names";
|
||||||
@@ -24,6 +24,7 @@ model User {
|
|||||||
notes Note[]
|
notes Note[]
|
||||||
dailyCheckboxes DailyCheckbox[]
|
dailyCheckboxes DailyCheckbox[]
|
||||||
tasks Task[] @relation("TaskOwner")
|
tasks Task[] @relation("TaskOwner")
|
||||||
|
tags Tag[] @relation("TagOwner")
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
@@ -63,13 +64,16 @@ model Task {
|
|||||||
|
|
||||||
model Tag {
|
model Tag {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String
|
||||||
color String @default("#6b7280")
|
color String @default("#6b7280")
|
||||||
isPinned Boolean @default(false)
|
isPinned Boolean @default(false)
|
||||||
|
ownerId String // Chaque tag appartient à un utilisateur
|
||||||
|
owner User @relation("TagOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
||||||
taskTags TaskTag[]
|
taskTags TaskTag[]
|
||||||
primaryTasks Task[] @relation("PrimaryTag")
|
primaryTasks Task[] @relation("PrimaryTag")
|
||||||
noteTags NoteTag[]
|
noteTags NoteTag[]
|
||||||
|
|
||||||
|
@@unique([name, ownerId]) // Un nom de tag unique par utilisateur
|
||||||
@@map("tags")
|
@@map("tags")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
import { tagsService } from '../src/services/task-management/tags';
|
import { tagsService } from '../src/services/task-management/tags';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
async function seedTags() {
|
async function seedTags() {
|
||||||
console.log('🏷️ Création des tags de test...');
|
console.log('🌱 Début du seeding des tags...');
|
||||||
|
|
||||||
|
// Récupérer le premier utilisateur pour assigner les tags
|
||||||
|
const firstUser = await prisma.user.findFirst({
|
||||||
|
orderBy: { createdAt: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!firstUser) {
|
||||||
|
console.log("❌ Aucun utilisateur trouvé. Créez d'abord un utilisateur.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`👤 Assignation des tags à: ${firstUser.email}`);
|
||||||
|
|
||||||
const testTags = [
|
const testTags = [
|
||||||
{ name: 'frontend', color: '#3B82F6' },
|
{ name: 'frontend', color: '#3B82F6' },
|
||||||
@@ -19,9 +34,15 @@ async function seedTags() {
|
|||||||
|
|
||||||
for (const tagData of testTags) {
|
for (const tagData of testTags) {
|
||||||
try {
|
try {
|
||||||
const existing = await tagsService.getTagByName(tagData.name);
|
const existing = await tagsService.getTagByName(
|
||||||
|
tagData.name,
|
||||||
|
firstUser.id
|
||||||
|
);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
const tag = await tagsService.createTag(tagData);
|
const tag = await tagsService.createTag({
|
||||||
|
...tagData,
|
||||||
|
userId: firstUser.id,
|
||||||
|
});
|
||||||
console.log(`✅ Tag créé: ${tag.name} (${tag.color})`);
|
console.log(`✅ Tag créé: ${tag.name} (${tag.color})`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`⚠️ Tag existe déjà: ${tagData.name}`);
|
console.log(`⚠️ Tag existe déjà: ${tagData.name}`);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { tagsService } from '@/services/task-management/tags';
|
import { tagsService } from '@/services/task-management/tags';
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import { Tag } from '@/lib/types';
|
import { Tag } from '@/lib/types';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
|
||||||
export type ActionResult<T = void> = {
|
export type ActionResult<T = void> = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -18,7 +20,16 @@ export async function createTag(
|
|||||||
color: string
|
color: string
|
||||||
): Promise<ActionResult<Tag>> {
|
): Promise<ActionResult<Tag>> {
|
||||||
try {
|
try {
|
||||||
const tag = await tagsService.createTag({ name, color });
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return { success: false, error: 'User not authenticated' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = await tagsService.createTag({
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
userId: session.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
// Revalider les pages qui utilisent les tags
|
// Revalider les pages qui utilisent les tags
|
||||||
revalidatePath('/');
|
revalidatePath('/');
|
||||||
@@ -43,7 +54,12 @@ export async function updateTag(
|
|||||||
data: { name?: string; color?: string; isPinned?: boolean }
|
data: { name?: string; color?: string; isPinned?: boolean }
|
||||||
): Promise<ActionResult<Tag>> {
|
): Promise<ActionResult<Tag>> {
|
||||||
try {
|
try {
|
||||||
const tag = await tagsService.updateTag(tagId, data);
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return { success: false, error: 'User not authenticated' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = await tagsService.updateTag(tagId, session.user.id, data);
|
||||||
|
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
return { success: false, error: 'Tag non trouvé' };
|
return { success: false, error: 'Tag non trouvé' };
|
||||||
@@ -69,7 +85,12 @@ export async function updateTag(
|
|||||||
*/
|
*/
|
||||||
export async function deleteTag(tagId: string): Promise<ActionResult> {
|
export async function deleteTag(tagId: string): Promise<ActionResult> {
|
||||||
try {
|
try {
|
||||||
await tagsService.deleteTag(tagId);
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return { success: false, error: 'User not authenticated' };
|
||||||
|
}
|
||||||
|
|
||||||
|
await tagsService.deleteTag(tagId, session.user.id);
|
||||||
|
|
||||||
// Revalider les pages qui utilisent les tags
|
// Revalider les pages qui utilisent les tags
|
||||||
revalidatePath('/');
|
revalidatePath('/');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { tagsService } from '@/services/task-management/tags';
|
import { tagsService } from '@/services/task-management/tags';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/tags/[id] - Récupère un tag par son ID
|
* GET /api/tags/[id] - Récupère un tag par son ID
|
||||||
@@ -9,8 +11,14 @@ export async function GET(
|
|||||||
{ params }: { params: Promise<{ id: string }> }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
// Vérifier l'authentification
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const tag = await tagsService.getTagById(id);
|
const tag = await tagsService.getTagById(id, session.user.id);
|
||||||
|
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
return NextResponse.json({ error: 'Tag non trouvé' }, { status: 404 });
|
return NextResponse.json({ error: 'Tag non trouvé' }, { status: 404 });
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { tagsService } from '@/services/task-management/tags';
|
import { tagsService } from '@/services/task-management/tags';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/tags - Récupère tous les tags ou recherche par query
|
* GET /api/tags - Récupère tous les tags ou recherche par query
|
||||||
*/
|
*/
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
// Vérifier l'authentification
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const query = searchParams.get('q');
|
const query = searchParams.get('q');
|
||||||
const popular = searchParams.get('popular');
|
const popular = searchParams.get('popular');
|
||||||
@@ -14,14 +22,14 @@ export async function GET(request: NextRequest) {
|
|||||||
let tags;
|
let tags;
|
||||||
|
|
||||||
if (popular === 'true') {
|
if (popular === 'true') {
|
||||||
// Récupérer les tags les plus utilisés
|
// Récupérer les tags les plus utilisés pour cet utilisateur
|
||||||
tags = await tagsService.getPopularTags(limit);
|
tags = await tagsService.getPopularTags(session.user.id, limit);
|
||||||
} else if (query) {
|
} else if (query) {
|
||||||
// Recherche par nom (pour autocomplete)
|
// Recherche par nom (pour autocomplete) pour cet utilisateur
|
||||||
tags = await tagsService.searchTags(query, limit);
|
tags = await tagsService.searchTags(query, session.user.id, limit);
|
||||||
} else {
|
} else {
|
||||||
// Récupérer tous les tags
|
// Récupérer tous les tags de cet utilisateur
|
||||||
tags = await tagsService.getTags();
|
tags = await tagsService.getTags(session.user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default async function KanbanPage() {
|
|||||||
// SSR - Récupération des données côté serveur
|
// SSR - Récupération des données côté serveur
|
||||||
const [initialTasks, initialTags] = await Promise.all([
|
const [initialTasks, initialTags] = await Promise.all([
|
||||||
tasksService.getTasks(userId),
|
tasksService.getTasks(userId),
|
||||||
tagsService.getTags(),
|
tagsService.getTags(userId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default async function NotesPage() {
|
|||||||
|
|
||||||
// SSR - Récupération des données côté serveur
|
// SSR - Récupération des données côté serveur
|
||||||
const initialNotes = await notesService.getNotes(session.user.id);
|
const initialNotes = await notesService.getNotes(session.user.id);
|
||||||
const initialTags = await tagsService.getTags();
|
const initialTags = await tagsService.getTags(session.user.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotesPageClient initialNotes={initialNotes} initialTags={initialTags} />
|
<NotesPageClient initialNotes={initialNotes} initialTags={initialTags} />
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default async function HomePage() {
|
|||||||
tagMetrics,
|
tagMetrics,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
tasksService.getTasks(userId),
|
tasksService.getTasks(userId),
|
||||||
tagsService.getTags(),
|
tagsService.getTags(userId),
|
||||||
tasksService.getTaskStats(userId),
|
tasksService.getTaskStats(userId),
|
||||||
AnalyticsService.getProductivityMetrics(userId),
|
AnalyticsService.getProductivityMetrics(userId),
|
||||||
DeadlineAnalyticsService.getDeadlineMetrics(userId),
|
DeadlineAnalyticsService.getDeadlineMetrics(userId),
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default async function AdvancedSettingsPage() {
|
|||||||
// Fetch all data server-side
|
// Fetch all data server-side
|
||||||
const [taskStats, tags] = await Promise.all([
|
const [taskStats, tags] = await Promise.all([
|
||||||
tasksService.getTaskStats(userId),
|
tasksService.getTaskStats(userId),
|
||||||
tagsService.getTags(),
|
tagsService.getTags(userId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Compose backup data like the API does
|
// Compose backup data like the API does
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
import { tagsService } from '@/services/task-management/tags';
|
import { tagsService } from '@/services/task-management/tags';
|
||||||
import { GeneralSettingsPageClient } from '@/components/settings/GeneralSettingsPageClient';
|
import { GeneralSettingsPageClient } from '@/components/settings/GeneralSettingsPageClient';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
|
||||||
// Force dynamic rendering for real-time data
|
// Force dynamic rendering for real-time data
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
export default async function GeneralSettingsPage() {
|
export default async function GeneralSettingsPage() {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[var(--background)] flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-2xl font-bold text-[var(--foreground)] mb-2">
|
||||||
|
Accès non autorisé
|
||||||
|
</h1>
|
||||||
|
<p className="text-[var(--muted-foreground)]">
|
||||||
|
Vous devez être connecté pour accéder aux paramètres.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch data server-side
|
// Fetch data server-side
|
||||||
const tags = await tagsService.getTags();
|
const tags = await tagsService.getTags(session.user.id);
|
||||||
|
|
||||||
return <GeneralSettingsPageClient initialTags={tags} />;
|
return <GeneralSettingsPageClient initialTags={tags} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default async function WeeklyManagerPage() {
|
|||||||
const [summary, initialTasks, initialTags] = await Promise.all([
|
const [summary, initialTasks, initialTags] = await Promise.all([
|
||||||
ManagerSummaryService.getManagerSummary(userId),
|
ManagerSummaryService.getManagerSummary(userId),
|
||||||
tasksService.getTasks(userId),
|
tasksService.getTasks(userId),
|
||||||
tagsService.getTags(),
|
tagsService.getTags(userId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { JiraTask } from '@/lib/types';
|
import { JiraTask } from '@/lib/types';
|
||||||
import { prisma } from '@/services/core/database';
|
import { prisma } from '@/services/core/database';
|
||||||
import { parseDate, formatDateForDisplay } from '@/lib/date-utils';
|
import { parseDate, formatDateForDisplay } from '@/lib/date-utils';
|
||||||
|
import { tagsService } from '../../task-management/tags';
|
||||||
|
|
||||||
export interface JiraConfig {
|
export interface JiraConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -326,26 +327,13 @@ export class JiraService {
|
|||||||
/**
|
/**
|
||||||
* S'assure que le tag "🔗 From Jira" existe dans la base
|
* S'assure que le tag "🔗 From Jira" existe dans la base
|
||||||
*/
|
*/
|
||||||
private async ensureJiraTagExists(): Promise<void> {
|
private async ensureJiraTagExists(userId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const tagName = '🔗 From Jira';
|
const tagName = '🔗 From Jira';
|
||||||
|
|
||||||
// Vérifier si le tag existe déjà
|
// Utiliser le service tags pour créer ou récupérer le tag
|
||||||
const existingTag = await prisma.tag.findUnique({
|
await tagsService.ensureTagsExist([tagName], userId);
|
||||||
where: { name: tagName },
|
console.log(`✅ Tag "${tagName}" créé/récupéré automatiquement`);
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingTag) {
|
|
||||||
// Créer le tag s'il n'existe pas
|
|
||||||
await prisma.tag.create({
|
|
||||||
data: {
|
|
||||||
name: tagName,
|
|
||||||
color: '#0082C9', // Bleu Jira
|
|
||||||
isPinned: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(`✅ Tag "${tagName}" créé automatiquement`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors de la création du tag Jira:', error);
|
console.error('Erreur lors de la création du tag Jira:', error);
|
||||||
// Ne pas faire échouer la sync pour un problème de tag
|
// Ne pas faire échouer la sync pour un problème de tag
|
||||||
@@ -375,7 +363,7 @@ export class JiraService {
|
|||||||
this.unknownStatuses.clear();
|
this.unknownStatuses.clear();
|
||||||
|
|
||||||
// S'assurer que le tag "From Jira" existe
|
// S'assurer que le tag "From Jira" existe
|
||||||
await this.ensureJiraTagExists();
|
await this.ensureJiraTagExists(userId);
|
||||||
|
|
||||||
// Récupérer les tickets Jira actuellement assignés
|
// Récupérer les tickets Jira actuellement assignés
|
||||||
const jiraTasks = await this.getAssignedIssues();
|
const jiraTasks = await this.getAssignedIssues();
|
||||||
@@ -526,7 +514,7 @@ export class JiraService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Assigner le tag Jira
|
// Assigner le tag Jira
|
||||||
await this.assignJiraTag(newTask.id);
|
await this.assignJiraTag(newTask.id, userId);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`➕ Nouvelle tâche créée: ${jiraTask.key} (status: ${taskData.status})`
|
`➕ Nouvelle tâche créée: ${jiraTask.key} (status: ${taskData.status})`
|
||||||
@@ -598,7 +586,7 @@ export class JiraService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// S'assurer que le tag Jira est assigné (pour les anciennes tâches) même en skip
|
// S'assurer que le tag Jira est assigné (pour les anciennes tâches) même en skip
|
||||||
await this.assignJiraTag(existingTask.id);
|
await this.assignJiraTag(existingTask.id, userId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'skipped',
|
type: 'skipped',
|
||||||
@@ -647,7 +635,7 @@ export class JiraService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// S'assurer que le tag Jira est assigné (pour les anciennes tâches)
|
// S'assurer que le tag Jira est assigné (pour les anciennes tâches)
|
||||||
await this.assignJiraTag(existingTask.id);
|
await this.assignJiraTag(existingTask.id, userId);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`🔄 Tâche mise à jour (titre/priorité préservés): ${jiraTask.key} (${changes.length} changement${changes.length > 1 ? 's' : ''})`
|
`🔄 Tâche mise à jour (titre/priorité préservés): ${jiraTask.key} (${changes.length} changement${changes.length > 1 ? 's' : ''})`
|
||||||
@@ -756,14 +744,12 @@ export class JiraService {
|
|||||||
/**
|
/**
|
||||||
* Assigne le tag "🔗 From Jira" à une tâche si pas déjà assigné
|
* Assigne le tag "🔗 From Jira" à une tâche si pas déjà assigné
|
||||||
*/
|
*/
|
||||||
private async assignJiraTag(taskId: string): Promise<void> {
|
private async assignJiraTag(taskId: string, userId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const tagName = '🔗 From Jira';
|
const tagName = '🔗 From Jira';
|
||||||
|
|
||||||
// Récupérer le tag
|
// Utiliser le service tags pour récupérer le tag
|
||||||
const jiraTag = await prisma.tag.findUnique({
|
const jiraTag = await tagsService.getTagByName(tagName, userId);
|
||||||
where: { name: tagName },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!jiraTag) {
|
if (!jiraTag) {
|
||||||
console.warn(`⚠️ Tag "${tagName}" introuvable lors de l'assignation`);
|
console.warn(`⚠️ Tag "${tagName}" introuvable lors de l'assignation`);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { TfsPullRequest } from '@/lib/types';
|
|||||||
import { prisma } from '@/services/core/database';
|
import { prisma } from '@/services/core/database';
|
||||||
import { parseDate, formatDateForDisplay } from '@/lib/date-utils';
|
import { parseDate, formatDateForDisplay } from '@/lib/date-utils';
|
||||||
import { userPreferencesService } from '@/services/core/user-preferences';
|
import { userPreferencesService } from '@/services/core/user-preferences';
|
||||||
|
import { tagsService } from '../../task-management/tags';
|
||||||
|
|
||||||
export interface TfsConfig {
|
export interface TfsConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -574,7 +575,7 @@ export class TfsService {
|
|||||||
console.log('🔄 Début synchronisation TFS Pull Requests...');
|
console.log('🔄 Début synchronisation TFS Pull Requests...');
|
||||||
|
|
||||||
// S'assurer que le tag TFS existe
|
// S'assurer que le tag TFS existe
|
||||||
await this.ensureTfsTagExists();
|
await this.ensureTfsTagExists(userId);
|
||||||
|
|
||||||
// Récupérer toutes les PRs assignées à l'utilisateur
|
// Récupérer toutes les PRs assignées à l'utilisateur
|
||||||
const allPullRequests = await this.getMyPullRequests();
|
const allPullRequests = await this.getMyPullRequests();
|
||||||
@@ -665,7 +666,7 @@ export class TfsService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Assigner le tag TFS
|
// Assigner le tag TFS
|
||||||
await this.assignTfsTag(newTask.id);
|
await this.assignTfsTag(newTask.id, userId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'created',
|
type: 'created',
|
||||||
@@ -693,7 +694,7 @@ export class TfsService {
|
|||||||
|
|
||||||
if (changes.length === 0) {
|
if (changes.length === 0) {
|
||||||
// S'assurer que le tag TFS est assigné (pour les anciennes tâches)
|
// S'assurer que le tag TFS est assigné (pour les anciennes tâches)
|
||||||
await this.assignTfsTag(existingTask.id);
|
await this.assignTfsTag(existingTask.id, userId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'skipped',
|
type: 'skipped',
|
||||||
@@ -724,21 +725,11 @@ export class TfsService {
|
|||||||
/**
|
/**
|
||||||
* S'assure que le tag TFS existe
|
* S'assure que le tag TFS existe
|
||||||
*/
|
*/
|
||||||
private async ensureTfsTagExists(): Promise<void> {
|
private async ensureTfsTagExists(userId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const existingTag = await prisma.tag.findFirst({
|
// Utiliser le service tags pour créer ou récupérer le tag
|
||||||
where: { name: '🧑💻 TFS' },
|
await tagsService.ensureTagsExist(['🧑💻 TFS'], userId);
|
||||||
});
|
console.log('✅ Tag TFS créé/récupéré');
|
||||||
|
|
||||||
if (!existingTag) {
|
|
||||||
await prisma.tag.create({
|
|
||||||
data: {
|
|
||||||
name: '🧑💻 TFS',
|
|
||||||
color: '#0066cc', // Bleu Azure DevOps
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log('✅ Tag TFS créé');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Erreur création tag TFS:', error);
|
console.warn('Erreur création tag TFS:', error);
|
||||||
}
|
}
|
||||||
@@ -747,21 +738,11 @@ export class TfsService {
|
|||||||
/**
|
/**
|
||||||
* Assigne automatiquement le tag "TFS" aux tâches importées
|
* Assigne automatiquement le tag "TFS" aux tâches importées
|
||||||
*/
|
*/
|
||||||
private async assignTfsTag(taskId: string): Promise<void> {
|
private async assignTfsTag(taskId: string, userId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let tfsTag = await prisma.tag.findFirst({
|
// Utiliser le service tags pour créer ou récupérer le tag
|
||||||
where: { name: '🧑💻 TFS' },
|
const tags = await tagsService.ensureTagsExist(['🧑💻 TFS'], userId);
|
||||||
});
|
const tfsTag = tags[0];
|
||||||
|
|
||||||
if (!tfsTag) {
|
|
||||||
tfsTag = await prisma.tag.create({
|
|
||||||
data: {
|
|
||||||
name: '🧑💻 TFS',
|
|
||||||
color: '#0078d4', // Couleur Azure
|
|
||||||
isPinned: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier si la relation existe déjà
|
// Vérifier si la relation existe déjà
|
||||||
const existingRelation = await prisma.taskTag.findFirst({
|
const existingRelation = await prisma.taskTag.findFirst({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { prisma } from '@/services/core/database';
|
import { prisma } from '@/services/core/database';
|
||||||
import { Task } from '@/lib/types';
|
import { Task } from '@/lib/types';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { tagsService } from './task-management/tags';
|
||||||
|
|
||||||
export interface Note {
|
export interface Note {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -178,19 +179,38 @@ export class NotesService {
|
|||||||
content: data.content,
|
content: data.content,
|
||||||
userId: data.userId,
|
userId: data.userId,
|
||||||
taskId: data.taskId, // Ajouter le taskId
|
taskId: data.taskId, // Ajouter le taskId
|
||||||
noteTags: data.tags
|
},
|
||||||
? {
|
include: {
|
||||||
create: data.tags.map((tagName) => ({
|
task: {
|
||||||
tag: {
|
include: {
|
||||||
connectOrCreate: {
|
taskTags: {
|
||||||
where: { name: tagName },
|
include: {
|
||||||
create: { name: tagName },
|
tag: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
primaryTag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Créer les relations avec les tags si fournis
|
||||||
|
if (data.tags && data.tags.length > 0) {
|
||||||
|
// Créer ou récupérer tous les tags
|
||||||
|
const tags = await tagsService.ensureTagsExist(data.tags, data.userId);
|
||||||
|
|
||||||
|
// Créer les relations NoteTag
|
||||||
|
await prisma.noteTag.createMany({
|
||||||
|
data: tags.map((tag) => ({
|
||||||
|
noteId: note.id,
|
||||||
|
tagId: tag.id,
|
||||||
})),
|
})),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
: undefined,
|
|
||||||
},
|
// Récupérer la note avec les tags
|
||||||
|
const noteWithTags = await prisma.note.findUnique({
|
||||||
|
where: { id: note.id },
|
||||||
include: {
|
include: {
|
||||||
noteTags: {
|
noteTags: {
|
||||||
include: {
|
include: {
|
||||||
@@ -211,10 +231,10 @@ export class NotesService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...note,
|
...noteWithTags!,
|
||||||
taskId: note.taskId || undefined, // Convertir null en undefined
|
taskId: noteWithTags!.taskId || undefined, // Convertir null en undefined
|
||||||
task: this.mapPrismaTaskToTask(note.task), // Mapper correctement l'objet Task
|
task: this.mapPrismaTaskToTask(noteWithTags!.task), // Mapper correctement l'objet Task
|
||||||
tags: note.noteTags.map((nt) => nt.tag.name),
|
tags: noteWithTags!.noteTags.map((nt) => nt.tag.name),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,22 +292,34 @@ export class NotesService {
|
|||||||
|
|
||||||
// Gérer les tags si fournis
|
// Gérer les tags si fournis
|
||||||
if (data.tags !== undefined) {
|
if (data.tags !== undefined) {
|
||||||
updateData.noteTags = {
|
// Supprimer toutes les relations existantes
|
||||||
deleteMany: {}, // Supprimer tous les tags existants
|
await prisma.noteTag.deleteMany({
|
||||||
create: data.tags.map((tagName) => ({
|
where: { noteId: noteId },
|
||||||
tag: {
|
});
|
||||||
connectOrCreate: {
|
|
||||||
where: { name: tagName },
|
// Créer les nouvelles relations si des tags sont fournis
|
||||||
create: { name: tagName },
|
if (data.tags.length > 0) {
|
||||||
},
|
// Créer ou récupérer tous les tags
|
||||||
},
|
const tags = await tagsService.ensureTagsExist(data.tags, userId);
|
||||||
|
|
||||||
|
// Créer les relations NoteTag
|
||||||
|
await prisma.noteTag.createMany({
|
||||||
|
data: tags.map((tag) => ({
|
||||||
|
noteId: noteId,
|
||||||
|
tagId: tag.id,
|
||||||
})),
|
})),
|
||||||
};
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = await prisma.note.update({
|
await prisma.note.update({
|
||||||
|
where: { id: noteId },
|
||||||
|
data: updateData as Prisma.NoteUpdateInput,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Récupérer la note avec les tags après la mise à jour
|
||||||
|
const noteWithTags = await prisma.note.findUnique({
|
||||||
where: { id: noteId },
|
where: { id: noteId },
|
||||||
data: updateData,
|
|
||||||
include: {
|
include: {
|
||||||
noteTags: {
|
noteTags: {
|
||||||
include: {
|
include: {
|
||||||
@@ -308,10 +340,10 @@ export class NotesService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...note,
|
...noteWithTags!,
|
||||||
taskId: note.taskId || undefined, // Convertir null en undefined
|
taskId: noteWithTags!.taskId || undefined, // Convertir null en undefined
|
||||||
task: this.mapPrismaTaskToTask(note.task), // Mapper correctement l'objet Task
|
task: this.mapPrismaTaskToTask(noteWithTags!.task), // Mapper correctement l'objet Task
|
||||||
tags: note.noteTags.map((nt) => nt.tag.name),
|
tags: noteWithTags!.noteTags.map((nt) => nt.tag.name),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import { Tag } from '@/lib/types';
|
|||||||
*/
|
*/
|
||||||
export const tagsService = {
|
export const tagsService = {
|
||||||
/**
|
/**
|
||||||
* Récupère tous les tags avec leur nombre d'utilisations
|
* Récupère tous les tags d'un utilisateur avec leur nombre d'utilisations
|
||||||
*/
|
*/
|
||||||
async getTags(): Promise<(Tag & { usage: number })[]> {
|
async getTags(userId: string): Promise<(Tag & { usage: number })[]> {
|
||||||
const tags = await prisma.tag.findMany({
|
const tags = await prisma.tag.findMany({
|
||||||
|
where: { ownerId: userId },
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
select: {
|
select: {
|
||||||
@@ -31,11 +32,14 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère un tag par son ID
|
* Récupère un tag par son ID (vérifie que l'utilisateur en est propriétaire)
|
||||||
*/
|
*/
|
||||||
async getTagById(id: string): Promise<Tag | null> {
|
async getTagById(id: string, userId: string): Promise<Tag | null> {
|
||||||
const tag = await prisma.tag.findUnique({
|
const tag = await prisma.tag.findFirst({
|
||||||
where: { id },
|
where: {
|
||||||
|
id,
|
||||||
|
ownerId: userId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!tag) return null;
|
if (!tag) return null;
|
||||||
@@ -49,14 +53,15 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère un tag par son nom
|
* Récupère un tag par son nom pour un utilisateur spécifique
|
||||||
*/
|
*/
|
||||||
async getTagByName(name: string): Promise<Tag | null> {
|
async getTagByName(name: string, userId: string): Promise<Tag | null> {
|
||||||
const tag = await prisma.tag.findFirst({
|
const tag = await prisma.tag.findFirst({
|
||||||
where: {
|
where: {
|
||||||
name: {
|
name: {
|
||||||
equals: name,
|
equals: name,
|
||||||
},
|
},
|
||||||
|
ownerId: userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,15 +76,16 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crée un nouveau tag
|
* Crée un nouveau tag pour un utilisateur
|
||||||
*/
|
*/
|
||||||
async createTag(data: {
|
async createTag(data: {
|
||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
|
userId: string;
|
||||||
}): Promise<Tag> {
|
}): Promise<Tag> {
|
||||||
// Vérifier si le tag existe déjà
|
// Vérifier si le tag existe déjà pour cet utilisateur
|
||||||
const existing = await this.getTagByName(data.name);
|
const existing = await this.getTagByName(data.name, data.userId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
throw new Error(`Un tag avec le nom "${data.name}" existe déjà`);
|
throw new Error(`Un tag avec le nom "${data.name}" existe déjà`);
|
||||||
}
|
}
|
||||||
@@ -89,6 +95,7 @@ export const tagsService = {
|
|||||||
name: data.name.trim(),
|
name: data.name.trim(),
|
||||||
color: data.color,
|
color: data.color,
|
||||||
isPinned: data.isPinned || false,
|
isPinned: data.isPinned || false,
|
||||||
|
ownerId: data.userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,21 +108,24 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Met à jour un tag
|
* Met à jour un tag (vérifie que l'utilisateur en est propriétaire)
|
||||||
*/
|
*/
|
||||||
async updateTag(
|
async updateTag(
|
||||||
id: string,
|
id: string,
|
||||||
|
userId: string,
|
||||||
data: { name?: string; color?: string; isPinned?: boolean }
|
data: { name?: string; color?: string; isPinned?: boolean }
|
||||||
): Promise<Tag | null> {
|
): Promise<Tag | null> {
|
||||||
// Vérifier que le tag existe
|
// Vérifier que le tag existe et appartient à l'utilisateur
|
||||||
const existing = await this.getTagById(id);
|
const existing = await this.getTagById(id, userId);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
throw new Error(`Tag avec l'ID "${id}" non trouvé`);
|
throw new Error(
|
||||||
|
`Tag avec l'ID "${id}" non trouvé ou vous n'en êtes pas propriétaire`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si on change le nom, vérifier qu'il n'existe pas déjà
|
// Si on change le nom, vérifier qu'il n'existe pas déjà pour cet utilisateur
|
||||||
if (data.name && data.name !== existing.name) {
|
if (data.name && data.name !== existing.name) {
|
||||||
const nameExists = await this.getTagByName(data.name);
|
const nameExists = await this.getTagByName(data.name, userId);
|
||||||
if (nameExists && nameExists.id !== id) {
|
if (nameExists && nameExists.id !== id) {
|
||||||
throw new Error(`Un tag avec le nom "${data.name}" existe déjà`);
|
throw new Error(`Un tag avec le nom "${data.name}" existe déjà`);
|
||||||
}
|
}
|
||||||
@@ -150,13 +160,15 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supprime un tag et toutes ses relations
|
* Supprime un tag et toutes ses relations (vérifie que l'utilisateur en est propriétaire)
|
||||||
*/
|
*/
|
||||||
async deleteTag(id: string): Promise<void> {
|
async deleteTag(id: string, userId: string): Promise<void> {
|
||||||
// Vérifier que le tag existe
|
// Vérifier que le tag existe et appartient à l'utilisateur
|
||||||
const existing = await this.getTagById(id);
|
const existing = await this.getTagById(id, userId);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
throw new Error(`Tag avec l'ID "${id}" non trouvé`);
|
throw new Error(
|
||||||
|
`Tag avec l'ID "${id}" non trouvé ou vous n'en êtes pas propriétaire`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Supprimer d'abord toutes les relations TaskTag
|
// Supprimer d'abord toutes les relations TaskTag
|
||||||
@@ -171,9 +183,10 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les tags les plus utilisés
|
* Récupère les tags les plus utilisés pour un utilisateur
|
||||||
*/
|
*/
|
||||||
async getPopularTags(
|
async getPopularTags(
|
||||||
|
userId: string,
|
||||||
limit: number = 10
|
limit: number = 10
|
||||||
): Promise<Array<Tag & { usage: number }>> {
|
): Promise<Array<Tag & { usage: number }>> {
|
||||||
// Utiliser une requête SQL brute pour compter les usages
|
// Utiliser une requête SQL brute pour compter les usages
|
||||||
@@ -188,6 +201,7 @@ export const tagsService = {
|
|||||||
SELECT t.id, t.name, t.color, COUNT(tt.tagId) as usage
|
SELECT t.id, t.name, t.color, COUNT(tt.tagId) as usage
|
||||||
FROM tags t
|
FROM tags t
|
||||||
LEFT JOIN task_tags tt ON t.id = tt.tagId
|
LEFT JOIN task_tags tt ON t.id = tt.tagId
|
||||||
|
WHERE t.ownerId = ${userId}
|
||||||
GROUP BY t.id, t.name, t.color
|
GROUP BY t.id, t.name, t.color
|
||||||
ORDER BY usage DESC, t.name ASC
|
ORDER BY usage DESC, t.name ASC
|
||||||
LIMIT ${limit}
|
LIMIT ${limit}
|
||||||
@@ -202,14 +216,19 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recherche des tags par nom (pour autocomplete)
|
* Recherche des tags par nom pour un utilisateur (pour autocomplete)
|
||||||
*/
|
*/
|
||||||
async searchTags(query: string, limit: number = 10): Promise<Tag[]> {
|
async searchTags(
|
||||||
|
query: string,
|
||||||
|
userId: string,
|
||||||
|
limit: number = 10
|
||||||
|
): Promise<Tag[]> {
|
||||||
const tags = await prisma.tag.findMany({
|
const tags = await prisma.tag.findMany({
|
||||||
where: {
|
where: {
|
||||||
name: {
|
name: {
|
||||||
contains: query,
|
contains: query,
|
||||||
},
|
},
|
||||||
|
ownerId: userId,
|
||||||
},
|
},
|
||||||
orderBy: { name: 'asc' },
|
orderBy: { name: 'asc' },
|
||||||
take: limit,
|
take: limit,
|
||||||
@@ -224,15 +243,15 @@ export const tagsService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Crée automatiquement des tags manquants à partir d'une liste de noms
|
* Crée automatiquement des tags manquants à partir d'une liste de noms pour un utilisateur
|
||||||
*/
|
*/
|
||||||
async ensureTagsExist(tagNames: string[]): Promise<Tag[]> {
|
async ensureTagsExist(tagNames: string[], userId: string): Promise<Tag[]> {
|
||||||
const results: Tag[] = [];
|
const results: Tag[] = [];
|
||||||
|
|
||||||
for (const name of tagNames) {
|
for (const name of tagNames) {
|
||||||
if (!name.trim()) continue;
|
if (!name.trim()) continue;
|
||||||
|
|
||||||
let tag = await this.getTagByName(name.trim());
|
let tag = await this.getTagByName(name.trim(), userId);
|
||||||
|
|
||||||
if (!tag) {
|
if (!tag) {
|
||||||
// Générer une couleur aléatoirement
|
// Générer une couleur aléatoirement
|
||||||
@@ -251,6 +270,7 @@ export const tagsService = {
|
|||||||
tag = await this.createTag({
|
tag = await this.createTag({
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
color: randomColor,
|
color: randomColor,
|
||||||
|
userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from '@/lib/types';
|
} from '@/lib/types';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { getToday } from '@/lib/date-utils';
|
import { getToday } from '@/lib/date-utils';
|
||||||
import { generateTagColor } from '@/lib/tag-colors';
|
import { tagsService } from './tags';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service pour la gestion des tâches (version standalone)
|
* Service pour la gestion des tâches (version standalone)
|
||||||
@@ -112,7 +112,11 @@ export class TasksService {
|
|||||||
|
|
||||||
// Créer les relations avec les tags
|
// Créer les relations avec les tags
|
||||||
if (taskData.tags && taskData.tags.length > 0) {
|
if (taskData.tags && taskData.tags.length > 0) {
|
||||||
await this.createTaskTagRelations(task.id, taskData.tags);
|
await this.createTaskTagRelations(
|
||||||
|
task.id,
|
||||||
|
taskData.tags,
|
||||||
|
taskData.ownerId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer la tâche avec les tags pour le retour
|
// Récupérer la tâche avec les tags pour le retour
|
||||||
@@ -186,7 +190,7 @@ export class TasksService {
|
|||||||
|
|
||||||
// Mettre à jour les relations avec les tags
|
// Mettre à jour les relations avec les tags
|
||||||
if (updates.tags !== undefined) {
|
if (updates.tags !== undefined) {
|
||||||
await this.updateTaskTagRelations(taskId, updates.tags);
|
await this.updateTaskTagRelations(taskId, updates.tags, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupérer la tâche avec les tags pour le retour
|
// Récupérer la tâche avec les tags pour le retour
|
||||||
@@ -337,19 +341,14 @@ export class TasksService {
|
|||||||
*/
|
*/
|
||||||
private async createTaskTagRelations(
|
private async createTaskTagRelations(
|
||||||
taskId: string,
|
taskId: string,
|
||||||
tagNames: string[]
|
tagNames: string[],
|
||||||
|
userId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
for (const tagName of tagNames) {
|
for (const tagName of tagNames) {
|
||||||
try {
|
try {
|
||||||
// Créer ou récupérer le tag
|
// Utiliser le service tags pour créer ou récupérer le tag
|
||||||
const tag = await prisma.tag.upsert({
|
const tags = await tagsService.ensureTagsExist([tagName], userId);
|
||||||
where: { name: tagName },
|
const tag = tags[0];
|
||||||
update: {}, // Pas de mise à jour nécessaire
|
|
||||||
create: {
|
|
||||||
name: tagName,
|
|
||||||
color: this.generateTagColor(tagName),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Créer la relation TaskTag si elle n'existe pas
|
// Créer la relation TaskTag si elle n'existe pas
|
||||||
await prisma.taskTag.upsert({
|
await prisma.taskTag.upsert({
|
||||||
@@ -379,7 +378,8 @@ export class TasksService {
|
|||||||
*/
|
*/
|
||||||
private async updateTaskTagRelations(
|
private async updateTaskTagRelations(
|
||||||
taskId: string,
|
taskId: string,
|
||||||
tagNames: string[]
|
tagNames: string[],
|
||||||
|
userId: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Supprimer toutes les relations existantes
|
// Supprimer toutes les relations existantes
|
||||||
await prisma.taskTag.deleteMany({
|
await prisma.taskTag.deleteMany({
|
||||||
@@ -388,17 +388,10 @@ export class TasksService {
|
|||||||
|
|
||||||
// Créer les nouvelles relations
|
// Créer les nouvelles relations
|
||||||
if (tagNames.length > 0) {
|
if (tagNames.length > 0) {
|
||||||
await this.createTaskTagRelations(taskId, tagNames);
|
await this.createTaskTagRelations(taskId, tagNames, userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Génère une couleur pour un tag basée sur son nom
|
|
||||||
*/
|
|
||||||
private generateTagColor(tagName: string): string {
|
|
||||||
return generateTagColor(tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convertit une tâche Prisma en objet Task
|
* Convertit une tâche Prisma en objet Task
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user