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,32 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ /**
6
+ * GET /api/auth/google/status
7
+ * Récupère le statut de connexion Google de l'utilisateur
8
+ */
9
+ export async function GET(request: NextRequest) {
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 googleAccount = await prisma.userGoogleAccount.findUnique({
20
+ where: { userId: session.user.id },
21
+ select: { email: true },
22
+ });
23
+
24
+ return NextResponse.json({
25
+ connected: !!googleAccount,
26
+ email: googleAccount?.email || null,
27
+ });
28
+ } catch (error: any) {
29
+ console.error('Erreur lors de la récupération du statut Google:', error);
30
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
31
+ }
32
+ }
@@ -0,0 +1,27 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { prisma } from '@/lib/prisma';
3
+ import { auth } from '@/lib/auth';
4
+
5
+ // GET /api/closing-reasons - Liste des motifs de fermeture (tout utilisateur authentifié)
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 reasons = await prisma.closingReason.findMany({
17
+ orderBy: { name: 'asc' },
18
+ });
19
+
20
+ return NextResponse.json(reasons);
21
+ } catch (error: any) {
22
+ console.error('Erreur lors de la récupération des motifs de fermeture:', error);
23
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
24
+ }
25
+ }
26
+
27
+
@@ -0,0 +1,94 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+ import { deleteFileFromDrive } from '@/lib/google-drive';
5
+ import { logFileDeleted } from '@/lib/contact-interactions';
6
+
7
+ // DELETE /api/contacts/[id]/files/[fileId] - Supprimer un fichier
8
+ export async function DELETE(
9
+ request: NextRequest,
10
+ { params }: { params: Promise<{ id: string; fileId: string }> },
11
+ ) {
12
+ try {
13
+ const session = await auth.api.getSession({
14
+ headers: request.headers,
15
+ });
16
+
17
+ if (!session) {
18
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
19
+ }
20
+
21
+ const { id: contactId, fileId } = await params;
22
+
23
+ // Vérifier que le contact existe
24
+ const contact = await prisma.contact.findUnique({
25
+ where: { id: contactId },
26
+ });
27
+
28
+ if (!contact) {
29
+ return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
30
+ }
31
+
32
+ // Vérifier que le fichier existe et appartient au contact
33
+ const file = await prisma.contactFile.findUnique({
34
+ where: { id: fileId },
35
+ });
36
+
37
+ if (!file) {
38
+ return NextResponse.json({ error: 'Fichier non trouvé' }, { status: 404 });
39
+ }
40
+
41
+ if (file.contactId !== contactId) {
42
+ return NextResponse.json({ error: 'Fichier non associé à ce contact' }, { status: 403 });
43
+ }
44
+
45
+ // Vérifier les permissions (seul l'uploader ou un admin peut supprimer)
46
+ const user = await prisma.user.findUnique({
47
+ where: { id: session.user.id },
48
+ select: { role: true },
49
+ });
50
+
51
+ if (file.uploadedById !== session.user.id && user?.role !== 'ADMIN') {
52
+ return NextResponse.json(
53
+ { error: "Vous n'avez pas la permission de supprimer ce fichier" },
54
+ { status: 403 },
55
+ );
56
+ }
57
+
58
+ // Sauvegarder les informations du fichier avant suppression pour l'interaction
59
+ const fileName = file.fileName;
60
+ const fileSize = file.fileSize;
61
+
62
+ // Supprimer le fichier de Google Drive
63
+ try {
64
+ await deleteFileFromDrive(session.user.id, file.googleDriveFileId);
65
+ } catch (error) {
66
+ console.error('Erreur lors de la suppression du fichier de Google Drive:', error);
67
+ // On continue quand même pour supprimer l'enregistrement en base
68
+ }
69
+
70
+ // Supprimer l'enregistrement de la base de données
71
+ await prisma.contactFile.delete({
72
+ where: { id: fileId },
73
+ });
74
+
75
+ // Créer une interaction pour la suppression du fichier
76
+ try {
77
+ await logFileDeleted(contactId, fileName, fileSize, session.user.id);
78
+ } catch (interactionError: any) {
79
+ console.error(
80
+ "Erreur lors de la création de l'interaction de suppression:",
81
+ interactionError,
82
+ );
83
+ // On continue même si l'interaction échoue
84
+ }
85
+
86
+ return NextResponse.json({ success: true });
87
+ } catch (error: any) {
88
+ console.error('Erreur lors de la suppression du fichier:', error);
89
+ return NextResponse.json(
90
+ { error: error.message || 'Erreur lors de la suppression du fichier' },
91
+ { status: 500 },
92
+ );
93
+ }
94
+ }
@@ -0,0 +1,269 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+ import { uploadFileToDrive, getFileInfo } from '@/lib/google-drive';
5
+ import { logFileUploaded, logFileReplaced } from '@/lib/contact-interactions';
6
+
7
+ // POST /api/contacts/[id]/files - Uploader un fichier
8
+ export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
9
+ try {
10
+ const session = await auth.api.getSession({
11
+ headers: request.headers,
12
+ });
13
+
14
+ if (!session) {
15
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
16
+ }
17
+
18
+ const { id: contactId } = await params;
19
+
20
+ // Vérifier que le contact existe
21
+ const contact = await prisma.contact.findUnique({
22
+ where: { id: contactId },
23
+ select: {
24
+ id: true,
25
+ firstName: true,
26
+ lastName: true,
27
+ },
28
+ });
29
+
30
+ if (!contact) {
31
+ return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
32
+ }
33
+
34
+ // Vérifier que l'utilisateur a un compte Google connecté
35
+ const googleAccount = await prisma.userGoogleAccount.findUnique({
36
+ where: { userId: session.user.id },
37
+ });
38
+
39
+ if (!googleAccount) {
40
+ return NextResponse.json(
41
+ {
42
+ error:
43
+ 'Aucun compte Google connecté. Veuillez connecter votre compte Google dans les paramètres.',
44
+ },
45
+ { status: 400 },
46
+ );
47
+ }
48
+
49
+ // Récupérer le FormData
50
+ const formData = await request.formData();
51
+ const file = formData.get('file') as File;
52
+
53
+ if (!file) {
54
+ return NextResponse.json({ error: 'Aucun fichier fourni' }, { status: 400 });
55
+ }
56
+
57
+ // Vérifier la taille du fichier (max 100MB)
58
+ const maxSize = 100 * 1024 * 1024; // 100MB
59
+ if (file.size > maxSize) {
60
+ return NextResponse.json(
61
+ { error: 'Le fichier est trop volumineux. Taille maximale: 100MB' },
62
+ { status: 400 },
63
+ );
64
+ }
65
+
66
+ // Nom du contact pour le dossier
67
+ const contactName =
68
+ `${contact.firstName || ''} ${contact.lastName || ''}`.trim() || `Contact ${contactId}`;
69
+
70
+ // Vérifier si un fichier avec le même nom existe déjà pour ce contact
71
+ const existingFile = await prisma.contactFile.findFirst({
72
+ where: {
73
+ contactId,
74
+ fileName: file.name,
75
+ },
76
+ });
77
+
78
+ let contactFile;
79
+
80
+ if (existingFile) {
81
+ // Si le fichier existe déjà, supprimer l'ancien fichier de Google Drive
82
+ try {
83
+ const { deleteFileFromDrive } = await import('@/lib/google-drive');
84
+ await deleteFileFromDrive(session.user.id, existingFile.googleDriveFileId);
85
+ } catch (error) {
86
+ console.error("Erreur lors de la suppression de l'ancien fichier:", error);
87
+ // On continue même si la suppression échoue
88
+ }
89
+
90
+ // Uploader le nouveau fichier vers Google Drive
91
+ const { fileId } = await uploadFileToDrive(session.user.id, contactId, contactName, file);
92
+
93
+ // Mettre à jour l'enregistrement existant
94
+ contactFile = await prisma.contactFile.update({
95
+ where: { id: existingFile.id },
96
+ data: {
97
+ fileSize: file.size,
98
+ mimeType: file.type || 'application/octet-stream',
99
+ googleDriveFileId: fileId,
100
+ uploadedById: session.user.id,
101
+ updatedAt: new Date(),
102
+ },
103
+ include: {
104
+ uploadedBy: {
105
+ select: {
106
+ id: true,
107
+ name: true,
108
+ email: true,
109
+ },
110
+ },
111
+ },
112
+ });
113
+
114
+ // Créer une interaction pour le remplacement du fichier
115
+ try {
116
+ await logFileReplaced(contactId, contactFile.id, file.name, file.size, session.user.id);
117
+ } catch (interactionError: any) {
118
+ console.error(
119
+ "Erreur lors de la création de l'interaction de remplacement:",
120
+ interactionError,
121
+ );
122
+ // On continue même si l'interaction échoue
123
+ }
124
+ } else {
125
+ // Uploader le fichier vers Google Drive
126
+ const { fileId } = await uploadFileToDrive(session.user.id, contactId, contactName, file);
127
+
128
+ // Créer un nouvel enregistrement dans la base de données
129
+ contactFile = await prisma.contactFile.create({
130
+ data: {
131
+ contactId,
132
+ fileName: file.name,
133
+ fileSize: file.size,
134
+ mimeType: file.type || 'application/octet-stream',
135
+ googleDriveFileId: fileId,
136
+ uploadedById: session.user.id,
137
+ },
138
+ include: {
139
+ uploadedBy: {
140
+ select: {
141
+ id: true,
142
+ name: true,
143
+ email: true,
144
+ },
145
+ },
146
+ },
147
+ });
148
+
149
+ // Créer une interaction pour l'upload du fichier (seulement si c'est un nouveau fichier)
150
+ try {
151
+ await logFileUploaded(contactId, contactFile.id, file.name, file.size, session.user.id);
152
+ } catch (interactionError: any) {
153
+ console.error("Erreur lors de la création de l'interaction d'upload:", interactionError);
154
+ // On continue même si l'interaction échoue
155
+ }
156
+ }
157
+
158
+ // Récupérer le webViewLink pour la réponse
159
+ const fileInfo = await getFileInfo(session.user.id, contactFile.googleDriveFileId);
160
+ const webViewLink = fileInfo.webViewLink;
161
+
162
+ return NextResponse.json({
163
+ id: contactFile.id,
164
+ fileName: contactFile.fileName,
165
+ fileSize: contactFile.fileSize,
166
+ mimeType: contactFile.mimeType,
167
+ webViewLink,
168
+ uploadedBy: contactFile.uploadedBy,
169
+ createdAt: contactFile.createdAt,
170
+ updatedAt: contactFile.updatedAt,
171
+ });
172
+ } catch (error: any) {
173
+ console.error("Erreur lors de l'upload du fichier:", error);
174
+ return NextResponse.json(
175
+ { error: error.message || "Erreur lors de l'upload du fichier" },
176
+ { status: 500 },
177
+ );
178
+ }
179
+ }
180
+
181
+ // GET /api/contacts/[id]/files - Lister les fichiers d'un contact
182
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
183
+ try {
184
+ const session = await auth.api.getSession({
185
+ headers: request.headers,
186
+ });
187
+
188
+ if (!session) {
189
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
190
+ }
191
+
192
+ const { id: contactId } = await params;
193
+
194
+ // Vérifier que le contact existe
195
+ const contact = await prisma.contact.findUnique({
196
+ where: { id: contactId },
197
+ });
198
+
199
+ if (!contact) {
200
+ return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
201
+ }
202
+
203
+ // Récupérer tous les fichiers du contact
204
+ const files = await prisma.contactFile.findMany({
205
+ where: { contactId },
206
+ include: {
207
+ uploadedBy: {
208
+ select: {
209
+ id: true,
210
+ name: true,
211
+ email: true,
212
+ },
213
+ },
214
+ },
215
+ orderBy: {
216
+ createdAt: 'desc',
217
+ },
218
+ });
219
+
220
+ // Pour chaque fichier, récupérer le lien de visualisation depuis Google Drive
221
+ const filesWithLinks = await Promise.all(
222
+ files.map(async (file) => {
223
+ try {
224
+ const googleAccount = await prisma.userGoogleAccount.findUnique({
225
+ where: { userId: session.user.id },
226
+ });
227
+
228
+ if (googleAccount) {
229
+ const fileInfo = await getFileInfo(session.user.id, file.googleDriveFileId);
230
+ return {
231
+ id: file.id,
232
+ fileName: file.fileName,
233
+ fileSize: file.fileSize,
234
+ mimeType: file.mimeType,
235
+ webViewLink: fileInfo.webViewLink,
236
+ uploadedBy: file.uploadedBy,
237
+ createdAt: file.createdAt,
238
+ updatedAt: file.updatedAt,
239
+ };
240
+ }
241
+ } catch (error) {
242
+ console.error(
243
+ `Erreur lors de la récupération du lien pour le fichier ${file.id}:`,
244
+ error,
245
+ );
246
+ }
247
+
248
+ return {
249
+ id: file.id,
250
+ fileName: file.fileName,
251
+ fileSize: file.fileSize,
252
+ mimeType: file.mimeType,
253
+ webViewLink: `https://drive.google.com/file/d/${file.googleDriveFileId}/view`,
254
+ uploadedBy: file.uploadedBy,
255
+ createdAt: file.createdAt,
256
+ updatedAt: file.updatedAt,
257
+ };
258
+ }),
259
+ );
260
+
261
+ return NextResponse.json(filesWithLinks);
262
+ } catch (error: any) {
263
+ console.error('Erreur lors de la récupération des fichiers:', error);
264
+ return NextResponse.json(
265
+ { error: error.message || 'Erreur lors de la récupération des fichiers' },
266
+ { status: 500 },
267
+ );
268
+ }
269
+ }
@@ -0,0 +1,91 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ // PUT /api/contacts/[id]/interactions/[interactionId] - Mettre à jour une interaction
6
+ export async function PUT(
7
+ request: NextRequest,
8
+ { params }: { params: Promise<{ id: string; interactionId: 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 { interactionId } = await params;
20
+ const body = await request.json();
21
+ const { type, title, content, date } = body;
22
+
23
+ // Vérifier que l'interaction existe
24
+ const existing = await prisma.interaction.findUnique({
25
+ where: { id: interactionId },
26
+ });
27
+
28
+ if (!existing) {
29
+ return NextResponse.json({ error: 'Interaction non trouvée' }, { status: 404 });
30
+ }
31
+
32
+ const interaction = await prisma.interaction.update({
33
+ where: { id: interactionId },
34
+ data: {
35
+ type: type || existing.type,
36
+ title: title !== undefined ? title : existing.title,
37
+ content: content || existing.content,
38
+ date: date ? new Date(date) : existing.date,
39
+ },
40
+ include: {
41
+ user: {
42
+ select: { id: true, name: true, email: true },
43
+ },
44
+ },
45
+ });
46
+
47
+ return NextResponse.json(interaction);
48
+ } catch (error: any) {
49
+ console.error("Erreur lors de la mise à jour de l'interaction:", error);
50
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
51
+ }
52
+ }
53
+
54
+ // DELETE /api/contacts/[id]/interactions/[interactionId] - Supprimer une interaction
55
+ export async function DELETE(
56
+ request: NextRequest,
57
+ { params }: { params: Promise<{ id: string; interactionId: string }> },
58
+ ) {
59
+ try {
60
+ const session = await auth.api.getSession({
61
+ headers: request.headers,
62
+ });
63
+
64
+ if (!session) {
65
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
66
+ }
67
+
68
+ const { interactionId } = await params;
69
+
70
+ // Vérifier que l'interaction existe
71
+ const existing = await prisma.interaction.findUnique({
72
+ where: { id: interactionId },
73
+ });
74
+
75
+ if (!existing) {
76
+ return NextResponse.json({ error: 'Interaction non trouvée' }, { status: 404 });
77
+ }
78
+
79
+ await prisma.interaction.delete({
80
+ where: { id: interactionId },
81
+ });
82
+
83
+ return NextResponse.json({
84
+ success: true,
85
+ message: 'Interaction supprimée avec succès',
86
+ });
87
+ } catch (error: any) {
88
+ console.error("Erreur lors de la suppression de l'interaction:", error);
89
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
90
+ }
91
+ }
@@ -0,0 +1,103 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { auth } from '@/lib/auth';
3
+ import { prisma } from '@/lib/prisma';
4
+
5
+ // GET /api/contacts/[id]/interactions - Récupérer les interactions d'un contact
6
+ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
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 { id } = await params;
17
+
18
+ // Vérifier que le contact existe
19
+ const contact = await prisma.contact.findUnique({
20
+ where: { id },
21
+ });
22
+
23
+ if (!contact) {
24
+ return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
25
+ }
26
+
27
+ const interactions = await prisma.interaction.findMany({
28
+ where: { contactId: id },
29
+ include: {
30
+ user: {
31
+ select: { id: true, name: true, email: true },
32
+ },
33
+ emailTracking: {
34
+ select: {
35
+ id: true,
36
+ openCount: true,
37
+ firstOpenedAt: true,
38
+ lastOpenedAt: true,
39
+ },
40
+ },
41
+ },
42
+ orderBy: { createdAt: 'desc' },
43
+ });
44
+
45
+ return NextResponse.json(interactions);
46
+ } catch (error: any) {
47
+ console.error('Erreur lors de la récupération des interactions:', error);
48
+ return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
49
+ }
50
+ }
51
+
52
+ // POST /api/contacts/[id]/interactions - Créer une nouvelle interaction
53
+ export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
54
+ try {
55
+ const session = await auth.api.getSession({
56
+ headers: request.headers,
57
+ });
58
+
59
+ if (!session) {
60
+ return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
61
+ }
62
+
63
+ const { id } = await params;
64
+ const body = await request.json();
65
+ const { type, title, content, date, metadata } = body;
66
+
67
+ // Validation
68
+ if (!type || !content) {
69
+ return NextResponse.json({ error: 'Le type et le contenu sont requis' }, { status: 400 });
70
+ }
71
+
72
+ // Vérifier que le contact existe
73
+ const contact = await prisma.contact.findUnique({
74
+ where: { id },
75
+ });
76
+
77
+ if (!contact) {
78
+ return NextResponse.json({ error: 'Contact non trouvé' }, { status: 404 });
79
+ }
80
+
81
+ const interaction = await prisma.interaction.create({
82
+ data: {
83
+ contactId: id,
84
+ type,
85
+ title: title || null,
86
+ content,
87
+ date: date ? new Date(date) : null,
88
+ userId: session.user.id,
89
+ metadata: metadata || undefined,
90
+ },
91
+ include: {
92
+ user: {
93
+ select: { id: true, name: true, email: true },
94
+ },
95
+ },
96
+ });
97
+
98
+ return NextResponse.json(interaction, { status: 201 });
99
+ } catch (error: any) {
100
+ console.error("Erreur lors de la création de l'interaction:", error);
101
+ return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
102
+ }
103
+ }