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,254 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdmin } from '@/lib/roles';
4
+
5
+ function extractSpreadsheetId(sheetUrlOrId: string): string {
6
+ if (!sheetUrlOrId) return sheetUrlOrId;
7
+
8
+ // Si c'est déjà un ID simple, on le renvoie
9
+ if (!sheetUrlOrId.includes('https://')) {
10
+ return sheetUrlOrId;
11
+ }
12
+
13
+ const match = sheetUrlOrId.match(/spreadsheets\/d\/([a-zA-Z0-9-_]+)/);
14
+ if (match && match[1]) {
15
+ return match[1];
16
+ }
17
+
18
+ return sheetUrlOrId;
19
+ }
20
+
21
+ // GET /api/settings/google-sheet - Récupérer toutes les configurations Google Sheets (admin uniquement)
22
+ export async function GET(request: NextRequest) {
23
+ try {
24
+ await requireAdmin(request.headers);
25
+
26
+ const client = prisma as any;
27
+
28
+ const configs = await client.googleSheetSyncConfig.findMany({
29
+ include: {
30
+ defaultStatus: true,
31
+ defaultAssignedUser: {
32
+ select: { id: true, name: true, email: true },
33
+ },
34
+ ownerUser: {
35
+ select: { id: true, name: true, email: true },
36
+ },
37
+ },
38
+ orderBy: {
39
+ createdAt: 'desc',
40
+ },
41
+ });
42
+
43
+ return NextResponse.json(
44
+ configs.map((config: any) => ({
45
+ id: config.id,
46
+ name: config.name,
47
+ spreadsheetId: config.spreadsheetId,
48
+ sheetName: config.sheetName,
49
+ headerRow: config.headerRow,
50
+ phoneColumn: config.phoneColumn,
51
+ firstNameColumn: config.firstNameColumn,
52
+ lastNameColumn: config.lastNameColumn,
53
+ emailColumn: config.emailColumn,
54
+ cityColumn: config.cityColumn,
55
+ postalCodeColumn: config.postalCodeColumn,
56
+ originColumn: config.originColumn,
57
+ columnMappings: config.columnMappings
58
+ ? (typeof config.columnMappings === 'string'
59
+ ? JSON.parse(config.columnMappings)
60
+ : config.columnMappings)
61
+ : null,
62
+ active: config.active,
63
+ lastSyncedRow: config.lastSyncedRow,
64
+ defaultStatusId: config.defaultStatusId,
65
+ defaultAssignedUserId: config.defaultAssignedUserId,
66
+ ownerUser: config.ownerUser,
67
+ defaultStatus: config.defaultStatus
68
+ ? {
69
+ id: config.defaultStatus.id,
70
+ name: config.defaultStatus.name,
71
+ color: config.defaultStatus.color,
72
+ }
73
+ : null,
74
+ defaultAssignedUser: config.defaultAssignedUser || null,
75
+ })),
76
+ );
77
+ } catch (error: any) {
78
+ console.error('Erreur lors de la récupération des configurations Google Sheets:', error);
79
+
80
+ if (error.message === 'Non authentifié') {
81
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
82
+ }
83
+
84
+ if (error.message === 'Permissions insuffisantes') {
85
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
86
+ }
87
+
88
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
89
+ }
90
+ }
91
+
92
+ // POST /api/settings/google-sheet - Créer une nouvelle configuration (admin uniquement)
93
+ export async function POST(request: NextRequest) {
94
+ try {
95
+ const session = await requireAdmin(request.headers);
96
+
97
+ const body = await request.json();
98
+ const {
99
+ name,
100
+ sheetUrl,
101
+ sheetName,
102
+ headerRow,
103
+ phoneColumn, // Ancien format (compatibilité)
104
+ firstNameColumn,
105
+ lastNameColumn,
106
+ emailColumn,
107
+ cityColumn,
108
+ postalCodeColumn,
109
+ originColumn,
110
+ columnMappings, // Nouveau format
111
+ active = true,
112
+ defaultStatusId,
113
+ defaultAssignedUserId,
114
+ } = body;
115
+
116
+ if (!name || !sheetUrl || !sheetName || !headerRow) {
117
+ return NextResponse.json(
118
+ {
119
+ error:
120
+ 'Les champs nom, lien du Google Sheet, nom de l’onglet et ligne des en-têtes sont obligatoires.',
121
+ },
122
+ { status: 400 },
123
+ );
124
+ }
125
+
126
+ // Vérifier qu'au moins un mapping téléphone est configuré
127
+ if (columnMappings && Array.isArray(columnMappings)) {
128
+ const phoneMapping = columnMappings.find(
129
+ (m: any) => m.action === 'map' && m.crmField === 'phone' && m.columnName?.trim() !== '',
130
+ );
131
+ if (!phoneMapping) {
132
+ return NextResponse.json(
133
+ {
134
+ error: 'Le mapping du téléphone est obligatoire.',
135
+ },
136
+ { status: 400 },
137
+ );
138
+ }
139
+ } else if (!phoneColumn) {
140
+ return NextResponse.json(
141
+ {
142
+ error: 'Le mapping du téléphone est obligatoire.',
143
+ },
144
+ { status: 400 },
145
+ );
146
+ }
147
+
148
+ const spreadsheetId = extractSpreadsheetId(sheetUrl);
149
+ const headerRowNumber = Number(headerRow);
150
+
151
+ if (!headerRowNumber || headerRowNumber < 1) {
152
+ return NextResponse.json(
153
+ { error: 'La ligne des en-têtes doit être un nombre positif (ex: 1, 2...).' },
154
+ { status: 400 },
155
+ );
156
+ }
157
+
158
+ const client = prisma as any;
159
+
160
+ // Convertir les mappings vers l'ancien format pour la compatibilité
161
+ let phoneColumnValue = phoneColumn || '';
162
+ let firstNameColumnValue = firstNameColumn || null;
163
+ let lastNameColumnValue = lastNameColumn || null;
164
+ let emailColumnValue = emailColumn || null;
165
+ let cityColumnValue = cityColumn || null;
166
+ let postalCodeColumnValue = postalCodeColumn || null;
167
+ let originColumnValue = originColumn || null;
168
+
169
+ if (columnMappings && Array.isArray(columnMappings)) {
170
+ columnMappings.forEach((mapping: any) => {
171
+ if (mapping.action === 'map' && mapping.crmField && mapping.columnName) {
172
+ switch (mapping.crmField) {
173
+ case 'phone':
174
+ phoneColumnValue = mapping.columnName;
175
+ break;
176
+ case 'firstName':
177
+ firstNameColumnValue = mapping.columnName;
178
+ break;
179
+ case 'lastName':
180
+ lastNameColumnValue = mapping.columnName;
181
+ break;
182
+ case 'email':
183
+ emailColumnValue = mapping.columnName;
184
+ break;
185
+ case 'city':
186
+ cityColumnValue = mapping.columnName;
187
+ break;
188
+ case 'postalCode':
189
+ postalCodeColumnValue = mapping.columnName;
190
+ break;
191
+ case 'origin':
192
+ originColumnValue = mapping.columnName;
193
+ break;
194
+ }
195
+ }
196
+ });
197
+ }
198
+
199
+ const config = await client.googleSheetSyncConfig.create({
200
+ data: {
201
+ name,
202
+ ownerUserId: session.user.id,
203
+ spreadsheetId,
204
+ sheetName,
205
+ headerRow: headerRowNumber,
206
+ phoneColumn: phoneColumnValue,
207
+ firstNameColumn: firstNameColumnValue,
208
+ lastNameColumn: lastNameColumnValue,
209
+ emailColumn: emailColumnValue,
210
+ cityColumn: cityColumnValue,
211
+ postalCodeColumn: postalCodeColumnValue,
212
+ originColumn: originColumnValue,
213
+ columnMappings: columnMappings ? JSON.parse(JSON.stringify(columnMappings)) : null,
214
+ active: !!active,
215
+ defaultStatusId: defaultStatusId || null,
216
+ defaultAssignedUserId: defaultAssignedUserId || null,
217
+ },
218
+ });
219
+
220
+ return NextResponse.json({
221
+ success: true,
222
+ config: {
223
+ id: config.id,
224
+ name: config.name,
225
+ spreadsheetId: config.spreadsheetId,
226
+ sheetName: config.sheetName,
227
+ headerRow: config.headerRow,
228
+ phoneColumn: config.phoneColumn,
229
+ firstNameColumn: config.firstNameColumn,
230
+ lastNameColumn: config.lastNameColumn,
231
+ emailColumn: config.emailColumn,
232
+ cityColumn: config.cityColumn,
233
+ postalCodeColumn: config.postalCodeColumn,
234
+ originColumn: config.originColumn,
235
+ active: config.active,
236
+ defaultStatusId: config.defaultStatusId,
237
+ defaultAssignedUserId: config.defaultAssignedUserId,
238
+ },
239
+ message: 'Configuration Google Sheets créée avec succès.',
240
+ });
241
+ } catch (error: any) {
242
+ console.error('Erreur lors de la création de la configuration Google Sheets:', error);
243
+
244
+ if (error.message === 'Non authentifié') {
245
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
246
+ }
247
+
248
+ if (error.message === 'Permissions insuffisantes') {
249
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
250
+ }
251
+
252
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
253
+ }
254
+ }
@@ -0,0 +1,123 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdmin } from '@/lib/roles';
4
+ import { encrypt } from '@/lib/encryption';
5
+
6
+ // PUT /api/settings/meta-leads/[id] - Mettre à jour une configuration (admin uniquement)
7
+ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
8
+ try {
9
+ await requireAdmin(request.headers);
10
+
11
+ const { id } = await params;
12
+ const body = await request.json();
13
+ const {
14
+ name,
15
+ pageId,
16
+ accessToken,
17
+ verifyToken,
18
+ active,
19
+ defaultStatusId,
20
+ defaultAssignedUserId,
21
+ } = body;
22
+
23
+ const existingConfig = await prisma.metaLeadConfig.findUnique({
24
+ where: { id },
25
+ });
26
+
27
+ if (!existingConfig) {
28
+ return NextResponse.json({ error: 'Configuration non trouvée' }, { status: 404 });
29
+ }
30
+
31
+ const updateData: any = {};
32
+ if (name !== undefined) updateData.name = name;
33
+ if (pageId !== undefined) updateData.pageId = pageId;
34
+ if (accessToken !== undefined) {
35
+ try {
36
+ updateData.accessToken = encrypt(accessToken);
37
+ } catch (encryptError: any) {
38
+ console.error("Erreur lors du chiffrement du jeton d'accès Meta:", encryptError);
39
+ return NextResponse.json(
40
+ { error: `Erreur de chiffrement: ${encryptError.message}` },
41
+ { status: 500 },
42
+ );
43
+ }
44
+ }
45
+ if (verifyToken !== undefined) updateData.verifyToken = verifyToken;
46
+ if (active !== undefined) updateData.active = !!active;
47
+ if (defaultStatusId !== undefined) updateData.defaultStatusId = defaultStatusId || null;
48
+ if (defaultAssignedUserId !== undefined)
49
+ updateData.defaultAssignedUserId = defaultAssignedUserId || null;
50
+
51
+ const config = await prisma.metaLeadConfig.update({
52
+ where: { id },
53
+ data: updateData,
54
+ });
55
+
56
+ return NextResponse.json({
57
+ success: true,
58
+ config: {
59
+ id: config.id,
60
+ name: config.name,
61
+ pageId: config.pageId,
62
+ verifyToken: config.verifyToken,
63
+ active: config.active,
64
+ defaultStatusId: config.defaultStatusId,
65
+ defaultAssignedUserId: config.defaultAssignedUserId,
66
+ },
67
+ message: 'Configuration Meta Lead Ads mise à jour avec succès.',
68
+ });
69
+ } catch (error: any) {
70
+ console.error('Erreur lors de la mise à jour de la configuration Meta Lead Ads:', error);
71
+
72
+ if (error.message === 'Non authentifié') {
73
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
74
+ }
75
+
76
+ if (error.message === 'Permissions insuffisantes') {
77
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
78
+ }
79
+
80
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
81
+ }
82
+ }
83
+
84
+ // DELETE /api/settings/meta-leads/[id] - Supprimer une configuration (admin uniquement)
85
+ export async function DELETE(
86
+ request: NextRequest,
87
+ { params }: { params: Promise<{ id: string }> },
88
+ ) {
89
+ try {
90
+ await requireAdmin(request.headers);
91
+
92
+ const { id } = await params;
93
+
94
+ const existingConfig = await prisma.metaLeadConfig.findUnique({
95
+ where: { id },
96
+ });
97
+
98
+ if (!existingConfig) {
99
+ return NextResponse.json({ error: 'Configuration non trouvée' }, { status: 404 });
100
+ }
101
+
102
+ await prisma.metaLeadConfig.delete({
103
+ where: { id },
104
+ });
105
+
106
+ return NextResponse.json({
107
+ success: true,
108
+ message: 'Configuration Meta Lead Ads supprimée avec succès.',
109
+ });
110
+ } catch (error: any) {
111
+ console.error('Erreur lors de la suppression de la configuration Meta Lead Ads:', error);
112
+
113
+ if (error.message === 'Non authentifié') {
114
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
115
+ }
116
+
117
+ if (error.message === 'Permissions insuffisantes') {
118
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
119
+ }
120
+
121
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
122
+ }
123
+ }
@@ -0,0 +1,132 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { requireAdmin } from '@/lib/roles';
4
+ import { encrypt } from '@/lib/encryption';
5
+
6
+ // GET /api/settings/meta-leads - Récupérer toutes les configurations Meta Lead Ads (admin uniquement)
7
+ export async function GET(request: NextRequest) {
8
+ try {
9
+ await requireAdmin(request.headers);
10
+
11
+ const configs = await prisma.metaLeadConfig.findMany({
12
+ include: {
13
+ defaultStatus: true,
14
+ defaultAssignedUser: {
15
+ select: { id: true, name: true, email: true },
16
+ },
17
+ },
18
+ orderBy: {
19
+ createdAt: 'desc',
20
+ },
21
+ });
22
+
23
+ return NextResponse.json(
24
+ configs.map((config) => ({
25
+ id: config.id,
26
+ name: config.name,
27
+ pageId: config.pageId,
28
+ verifyToken: config.verifyToken,
29
+ active: config.active,
30
+ defaultStatusId: config.defaultStatusId,
31
+ defaultAssignedUserId: config.defaultAssignedUserId,
32
+ defaultStatus: config.defaultStatus
33
+ ? {
34
+ id: config.defaultStatus.id,
35
+ name: config.defaultStatus.name,
36
+ color: config.defaultStatus.color,
37
+ }
38
+ : null,
39
+ defaultAssignedUser: config.defaultAssignedUser || null,
40
+ })),
41
+ );
42
+ } catch (error: any) {
43
+ console.error('Erreur lors de la récupération des configurations Meta Lead Ads:', error);
44
+
45
+ if (error.message === 'Non authentifié') {
46
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
47
+ }
48
+
49
+ if (error.message === 'Permissions insuffisantes') {
50
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
51
+ }
52
+
53
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
54
+ }
55
+ }
56
+
57
+ // POST /api/settings/meta-leads - Créer une nouvelle configuration (admin uniquement)
58
+ export async function POST(request: NextRequest) {
59
+ try {
60
+ await requireAdmin(request.headers);
61
+
62
+ const body = await request.json();
63
+ const {
64
+ name,
65
+ pageId,
66
+ accessToken,
67
+ verifyToken,
68
+ active = true,
69
+ defaultStatusId,
70
+ defaultAssignedUserId,
71
+ } = body;
72
+
73
+ if (!name || !pageId || !accessToken || !verifyToken) {
74
+ return NextResponse.json(
75
+ {
76
+ error:
77
+ 'Les champs nom, pageId, accessToken et verifyToken sont obligatoires pour activer Meta Lead Ads.',
78
+ },
79
+ { status: 400 },
80
+ );
81
+ }
82
+
83
+ let encryptedAccessToken: string;
84
+ try {
85
+ encryptedAccessToken = encrypt(accessToken);
86
+ } catch (encryptError: any) {
87
+ console.error("Erreur lors du chiffrement du jeton d'accès Meta:", encryptError);
88
+ return NextResponse.json(
89
+ { error: `Erreur de chiffrement: ${encryptError.message}` },
90
+ { status: 500 },
91
+ );
92
+ }
93
+
94
+ const config = await prisma.metaLeadConfig.create({
95
+ data: {
96
+ name,
97
+ pageId,
98
+ accessToken: encryptedAccessToken,
99
+ verifyToken,
100
+ active: !!active,
101
+ defaultStatusId: defaultStatusId || null,
102
+ defaultAssignedUserId: defaultAssignedUserId || null,
103
+ },
104
+ });
105
+
106
+ return NextResponse.json({
107
+ success: true,
108
+ config: {
109
+ id: config.id,
110
+ name: config.name,
111
+ pageId: config.pageId,
112
+ verifyToken: config.verifyToken,
113
+ active: config.active,
114
+ defaultStatusId: config.defaultStatusId,
115
+ defaultAssignedUserId: config.defaultAssignedUserId,
116
+ },
117
+ message: 'Configuration Meta Lead Ads créée avec succès.',
118
+ });
119
+ } catch (error: any) {
120
+ console.error('Erreur lors de la création de la configuration Meta Lead Ads:', error);
121
+
122
+ if (error.message === 'Non authentifié') {
123
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
124
+ }
125
+
126
+ if (error.message === 'Permissions insuffisantes') {
127
+ return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
128
+ }
129
+
130
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
131
+ }
132
+ }
@@ -0,0 +1,42 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ // PUT /api/settings/profile - Mettre à jour le profil de l'utilisateur (nom uniquement)
6
+ export async function PUT(request: NextRequest) {
7
+ try {
8
+ const session = await auth.api.getSession({
9
+ headers: request.headers,
10
+ });
11
+
12
+ if (!session) {
13
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
14
+ }
15
+
16
+ const body = await request.json();
17
+ const { name } = body;
18
+
19
+ if (!name || name.trim().length === 0) {
20
+ return NextResponse.json({ error: 'Le nom est requis' }, { status: 400 });
21
+ }
22
+
23
+ // Mettre à jour le nom de l'utilisateur
24
+ const updatedUser = await prisma.user.update({
25
+ where: { id: session.user.id },
26
+ data: { name: name.trim() },
27
+ });
28
+
29
+ return NextResponse.json({
30
+ success: true,
31
+ user: {
32
+ id: updatedUser.id,
33
+ name: updatedUser.name,
34
+ email: updatedUser.email,
35
+ },
36
+ message: 'Nom mis à jour avec succès',
37
+ });
38
+ } catch (error: any) {
39
+ console.error('Erreur lors de la mise à jour du profil:', error);
40
+ return NextResponse.json({ error: 'Erreur lors de la mise à jour du profil' }, { status: 500 });
41
+ }
42
+ }
@@ -0,0 +1,130 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+ import { encrypt } from '@/lib/encryption';
5
+
6
+ // GET /api/settings/smtp - Récupérer la configuration SMTP de l'utilisateur
7
+ export async function GET(request: NextRequest) {
8
+ try {
9
+ const session = await auth.api.getSession({
10
+ headers: request.headers,
11
+ });
12
+
13
+ if (!session) {
14
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
15
+ }
16
+
17
+ const smtpConfig = await prisma.smtpConfig.findUnique({
18
+ where: { userId: session.user.id },
19
+ select: {
20
+ id: true,
21
+ host: true,
22
+ port: true,
23
+ secure: true,
24
+ username: true,
25
+ fromEmail: true,
26
+ fromName: true,
27
+ signature: true,
28
+ // Ne pas retourner le mot de passe
29
+ },
30
+ });
31
+
32
+ return NextResponse.json(smtpConfig || null);
33
+ } catch (error: any) {
34
+ console.error('Erreur lors de la récupération de la config SMTP:', error);
35
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
36
+ }
37
+ }
38
+
39
+ // PUT /api/settings/smtp - Sauvegarder ou mettre à jour la configuration SMTP
40
+ export async function PUT(request: NextRequest) {
41
+ try {
42
+ const session = await auth.api.getSession({
43
+ headers: request.headers,
44
+ });
45
+
46
+ if (!session) {
47
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
48
+ }
49
+
50
+ const body = await request.json();
51
+ const { host, port, secure, username, password, fromEmail, fromName, signature } = body;
52
+
53
+ // Validation
54
+ if (!host || !port || !username || !password || !fromEmail) {
55
+ return NextResponse.json(
56
+ { error: 'Tous les champs sont requis (host, port, username, password, fromEmail)' },
57
+ { status: 400 },
58
+ );
59
+ }
60
+
61
+ if (port < 1 || port > 65535) {
62
+ return NextResponse.json({ error: 'Le port doit être entre 1 et 65535' }, { status: 400 });
63
+ }
64
+
65
+ // Chiffrer le mot de passe avant stockage
66
+ let encryptedPassword: string;
67
+ try {
68
+ encryptedPassword = encrypt(password);
69
+ } catch (encryptError: any) {
70
+ console.error('Erreur lors du chiffrement du mot de passe:', encryptError);
71
+ return NextResponse.json(
72
+ { error: `Erreur de chiffrement: ${encryptError.message}` },
73
+ { status: 500 },
74
+ );
75
+ }
76
+
77
+ // Créer ou mettre à jour la configuration SMTP
78
+ let smtpConfig;
79
+ try {
80
+ smtpConfig = await prisma.smtpConfig.upsert({
81
+ where: { userId: session.user.id },
82
+ update: {
83
+ host,
84
+ port: parseInt(port),
85
+ secure: secure === true || secure === 'true',
86
+ username,
87
+ password: encryptedPassword, // Mot de passe chiffré
88
+ fromEmail,
89
+ fromName: fromName || null,
90
+ signature: signature || null,
91
+ },
92
+ create: {
93
+ userId: session.user.id,
94
+ host,
95
+ port: parseInt(port),
96
+ secure: secure === true || secure === 'true',
97
+ username,
98
+ password: encryptedPassword, // Mot de passe chiffré
99
+ fromEmail,
100
+ fromName: fromName || null,
101
+ signature: signature || null,
102
+ },
103
+ });
104
+ } catch (dbError: any) {
105
+ console.error('Erreur lors de la sauvegarde en base de données:', dbError);
106
+ return NextResponse.json(
107
+ { error: `Erreur de base de données: ${dbError.message}` },
108
+ { status: 500 },
109
+ );
110
+ }
111
+
112
+ return NextResponse.json({
113
+ success: true,
114
+ config: {
115
+ id: smtpConfig.id,
116
+ host: smtpConfig.host,
117
+ port: smtpConfig.port,
118
+ secure: smtpConfig.secure,
119
+ username: smtpConfig.username,
120
+ fromEmail: smtpConfig.fromEmail,
121
+ fromName: smtpConfig.fromName,
122
+ signature: smtpConfig.signature,
123
+ },
124
+ message: 'Configuration SMTP sauvegardée avec succès',
125
+ });
126
+ } catch (error: any) {
127
+ console.error('Erreur lors de la sauvegarde de la config SMTP:', error);
128
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
129
+ }
130
+ }