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,95 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ // GET /api/reminders - Récupérer tous les rappels de l'utilisateur
6
+ export async function GET(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 now = new Date();
17
+ const future = new Date(now);
18
+ future.setDate(future.getDate() + 7); // 7 jours à venir
19
+
20
+ // Récupérer toutes les tâches non complétées de l'utilisateur avec rappels
21
+ const tasks = await prisma.task.findMany({
22
+ where: {
23
+ assignedUserId: session.user.id,
24
+ completed: false,
25
+ scheduledAt: {
26
+ gte: now,
27
+ lte: future,
28
+ },
29
+ reminderMinutesBefore: {
30
+ not: null,
31
+ },
32
+ },
33
+ include: {
34
+ contact: {
35
+ select: {
36
+ id: true,
37
+ firstName: true,
38
+ lastName: true,
39
+ email: true,
40
+ phone: true,
41
+ },
42
+ },
43
+ assignedUser: {
44
+ select: {
45
+ id: true,
46
+ name: true,
47
+ email: true,
48
+ },
49
+ },
50
+ },
51
+ orderBy: {
52
+ scheduledAt: 'asc',
53
+ },
54
+ });
55
+
56
+ // Formater les rappels - inclure toutes les tâches avec rappels
57
+ const reminders = tasks
58
+ .filter((task) => {
59
+ if (!task.reminderMinutesBefore) return false;
60
+ const scheduled = new Date(task.scheduledAt);
61
+ const reminderTime = new Date(scheduled.getTime() - task.reminderMinutesBefore * 60 * 1000);
62
+ // Inclure les rappels qui sont passés mais pas encore à l'heure de la tâche
63
+ // ou les rappels qui sont à venir (dans les 7 prochains jours)
64
+ const timeUntilReminder = reminderTime.getTime() - now.getTime();
65
+ return (reminderTime <= now && now < scheduled) || (timeUntilReminder > 0 && timeUntilReminder < 7 * 24 * 60 * 60 * 1000);
66
+ })
67
+ .map((task) => {
68
+ const scheduled = new Date(task.scheduledAt);
69
+ const reminderTime = new Date(
70
+ scheduled.getTime() - (task.reminderMinutesBefore || 0) * 60 * 1000,
71
+ );
72
+
73
+ return {
74
+ id: `${task.id}-reminder`,
75
+ taskId: task.id,
76
+ type: task.type,
77
+ title: task.title,
78
+ description: task.description,
79
+ priority: task.priority,
80
+ scheduledAt: task.scheduledAt,
81
+ reminderTime: reminderTime.toISOString(),
82
+ reminderMinutesBefore: task.reminderMinutesBefore,
83
+ contact: task.contact,
84
+ assignedUser: task.assignedUser,
85
+ };
86
+ })
87
+ .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime());
88
+
89
+ return NextResponse.json(reminders);
90
+ } catch (error: any) {
91
+ console.error('Erreur lors de la récupération des rappels:', error);
92
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
93
+ }
94
+ }
95
+
@@ -0,0 +1,73 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { hashPassword } from '@/lib/auth';
4
+
5
+ export async function POST(request: NextRequest) {
6
+ try {
7
+ const body = await request.json();
8
+ const { token, password } = body;
9
+
10
+ if (!token || !password) {
11
+ return NextResponse.json({ error: 'Token et mot de passe requis' }, { status: 400 });
12
+ }
13
+
14
+ if (password.length < 6) {
15
+ return NextResponse.json(
16
+ { error: 'Le mot de passe doit contenir au moins 6 caractères' },
17
+ { status: 400 },
18
+ );
19
+ }
20
+
21
+ // Valider le token
22
+ const verification = await prisma.verification.findFirst({
23
+ where: {
24
+ value: token,
25
+ expiresAt: {
26
+ gt: new Date(),
27
+ },
28
+ },
29
+ });
30
+
31
+ if (!verification) {
32
+ return NextResponse.json({ error: 'Lien invalide ou expiré' }, { status: 400 });
33
+ }
34
+
35
+ // Trouver l'utilisateur
36
+ const user = await prisma.user.findUnique({
37
+ where: { email: verification.identifier },
38
+ include: {
39
+ accounts: {
40
+ where: { providerId: 'credential' },
41
+ },
42
+ },
43
+ });
44
+
45
+ if (!user || user.accounts.length === 0) {
46
+ return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
47
+ }
48
+
49
+ // Hasher le nouveau mot de passe
50
+ const hashedPassword = await hashPassword(password);
51
+
52
+ // Mettre à jour le mot de passe dans l'Account
53
+ await prisma.account.update({
54
+ where: { id: user.accounts[0].id },
55
+ data: {
56
+ password: hashedPassword,
57
+ },
58
+ });
59
+
60
+ // Supprimer le token de vérification
61
+ await prisma.verification.delete({
62
+ where: { id: verification.id },
63
+ });
64
+
65
+ return NextResponse.json({
66
+ success: true,
67
+ message: 'Mot de passe réinitialisé avec succès',
68
+ });
69
+ } catch (error: any) {
70
+ console.error('Erreur lors de la complétion:', error);
71
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
72
+ }
73
+ }
@@ -0,0 +1,84 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+
4
+ export async function POST(request: NextRequest) {
5
+ try {
6
+ const body = await request.json();
7
+ const { email } = body;
8
+
9
+ if (!email) {
10
+ return NextResponse.json({ error: 'Email requis' }, { status: 400 });
11
+ }
12
+
13
+ // Vérifier si l'utilisateur existe
14
+ const user = await prisma.user.findUnique({
15
+ where: { email },
16
+ include: {
17
+ accounts: {
18
+ where: { providerId: 'credential' },
19
+ },
20
+ },
21
+ });
22
+
23
+ // Ne pas révéler si l'email existe ou non (sécurité)
24
+ // Mais on envoie quand même un message de succès
25
+ if (!user || user.accounts.length === 0) {
26
+ // Retourner un succès même si l'utilisateur n'existe pas (sécurité)
27
+ return NextResponse.json({
28
+ success: true,
29
+ message: 'Si cet email existe, un code vous a été envoyé',
30
+ });
31
+ }
32
+
33
+ // Générer un code à 6 chiffres
34
+ const code = Math.floor(100000 + Math.random() * 900000).toString();
35
+
36
+ // Créer ou mettre à jour le token de vérification
37
+ const expiresAt = new Date();
38
+ expiresAt.setMinutes(expiresAt.getMinutes() + 15); // Valide 15 minutes
39
+
40
+ // Supprimer les anciens tokens pour cet email
41
+ await prisma.verification.deleteMany({
42
+ where: {
43
+ identifier: email,
44
+ },
45
+ });
46
+
47
+ // Créer le nouveau token avec le code
48
+ await prisma.verification.create({
49
+ data: {
50
+ id: crypto.randomUUID(),
51
+ identifier: email,
52
+ value: code,
53
+ expiresAt,
54
+ },
55
+ });
56
+
57
+ // Envoyer l'email avec le code
58
+ const baseUrl = process.env.BETTER_AUTH_URL || 'http://localhost:3000';
59
+
60
+ try {
61
+ await fetch(`${baseUrl}/api/send`, {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({
65
+ to: email,
66
+ subject: 'Code de réinitialisation de mot de passe',
67
+ template: 'reset-password',
68
+ code,
69
+ }),
70
+ });
71
+ } catch (emailError) {
72
+ console.error("Erreur lors de l'envoi de l'email:", emailError);
73
+ // On continue même si l'email échoue
74
+ }
75
+
76
+ return NextResponse.json({
77
+ success: true,
78
+ message: 'Si cet email existe, un code vous a été envoyé',
79
+ });
80
+ } catch (error: any) {
81
+ console.error('Erreur lors de la demande de réinitialisation:', error);
82
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
83
+ }
84
+ }
@@ -0,0 +1,49 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const { searchParams } = new URL(request.url);
7
+ const token = searchParams.get('token');
8
+
9
+ if (!token) {
10
+ return NextResponse.json({ error: 'Token manquant' }, { status: 400 });
11
+ }
12
+
13
+ // Trouver le token de vérification
14
+ const verification = await prisma.verification.findFirst({
15
+ where: {
16
+ value: token,
17
+ expiresAt: {
18
+ gt: new Date(), // Pas expiré
19
+ },
20
+ },
21
+ });
22
+
23
+ if (!verification) {
24
+ return NextResponse.json({ error: 'Lien invalide ou expiré' }, { status: 400 });
25
+ }
26
+
27
+ // Vérifier si l'utilisateur existe et a un compte
28
+ const user = await prisma.user.findUnique({
29
+ where: { email: verification.identifier },
30
+ include: {
31
+ accounts: {
32
+ where: { providerId: 'credential' },
33
+ },
34
+ },
35
+ });
36
+
37
+ if (!user || user.accounts.length === 0) {
38
+ return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
39
+ }
40
+
41
+ return NextResponse.json({
42
+ email: user.email,
43
+ valid: true,
44
+ });
45
+ } catch (error) {
46
+ console.error('Erreur lors de la validation:', error);
47
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
48
+ }
49
+ }
@@ -0,0 +1,74 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+
4
+ export async function POST(request: NextRequest) {
5
+ try {
6
+ const body = await request.json();
7
+ const { email, code } = body;
8
+
9
+ if (!email || !code) {
10
+ return NextResponse.json({ error: 'Email et code requis' }, { status: 400 });
11
+ }
12
+
13
+ if (code.length !== 6 || !/^\d+$/.test(code)) {
14
+ return NextResponse.json({ error: 'Code invalide' }, { status: 400 });
15
+ }
16
+
17
+ // Trouver le token de vérification
18
+ const verification = await prisma.verification.findFirst({
19
+ where: {
20
+ identifier: email,
21
+ value: code,
22
+ expiresAt: {
23
+ gt: new Date(), // Pas expiré
24
+ },
25
+ },
26
+ });
27
+
28
+ if (!verification) {
29
+ return NextResponse.json({ error: 'Code invalide ou expiré' }, { status: 400 });
30
+ }
31
+
32
+ // Vérifier si l'utilisateur existe et a un compte
33
+ const user = await prisma.user.findUnique({
34
+ where: { email },
35
+ include: {
36
+ accounts: {
37
+ where: { providerId: 'credential' },
38
+ },
39
+ },
40
+ });
41
+
42
+ if (!user || user.accounts.length === 0) {
43
+ return NextResponse.json({ error: 'Utilisateur non trouvé' }, { status: 404 });
44
+ }
45
+
46
+ // Générer un token pour la réinitialisation (différent du code)
47
+ const resetToken = crypto.randomUUID();
48
+ const expiresAt = new Date();
49
+ expiresAt.setHours(expiresAt.getHours() + 1); // Valide 1 heure
50
+
51
+ // Supprimer l'ancien token de code
52
+ await prisma.verification.delete({
53
+ where: { id: verification.id },
54
+ });
55
+
56
+ // Créer un nouveau token pour la réinitialisation
57
+ await prisma.verification.create({
58
+ data: {
59
+ id: crypto.randomUUID(),
60
+ identifier: email,
61
+ value: resetToken,
62
+ expiresAt,
63
+ },
64
+ });
65
+
66
+ return NextResponse.json({
67
+ success: true,
68
+ token: resetToken,
69
+ });
70
+ } catch (error) {
71
+ console.error('Erreur lors de la vérification:', error);
72
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
73
+ }
74
+ }
@@ -0,0 +1,183 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { headers } from 'next/headers';
4
+ import { prisma } from '@/lib/prisma';
5
+ import { checkPermission } from '@/lib/check-permission';
6
+ import { logAudit } from '@/lib/audit-log';
7
+
8
+ // PUT /api/roles/[id] - Modifier un profil
9
+ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
10
+ try {
11
+ const session = await auth.api.getSession({
12
+ headers: await headers(),
13
+ });
14
+
15
+ if (!session) {
16
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
17
+ }
18
+
19
+ // Vérifier que l'utilisateur a la permission de gérer les rôles
20
+ const hasPermission = await checkPermission('users.manage_roles');
21
+ if (!hasPermission) {
22
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 403 });
23
+ }
24
+
25
+ const { id } = await params;
26
+ const body = await req.json();
27
+ const { name, description, permissions } = body;
28
+
29
+ // Vérifier que le profil existe
30
+ const existingRole = await prisma.customRole.findUnique({
31
+ where: { id },
32
+ include: {
33
+ _count: {
34
+ select: { users: true },
35
+ },
36
+ },
37
+ });
38
+
39
+ if (!existingRole) {
40
+ return NextResponse.json({ error: 'Profil non trouvé' }, { status: 404 });
41
+ }
42
+
43
+ // Validation
44
+ if (name && typeof name !== 'string') {
45
+ return NextResponse.json(
46
+ { error: 'Le nom doit être une chaîne de caractères' },
47
+ { status: 400 },
48
+ );
49
+ }
50
+
51
+ if (permissions && !Array.isArray(permissions)) {
52
+ return NextResponse.json(
53
+ { error: 'Les permissions doivent être un tableau' },
54
+ { status: 400 },
55
+ );
56
+ }
57
+
58
+ // Vérifier que le nouveau nom n'existe pas déjà (si changé)
59
+ if (name && name.trim() !== existingRole.name) {
60
+ const duplicateName = await prisma.customRole.findUnique({
61
+ where: { name: name.trim() },
62
+ });
63
+
64
+ if (duplicateName) {
65
+ return NextResponse.json({ error: 'Un profil avec ce nom existe déjà' }, { status: 400 });
66
+ }
67
+ }
68
+
69
+ // Mettre à jour le profil
70
+ const updatedRole = await prisma.customRole.update({
71
+ where: { id },
72
+ data: {
73
+ ...(name && { name: name.trim() }),
74
+ ...(description !== undefined && { description: description?.trim() || null }),
75
+ ...(permissions && { permissions }),
76
+ },
77
+ });
78
+
79
+ await logAudit({
80
+ actorId: session.user.id,
81
+ action: 'ROLE_UPDATED',
82
+ entityType: 'ROLE',
83
+ entityId: updatedRole.id,
84
+ metadata: {
85
+ before: {
86
+ name: existingRole.name,
87
+ description: existingRole.description,
88
+ permissions: existingRole.permissions,
89
+ },
90
+ after: {
91
+ name: updatedRole.name,
92
+ description: updatedRole.description,
93
+ permissions: updatedRole.permissions,
94
+ },
95
+ },
96
+ });
97
+
98
+ return NextResponse.json({
99
+ message: 'Profil modifié avec succès',
100
+ role: {
101
+ id: updatedRole.id,
102
+ name: updatedRole.name,
103
+ description: updatedRole.description,
104
+ permissions: updatedRole.permissions,
105
+ isSystem: updatedRole.isSystem,
106
+ usersCount: existingRole._count.users,
107
+ createdAt: updatedRole.createdAt.toISOString(),
108
+ updatedAt: updatedRole.updatedAt.toISOString(),
109
+ },
110
+ });
111
+ } catch (error) {
112
+ console.error('Erreur lors de la modification du profil:', error);
113
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
114
+ }
115
+ }
116
+
117
+ // DELETE /api/roles/[id] - Supprimer un profil
118
+ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
119
+ try {
120
+ const session = await auth.api.getSession({
121
+ headers: await headers(),
122
+ });
123
+
124
+ if (!session) {
125
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
126
+ }
127
+
128
+ // Vérifier que l'utilisateur a la permission de gérer les rôles
129
+ const hasPermission = await checkPermission('users.manage_roles');
130
+ if (!hasPermission) {
131
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 403 });
132
+ }
133
+
134
+ const { id } = await params;
135
+
136
+ // Vérifier que le profil existe
137
+ const existingRole = await prisma.customRole.findUnique({
138
+ where: { id },
139
+ include: {
140
+ _count: {
141
+ select: { users: true },
142
+ },
143
+ },
144
+ });
145
+
146
+ if (!existingRole) {
147
+ return NextResponse.json({ error: 'Profil non trouvé' }, { status: 404 });
148
+ }
149
+
150
+ // Empêcher la suppression si des utilisateurs utilisent ce profil
151
+ if (existingRole._count.users > 0) {
152
+ return NextResponse.json(
153
+ {
154
+ error: `Ce profil ne peut pas être supprimé car ${existingRole._count.users} l'utilisent`,
155
+ },
156
+ { status: 400 },
157
+ );
158
+ }
159
+
160
+ // Supprimer le profil
161
+ await prisma.customRole.delete({
162
+ where: { id },
163
+ });
164
+
165
+ await logAudit({
166
+ actorId: session.user.id,
167
+ action: 'ROLE_DELETED',
168
+ entityType: 'ROLE',
169
+ entityId: existingRole.id,
170
+ metadata: {
171
+ name: existingRole.name,
172
+ description: existingRole.description,
173
+ },
174
+ });
175
+
176
+ return NextResponse.json({
177
+ message: 'Profil supprimé avec succès',
178
+ });
179
+ } catch (error) {
180
+ console.error('Erreur lors de la suppression du profil:', error);
181
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
182
+ }
183
+ }
@@ -0,0 +1,140 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { headers } from 'next/headers';
4
+ import { prisma } from '@/lib/prisma';
5
+ import { checkPermission } from '@/lib/check-permission';
6
+ import { logAudit } from '@/lib/audit-log';
7
+
8
+ // GET /api/roles - Récupérer tous les profils (système + personnalisés)
9
+ export async function GET(req: NextRequest) {
10
+ try {
11
+ const session = await auth.api.getSession({
12
+ headers: await headers(),
13
+ });
14
+
15
+ if (!session) {
16
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
17
+ }
18
+
19
+ // Vérifier que l'utilisateur a la permission de gérer les rôles
20
+ const hasPermission = await checkPermission('users.manage_roles');
21
+ if (!hasPermission) {
22
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 403 });
23
+ }
24
+
25
+ // Récupérer tous les profils depuis la BDD (système + personnalisés)
26
+ const roles = await prisma.customRole.findMany({
27
+ include: {
28
+ _count: {
29
+ select: { users: true },
30
+ },
31
+ },
32
+ orderBy: [
33
+ { isSystem: 'desc' }, // Profils système en premier
34
+ { createdAt: 'desc' }, // Puis les plus récents
35
+ ],
36
+ });
37
+
38
+ // Formatter les profils
39
+ const formattedRoles = roles.map((role) => ({
40
+ id: role.id,
41
+ name: role.name,
42
+ description: role.description,
43
+ permissions: role.permissions as string[],
44
+ isSystem: role.isSystem,
45
+ usersCount: role._count.users,
46
+ createdAt: role.createdAt.toISOString(),
47
+ updatedAt: role.updatedAt.toISOString(),
48
+ }));
49
+
50
+ return NextResponse.json(formattedRoles);
51
+ } catch (error) {
52
+ console.error('Erreur lors de la récupération des profils:', error);
53
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 })
54
+ }
55
+ }
56
+
57
+ // POST /api/roles - Créer un nouveau profil personnalisé
58
+ export async function POST(req: NextRequest) {
59
+ try {
60
+ const session = await auth.api.getSession({
61
+ headers: await headers(),
62
+ });
63
+
64
+ if (!session) {
65
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
66
+ }
67
+
68
+ // Vérifier que l'utilisateur a la permission de gérer les rôles
69
+ const hasPermission = await checkPermission('users.manage_roles');
70
+ if (!hasPermission) {
71
+ return NextResponse.json({ error: 'Non autorisé' }, { status: 403 });
72
+ }
73
+
74
+ const body = await req.json();
75
+ const { name, description, permissions } = body;
76
+
77
+ // Validation
78
+ if (!name || typeof name !== 'string' || name.trim().length === 0) {
79
+ return NextResponse.json({ error: 'Le nom du profil est requis' }, { status: 400 });
80
+ }
81
+
82
+ if (!Array.isArray(permissions)) {
83
+ return NextResponse.json(
84
+ { error: 'Les permissions doivent être un tableau' },
85
+ { status: 400 },
86
+ );
87
+ }
88
+
89
+ // Vérifier que le nom n'existe pas déjà
90
+ const existing = await prisma.customRole.findUnique({
91
+ where: { name: name.trim() },
92
+ });
93
+
94
+ if (existing) {
95
+ return NextResponse.json({ error: 'Un profil avec ce nom existe déjà' }, { status: 400 });
96
+ }
97
+
98
+ // Créer le profil
99
+ const newRole = await prisma.customRole.create({
100
+ data: {
101
+ name: name.trim(),
102
+ description: description?.trim() || null,
103
+ permissions: permissions,
104
+ isSystem: false,
105
+ },
106
+ });
107
+
108
+ await logAudit({
109
+ actorId: session.user.id,
110
+ action: 'ROLE_CREATED',
111
+ entityType: 'ROLE',
112
+ entityId: newRole.id,
113
+ metadata: {
114
+ name: newRole.name,
115
+ description: newRole.description,
116
+ permissions,
117
+ },
118
+ });
119
+
120
+ return NextResponse.json(
121
+ {
122
+ message: 'Profil créé avec succès',
123
+ role: {
124
+ id: newRole.id,
125
+ name: newRole.name,
126
+ description: newRole.description,
127
+ permissions: newRole.permissions,
128
+ isSystem: newRole.isSystem,
129
+ usersCount: 0,
130
+ createdAt: newRole.createdAt.toISOString(),
131
+ updatedAt: newRole.updatedAt.toISOString(),
132
+ },
133
+ },
134
+ { status: 201 },
135
+ );
136
+ } catch (error) {
137
+ console.error('Erreur lors de la création du profil:', error);
138
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
139
+ }
140
+ }