create-crm-tmp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/bin/create-crm-tmp.js +93 -0
  2. package/package.json +25 -0
  3. package/template/.prettierignore +33 -0
  4. package/template/.prettierrc.json +25 -0
  5. package/template/README.md +173 -0
  6. package/template/eslint.config.mjs +18 -0
  7. package/template/exemple-contacts.csv +11 -0
  8. package/template/next.config.ts +8 -0
  9. package/template/package.json +64 -0
  10. package/template/postcss.config.mjs +7 -0
  11. package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
  12. package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
  13. package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
  14. package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
  15. package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
  16. package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
  17. package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
  18. package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
  19. package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
  20. package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
  21. package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
  22. package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
  23. package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
  24. package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
  25. package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
  26. package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
  27. package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
  28. package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
  29. package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
  30. package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
  31. package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
  32. package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
  33. package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
  34. package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
  35. package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
  36. package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
  37. package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
  38. package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
  39. package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
  40. package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
  41. package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
  42. package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
  43. package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
  44. package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
  45. package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
  46. package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
  47. package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
  48. package/template/prisma/migrations/migration_lock.toml +3 -0
  49. package/template/prisma/schema.prisma +582 -0
  50. package/template/prisma.config.ts +14 -0
  51. package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
  52. package/template/src/app/(auth)/layout.tsx +3 -0
  53. package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
  54. package/template/src/app/(auth)/reset-password/page.tsx +146 -0
  55. package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
  56. package/template/src/app/(auth)/signin/page.tsx +166 -0
  57. package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
  58. package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
  59. package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
  60. package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
  61. package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
  62. package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
  63. package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
  64. package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
  65. package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
  66. package/template/src/app/(dashboard)/layout.tsx +30 -0
  67. package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
  68. package/template/src/app/(dashboard)/templates/page.tsx +567 -0
  69. package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
  70. package/template/src/app/(dashboard)/users/page.tsx +457 -0
  71. package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
  72. package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
  73. package/template/src/app/api/audit-logs/route.ts +57 -0
  74. package/template/src/app/api/auth/[...all]/route.ts +4 -0
  75. package/template/src/app/api/auth/check-active/route.ts +31 -0
  76. package/template/src/app/api/auth/google/callback/route.ts +94 -0
  77. package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
  78. package/template/src/app/api/auth/google/route.ts +34 -0
  79. package/template/src/app/api/auth/google/status/route.ts +32 -0
  80. package/template/src/app/api/closing-reasons/route.ts +27 -0
  81. package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
  82. package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
  83. package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
  84. package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
  85. package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
  86. package/template/src/app/api/contacts/[id]/route.ts +322 -0
  87. package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
  88. package/template/src/app/api/contacts/export/route.ts +270 -0
  89. package/template/src/app/api/contacts/import/route.ts +381 -0
  90. package/template/src/app/api/contacts/route.ts +283 -0
  91. package/template/src/app/api/dashboard/stats/route.ts +299 -0
  92. package/template/src/app/api/email/track/[id]/route.ts +68 -0
  93. package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
  94. package/template/src/app/api/invite/complete/route.ts +88 -0
  95. package/template/src/app/api/invite/validate/route.ts +55 -0
  96. package/template/src/app/api/reminders/route.ts +95 -0
  97. package/template/src/app/api/reset-password/complete/route.ts +73 -0
  98. package/template/src/app/api/reset-password/request/route.ts +84 -0
  99. package/template/src/app/api/reset-password/validate/route.ts +49 -0
  100. package/template/src/app/api/reset-password/verify/route.ts +74 -0
  101. package/template/src/app/api/roles/[id]/route.ts +183 -0
  102. package/template/src/app/api/roles/route.ts +140 -0
  103. package/template/src/app/api/send/route.ts +282 -0
  104. package/template/src/app/api/settings/change-password/route.ts +95 -0
  105. package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
  106. package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
  107. package/template/src/app/api/settings/company/route.ts +121 -0
  108. package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
  109. package/template/src/app/api/settings/google-ads/route.ts +122 -0
  110. package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
  111. package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
  112. package/template/src/app/api/settings/google-sheet/route.ts +254 -0
  113. package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
  114. package/template/src/app/api/settings/meta-leads/route.ts +132 -0
  115. package/template/src/app/api/settings/profile/route.ts +42 -0
  116. package/template/src/app/api/settings/smtp/route.ts +130 -0
  117. package/template/src/app/api/settings/smtp/test/route.ts +121 -0
  118. package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
  119. package/template/src/app/api/settings/statuses/route.ts +83 -0
  120. package/template/src/app/api/statuses/route.ts +25 -0
  121. package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
  122. package/template/src/app/api/tasks/[id]/route.ts +728 -0
  123. package/template/src/app/api/tasks/meet/route.ts +240 -0
  124. package/template/src/app/api/tasks/route.ts +417 -0
  125. package/template/src/app/api/templates/[id]/route.ts +140 -0
  126. package/template/src/app/api/templates/route.ts +91 -0
  127. package/template/src/app/api/users/[id]/route.ts +168 -0
  128. package/template/src/app/api/users/list/route.ts +45 -0
  129. package/template/src/app/api/users/me/route.ts +48 -0
  130. package/template/src/app/api/users/route.ts +250 -0
  131. package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
  132. package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
  133. package/template/src/app/api/workflows/[id]/route.ts +192 -0
  134. package/template/src/app/api/workflows/process/route.ts +293 -0
  135. package/template/src/app/api/workflows/route.ts +124 -0
  136. package/template/src/app/favicon.ico +0 -0
  137. package/template/src/app/globals.css +1416 -0
  138. package/template/src/app/layout.tsx +31 -0
  139. package/template/src/app/page.tsx +32 -0
  140. package/template/src/components/dashboard/activity-chart.tsx +67 -0
  141. package/template/src/components/dashboard/contacts-chart.tsx +63 -0
  142. package/template/src/components/dashboard/recent-activity.tsx +164 -0
  143. package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
  144. package/template/src/components/dashboard/stat-card.tsx +61 -0
  145. package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
  146. package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
  147. package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
  148. package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
  149. package/template/src/components/editor.tsx +856 -0
  150. package/template/src/components/email-template.tsx +35 -0
  151. package/template/src/components/header.tsx +320 -0
  152. package/template/src/components/invitation-email-template.tsx +79 -0
  153. package/template/src/components/meet-cancellation-email-template.tsx +120 -0
  154. package/template/src/components/meet-confirmation-email-template.tsx +156 -0
  155. package/template/src/components/meet-update-email-template.tsx +209 -0
  156. package/template/src/components/page-header.tsx +61 -0
  157. package/template/src/components/reset-password-email-template.tsx +79 -0
  158. package/template/src/components/sidebar.tsx +294 -0
  159. package/template/src/components/skeleton.tsx +380 -0
  160. package/template/src/components/ui/commands.tsx +396 -0
  161. package/template/src/components/ui/components.tsx +150 -0
  162. package/template/src/components/ui/theme.tsx +5 -0
  163. package/template/src/components/view-as-banner.tsx +45 -0
  164. package/template/src/components/view-as-modal.tsx +186 -0
  165. package/template/src/contexts/mobile-menu-context.tsx +31 -0
  166. package/template/src/contexts/sidebar-context.tsx +107 -0
  167. package/template/src/contexts/task-reminder-context.tsx +239 -0
  168. package/template/src/contexts/view-as-context.tsx +84 -0
  169. package/template/src/hooks/use-user-role.ts +82 -0
  170. package/template/src/lib/audit-log.ts +45 -0
  171. package/template/src/lib/auth-client.ts +16 -0
  172. package/template/src/lib/auth.ts +35 -0
  173. package/template/src/lib/check-permission.ts +193 -0
  174. package/template/src/lib/contact-duplicate.ts +112 -0
  175. package/template/src/lib/contact-interactions.ts +371 -0
  176. package/template/src/lib/encryption.ts +99 -0
  177. package/template/src/lib/google-calendar.ts +300 -0
  178. package/template/src/lib/google-drive.ts +372 -0
  179. package/template/src/lib/permissions.ts +412 -0
  180. package/template/src/lib/prisma.ts +32 -0
  181. package/template/src/lib/roles.ts +120 -0
  182. package/template/src/lib/template-variables.ts +76 -0
  183. package/template/src/lib/utils.ts +46 -0
  184. package/template/src/lib/workflow-executor.ts +482 -0
  185. package/template/src/proxy.ts +91 -0
  186. package/template/tsconfig.json +34 -0
  187. package/template/vercel.json +8 -0
@@ -0,0 +1,258 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { decrypt } from '@/lib/encryption';
4
+ import { handleContactDuplicate } from '@/lib/contact-duplicate';
5
+ import { normalizePhoneNumber } from '@/lib/utils';
6
+
7
+ interface MetaLeadChange {
8
+ field: string;
9
+ value: {
10
+ leadgen_id: string;
11
+ form_id: string;
12
+ created_time: number;
13
+ page_id: string;
14
+ };
15
+ }
16
+
17
+ // GET /api/webhooks/meta-leads - Vérification du webhook Meta (subscription)
18
+ export async function GET(request: NextRequest) {
19
+ try {
20
+ const url = new URL(request.url);
21
+ const mode = url.searchParams.get('hub.mode');
22
+ const verifyToken = url.searchParams.get('hub.verify_token');
23
+ const challenge = url.searchParams.get('hub.challenge');
24
+
25
+ if (mode !== 'subscribe' || !verifyToken || !challenge) {
26
+ return NextResponse.json({ error: 'Requête invalide' }, { status: 400 });
27
+ }
28
+
29
+ // Vérifier toutes les configurations actives pour trouver celle qui correspond au verifyToken
30
+ const configs = await prisma.metaLeadConfig.findMany({
31
+ where: { active: true },
32
+ });
33
+
34
+ const config = configs.find((c) => c.verifyToken === verifyToken);
35
+
36
+ if (!config) {
37
+ return NextResponse.json({ error: 'Token de vérification invalide' }, { status: 403 });
38
+ }
39
+
40
+ return new Response(challenge, {
41
+ status: 200,
42
+ headers: { 'Content-Type': 'text/plain' },
43
+ });
44
+ } catch (error: any) {
45
+ console.error('Erreur lors de la vérification du webhook Meta Lead Ads:', error);
46
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
47
+ }
48
+ }
49
+
50
+ // POST /api/webhooks/meta-leads - Réception des leads Meta
51
+ export async function POST(request: NextRequest) {
52
+ try {
53
+ const body = await request.json();
54
+
55
+ if (body.object !== 'page' || !Array.isArray(body.entry)) {
56
+ return NextResponse.json({ received: true });
57
+ }
58
+
59
+ // Récupérer toutes les configurations actives
60
+ const configs = await prisma.metaLeadConfig.findMany({
61
+ where: { active: true },
62
+ });
63
+
64
+ if (!configs || configs.length === 0) {
65
+ console.warn('Webhook Meta Lead Ads reçu mais aucune configuration active trouvée.');
66
+ return NextResponse.json({ received: true });
67
+ }
68
+
69
+ for (const entry of body.entry) {
70
+ if (!Array.isArray(entry.changes)) continue;
71
+
72
+ for (const change of entry.changes as MetaLeadChange[]) {
73
+ if (change.field !== 'leadgen') continue;
74
+
75
+ const { leadgen_id: leadId, page_id: pageId } = change.value;
76
+
77
+ // Trouver la configuration correspondante à la page
78
+ const config = configs.find((c) => c.pageId === pageId);
79
+
80
+ if (!config) {
81
+ console.warn(
82
+ `Lead Meta reçu pour la page ${pageId} mais aucune configuration active trouvée pour cette page.`,
83
+ );
84
+ continue;
85
+ }
86
+
87
+ try {
88
+ const accessToken = decrypt(config.accessToken);
89
+
90
+ // Récupérer les données du lead depuis l'API Graph
91
+ const leadResponse = await fetch(
92
+ `https://graph.facebook.com/v18.0/${leadId}?access_token=${encodeURIComponent(
93
+ accessToken,
94
+ )}`,
95
+ );
96
+
97
+ if (!leadResponse.ok) {
98
+ const errorText = await leadResponse.text();
99
+ console.error('Erreur lors de la récupération du lead Meta:', errorText);
100
+ continue;
101
+ }
102
+
103
+ const leadData = await leadResponse.json();
104
+ const fieldData: Array<{ name: string; values: string[] }> = leadData.field_data || [];
105
+
106
+ const getField = (name: string): string | undefined => {
107
+ const field = fieldData.find((f) => f.name === name);
108
+ return field?.values?.[0];
109
+ };
110
+
111
+ let firstName = getField('first_name') || undefined;
112
+ let lastName = getField('last_name') || undefined;
113
+ const fullName = getField('full_name') || getField('name');
114
+ const email = getField('email');
115
+ const phone = getField('phone_number') || getField('phone');
116
+
117
+ if ((!firstName || !lastName) && fullName) {
118
+ const parts = fullName.split(' ');
119
+ firstName = firstName || parts.slice(0, -1).join(' ') || parts[0];
120
+ lastName = lastName || parts.slice(-1).join(' ');
121
+ }
122
+
123
+ // Le téléphone est obligatoire pour un contact dans ce CRM
124
+ if (!phone) {
125
+ console.warn(
126
+ 'Lead Meta reçu sans numéro de téléphone, impossible de créer le contact. Lead ID:',
127
+ leadId,
128
+ );
129
+ continue;
130
+ }
131
+
132
+ // Normaliser le numéro de téléphone
133
+ const normalizedPhone = normalizePhoneNumber(phone);
134
+
135
+ // Déterminer l'utilisateur pour createdById (nécessaire pour créer le contact)
136
+ let createdById = config.defaultAssignedUserId || null;
137
+ if (!createdById) {
138
+ const adminUser = await prisma.user.findFirst({
139
+ where: { role: 'ADMIN' },
140
+ orderBy: { createdAt: 'asc' },
141
+ });
142
+ if (adminUser) {
143
+ createdById = adminUser.id;
144
+ }
145
+ }
146
+
147
+ if (!createdById) {
148
+ console.warn(
149
+ 'Lead Meta reçu mais aucun utilisateur pour créer le contact trouvé. Lead ID:',
150
+ leadId,
151
+ );
152
+ continue;
153
+ }
154
+
155
+ // Déterminer l'assignation selon le rôle de l'utilisateur par défaut
156
+ let assignedCommercialId: string | null = null;
157
+ let assignedTeleproId: string | null = null;
158
+
159
+ if (config.defaultAssignedUserId) {
160
+ const defaultUser = await prisma.user.findUnique({
161
+ where: { id: config.defaultAssignedUserId },
162
+ select: { role: true },
163
+ });
164
+
165
+ if (defaultUser) {
166
+ if (
167
+ defaultUser.role === 'COMMERCIAL' ||
168
+ defaultUser.role === 'ADMIN' ||
169
+ defaultUser.role === 'MANAGER'
170
+ ) {
171
+ assignedCommercialId = config.defaultAssignedUserId;
172
+ } else if (defaultUser.role === 'TELEPRO') {
173
+ assignedTeleproId = config.defaultAssignedUserId;
174
+ }
175
+ // Sinon, on ne assigne pas (null pour les deux)
176
+ }
177
+ }
178
+
179
+ // Vérifier si c'est un doublon (nom, prénom ET email)
180
+ const duplicateContactId = await handleContactDuplicate(
181
+ firstName,
182
+ lastName,
183
+ email,
184
+ `Meta Lead Ads - ${config.name}`,
185
+ createdById,
186
+ );
187
+
188
+ let contact;
189
+ if (duplicateContactId) {
190
+ // C'est un doublon, récupérer le contact existant
191
+ contact = await prisma.contact.findUnique({
192
+ where: { id: duplicateContactId },
193
+ });
194
+ } else {
195
+ // Vérifier si un contact existe déjà (par téléphone uniquement)
196
+ contact = await prisma.contact.findFirst({
197
+ where: { phone: normalizedPhone },
198
+ });
199
+
200
+ if (!contact) {
201
+ contact = await prisma.contact.create({
202
+ data: {
203
+ firstName: firstName || null,
204
+ lastName: lastName || null,
205
+ email: email ? email.toLowerCase() : null,
206
+ phone: normalizedPhone,
207
+ origin: `Meta Lead Ads - ${config.name}`,
208
+ statusId: config.defaultStatusId || null,
209
+ assignedCommercialId: assignedCommercialId,
210
+ assignedTeleproId: assignedTeleproId,
211
+ createdById: createdById,
212
+ },
213
+ });
214
+ } else {
215
+ // Mettre à jour quelques infos si manquantes
216
+ await prisma.contact.update({
217
+ where: { id: contact.id },
218
+ data: {
219
+ firstName: contact.firstName || firstName || null,
220
+ lastName: contact.lastName || lastName || null,
221
+ email: contact.email || (email ? email.toLowerCase() : null),
222
+ origin: contact.origin || `Meta Lead Ads - ${config.name}`,
223
+ statusId: contact.statusId || config.defaultStatusId || null,
224
+ // Ne pas écraser les assignations existantes
225
+ assignedCommercialId: contact.assignedCommercialId || assignedCommercialId,
226
+ assignedTeleproId: contact.assignedTeleproId || assignedTeleproId,
227
+ },
228
+ });
229
+ }
230
+ }
231
+
232
+ // Créer une interaction "Lead Meta"
233
+ if (contact) {
234
+ await prisma.interaction.create({
235
+ data: {
236
+ contactId: contact.id,
237
+ type: 'NOTE',
238
+ title: `Lead Meta Lead Ads - ${config.name}`,
239
+ content: `Lead importé automatiquement depuis Meta Lead Ads (${config.name}, formulaire: ${
240
+ change.value.form_id
241
+ }).`,
242
+ userId: createdById,
243
+ date: new Date(change.value.created_time * 1000),
244
+ },
245
+ });
246
+ }
247
+ } catch (err: any) {
248
+ console.error('Erreur lors du traitement du lead Meta:', err);
249
+ }
250
+ }
251
+ }
252
+
253
+ return NextResponse.json({ received: true });
254
+ } catch (error: any) {
255
+ console.error('Erreur lors du traitement du webhook Meta Lead Ads:', error);
256
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
257
+ }
258
+ }
@@ -0,0 +1,192 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ // GET /api/workflows/[id] - Récupérer un workflow spécifique
6
+ export async function GET(
7
+ request: NextRequest,
8
+ { params }: { params: Promise<{ id: string }> },
9
+ ) {
10
+ try {
11
+ const session = await auth.api.getSession({
12
+ headers: request.headers,
13
+ });
14
+
15
+ if (!session) {
16
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
17
+ }
18
+
19
+ const { id } = await params;
20
+
21
+ const workflow = await prisma.workflow.findFirst({
22
+ where: {
23
+ id,
24
+ userId: session.user.id,
25
+ },
26
+ include: {
27
+ actions: {
28
+ orderBy: { order: 'asc' },
29
+ include: {
30
+ emailTemplate: true,
31
+ newStatus: true,
32
+ conditionStatus: true,
33
+ },
34
+ },
35
+ triggerStatus: true,
36
+ triggerToStatus: true,
37
+ },
38
+ });
39
+
40
+ if (!workflow) {
41
+ return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
42
+ }
43
+
44
+ return NextResponse.json(workflow);
45
+ } catch (error: any) {
46
+ console.error('Erreur lors de la récupération du workflow:', error);
47
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
48
+ }
49
+ }
50
+
51
+ // PUT /api/workflows/[id] - Mettre à jour un workflow
52
+ export async function PUT(
53
+ request: NextRequest,
54
+ { params }: { params: Promise<{ id: string }> },
55
+ ) {
56
+ try {
57
+ const session = await auth.api.getSession({
58
+ headers: request.headers,
59
+ });
60
+
61
+ if (!session) {
62
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
63
+ }
64
+
65
+ const { id } = await params;
66
+ const body = await request.json();
67
+ const {
68
+ name,
69
+ description,
70
+ active,
71
+ triggerType,
72
+ triggerFromStatusId,
73
+ triggerToStatusId,
74
+ triggerTimeDays,
75
+ triggerTimeHours,
76
+ actions = [],
77
+ } = body;
78
+
79
+ // Vérifier que le workflow appartient à l'utilisateur
80
+ const existingWorkflow = await prisma.workflow.findFirst({
81
+ where: {
82
+ id,
83
+ userId: session.user.id,
84
+ },
85
+ });
86
+
87
+ if (!existingWorkflow) {
88
+ return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
89
+ }
90
+
91
+ // Validation
92
+ if (!name || !triggerType) {
93
+ return NextResponse.json(
94
+ { error: 'Le nom et le type de déclencheur sont requis' },
95
+ { status: 400 },
96
+ );
97
+ }
98
+
99
+ // Supprimer les anciennes actions
100
+ await prisma.workflowAction.deleteMany({
101
+ where: { workflowId: id },
102
+ });
103
+
104
+ // Mettre à jour le workflow
105
+ const workflow = await prisma.workflow.update({
106
+ where: { id },
107
+ data: {
108
+ name,
109
+ description: description || null,
110
+ active: active !== undefined ? active : existingWorkflow.active,
111
+ triggerType,
112
+ triggerFromStatusId: triggerFromStatusId || null,
113
+ triggerToStatusId: triggerToStatusId || null,
114
+ triggerTimeDays: triggerTimeDays || null,
115
+ triggerTimeHours: triggerTimeHours || null,
116
+ actions: {
117
+ create: actions.map((action: any, index: number) => ({
118
+ actionType: action.actionType,
119
+ order: index,
120
+ delayDays: action.delayDays || 0,
121
+ delayHours: action.delayHours || 0,
122
+ emailTemplateId: action.emailTemplateId || null,
123
+ smsMessage: action.smsMessage || null,
124
+ newStatusId: action.newStatusId || null,
125
+ taskTitle: action.taskTitle || null,
126
+ taskDescription: action.taskDescription || null,
127
+ conditionOperator: action.conditionOperator || null,
128
+ conditionStatusId: action.conditionStatusId || null,
129
+ })),
130
+ },
131
+ },
132
+ include: {
133
+ actions: {
134
+ orderBy: { order: 'asc' },
135
+ include: {
136
+ emailTemplate: true,
137
+ newStatus: true,
138
+ conditionStatus: true,
139
+ },
140
+ },
141
+ triggerStatus: true,
142
+ triggerToStatus: true,
143
+ },
144
+ });
145
+
146
+ return NextResponse.json(workflow);
147
+ } catch (error: any) {
148
+ console.error('Erreur lors de la mise à jour du workflow:', error);
149
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
150
+ }
151
+ }
152
+
153
+ // DELETE /api/workflows/[id] - Supprimer un workflow
154
+ export async function DELETE(
155
+ request: NextRequest,
156
+ { params }: { params: Promise<{ id: string }> },
157
+ ) {
158
+ try {
159
+ const session = await auth.api.getSession({
160
+ headers: request.headers,
161
+ });
162
+
163
+ if (!session) {
164
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
165
+ }
166
+
167
+ const { id } = await params;
168
+
169
+ // Vérifier que le workflow appartient à l'utilisateur
170
+ const workflow = await prisma.workflow.findFirst({
171
+ where: {
172
+ id,
173
+ userId: session.user.id,
174
+ },
175
+ });
176
+
177
+ if (!workflow) {
178
+ return NextResponse.json({ error: 'Workflow non trouvé' }, { status: 404 });
179
+ }
180
+
181
+ // Supprimer le workflow (les actions seront supprimées en cascade)
182
+ await prisma.workflow.delete({
183
+ where: { id },
184
+ });
185
+
186
+ return NextResponse.json({ success: true });
187
+ } catch (error: any) {
188
+ console.error('Erreur lors de la suppression du workflow:', error);
189
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
190
+ }
191
+ }
192
+