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.
- package/bin/create-crm-tmp.js +93 -0
- package/package.json +25 -0
- package/template/.prettierignore +33 -0
- package/template/.prettierrc.json +25 -0
- package/template/README.md +173 -0
- package/template/eslint.config.mjs +18 -0
- package/template/exemple-contacts.csv +11 -0
- package/template/next.config.ts +8 -0
- package/template/package.json +64 -0
- package/template/postcss.config.mjs +7 -0
- package/template/prisma/migrations/20251126144728_init/migration.sql +78 -0
- package/template/prisma/migrations/20251126155204_add_user_roles/migration.sql +5 -0
- package/template/prisma/migrations/20251128095126_add_company_info/migration.sql +19 -0
- package/template/prisma/migrations/20251128123321_add_smtp_config/migration.sql +22 -0
- package/template/prisma/migrations/20251128132303_add_status/migration.sql +23 -0
- package/template/prisma/migrations/20251201102207_add_user_active/migration.sql +75 -0
- package/template/prisma/migrations/20251201105507_add_email_signature/migration.sql +2 -0
- package/template/prisma/migrations/20251201151122_add_tasks/migration.sql +45 -0
- package/template/prisma/migrations/20251202111854_add_task_reminder/migration.sql +2 -0
- package/template/prisma/migrations/20251202135859_add_google_meet_integration/migration.sql +27 -0
- package/template/prisma/migrations/20251203103317_add_meta_lead_integration/migration.sql +20 -0
- package/template/prisma/migrations/20251203104002_add_google_ads_integration/migration.sql +18 -0
- package/template/prisma/migrations/20251203112122_add_google_sheet_integration/migration.sql +32 -0
- package/template/prisma/migrations/20251203153853_allow_multiple_integration_configs/migration.sql +20 -0
- package/template/prisma/migrations/20251205141705_update_user_roles/migration.sql +12 -0
- package/template/prisma/migrations/20251205150000_add_commercial_and_telepro_assignment/migration.sql +21 -0
- package/template/prisma/migrations/20251205160000_add_interaction_logging/migration.sql +11 -0
- package/template/prisma/migrations/20251208090314_add_automatic_interaction_types/migration.sql +12 -0
- package/template/prisma/migrations/20251208094843_mg/migration.sql +14 -0
- package/template/prisma/migrations/20251208100000_add_company_support/migration.sql +14 -0
- package/template/prisma/migrations/20251208110000_add_templates/migration.sql +26 -0
- package/template/prisma/migrations/20251208141304_add_video_conference_task_type/migration.sql +2 -0
- package/template/prisma/migrations/20251209104759_add_internal_note_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251209134803_add_company_field/migration.sql +2 -0
- package/template/prisma/migrations/20251209150000_rename_company_to_company_name/migration.sql +3 -0
- package/template/prisma/migrations/20251209150016_add_email_tracking/migration.sql +21 -0
- package/template/prisma/migrations/20251209155908_add_notify_contact_to_task/migration.sql +2 -0
- package/template/prisma/migrations/20251210110019_add_appointment_types/migration.sql +10 -0
- package/template/prisma/migrations/20251210113928_add_contact_files/migration.sql +26 -0
- package/template/prisma/migrations/20251212132339_add_custom_roles/migration.sql +24 -0
- package/template/prisma/migrations/20251215104448_add_file_interaction_types/migration.sql +11 -0
- package/template/prisma/migrations/20251215145616_add_closing_reasons/migration.sql +12 -0
- package/template/prisma/migrations/20251216140850_add_log_users/migration.sql +25 -0
- package/template/prisma/migrations/20251216151000_rename_perdu_to_ferme/migration.sql +8 -0
- package/template/prisma/migrations/20251216162318_add_column_mappings_to_google_sheet/migration.sql +2 -0
- package/template/prisma/migrations/20251216185127_add_workflows/migration.sql +80 -0
- package/template/prisma/migrations/20251216192237_add_scheduled_workflow_actions/migration.sql +32 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +582 -0
- package/template/prisma.config.ts +14 -0
- package/template/src/app/(auth)/invite/[token]/page.tsx +200 -0
- package/template/src/app/(auth)/layout.tsx +3 -0
- package/template/src/app/(auth)/reset-password/complete/page.tsx +213 -0
- package/template/src/app/(auth)/reset-password/page.tsx +146 -0
- package/template/src/app/(auth)/reset-password/verify/page.tsx +183 -0
- package/template/src/app/(auth)/signin/page.tsx +166 -0
- package/template/src/app/(dashboard)/agenda/page.tsx +3051 -0
- package/template/src/app/(dashboard)/automatisation/[id]/page.tsx +24 -0
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +905 -0
- package/template/src/app/(dashboard)/automatisation/new/page.tsx +20 -0
- package/template/src/app/(dashboard)/automatisation/page.tsx +337 -0
- package/template/src/app/(dashboard)/closing/page.tsx +1052 -0
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +6028 -0
- package/template/src/app/(dashboard)/contacts/page.tsx +3713 -0
- package/template/src/app/(dashboard)/dashboard/page.tsx +186 -0
- package/template/src/app/(dashboard)/layout.tsx +30 -0
- package/template/src/app/(dashboard)/settings/page.tsx +4070 -0
- package/template/src/app/(dashboard)/templates/page.tsx +567 -0
- package/template/src/app/(dashboard)/users/list/page.tsx +507 -0
- package/template/src/app/(dashboard)/users/page.tsx +457 -0
- package/template/src/app/(dashboard)/users/permissions/page.tsx +181 -0
- package/template/src/app/(dashboard)/users/roles/page.tsx +434 -0
- package/template/src/app/api/audit-logs/route.ts +57 -0
- package/template/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/src/app/api/auth/check-active/route.ts +31 -0
- package/template/src/app/api/auth/google/callback/route.ts +94 -0
- package/template/src/app/api/auth/google/disconnect/route.ts +32 -0
- package/template/src/app/api/auth/google/route.ts +34 -0
- package/template/src/app/api/auth/google/status/route.ts +32 -0
- package/template/src/app/api/closing-reasons/route.ts +27 -0
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +94 -0
- package/template/src/app/api/contacts/[id]/files/route.ts +269 -0
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +91 -0
- package/template/src/app/api/contacts/[id]/interactions/route.ts +103 -0
- package/template/src/app/api/contacts/[id]/meet/route.ts +296 -0
- package/template/src/app/api/contacts/[id]/route.ts +322 -0
- package/template/src/app/api/contacts/[id]/send-email/route.ts +254 -0
- package/template/src/app/api/contacts/export/route.ts +270 -0
- package/template/src/app/api/contacts/import/route.ts +381 -0
- package/template/src/app/api/contacts/route.ts +283 -0
- package/template/src/app/api/dashboard/stats/route.ts +299 -0
- package/template/src/app/api/email/track/[id]/route.ts +68 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +526 -0
- package/template/src/app/api/invite/complete/route.ts +88 -0
- package/template/src/app/api/invite/validate/route.ts +55 -0
- package/template/src/app/api/reminders/route.ts +95 -0
- package/template/src/app/api/reset-password/complete/route.ts +73 -0
- package/template/src/app/api/reset-password/request/route.ts +84 -0
- package/template/src/app/api/reset-password/validate/route.ts +49 -0
- package/template/src/app/api/reset-password/verify/route.ts +74 -0
- package/template/src/app/api/roles/[id]/route.ts +183 -0
- package/template/src/app/api/roles/route.ts +140 -0
- package/template/src/app/api/send/route.ts +282 -0
- package/template/src/app/api/settings/change-password/route.ts +95 -0
- package/template/src/app/api/settings/closing-reasons/[id]/route.ts +84 -0
- package/template/src/app/api/settings/closing-reasons/route.ts +74 -0
- package/template/src/app/api/settings/company/route.ts +121 -0
- package/template/src/app/api/settings/google-ads/[id]/route.ts +117 -0
- package/template/src/app/api/settings/google-ads/route.ts +122 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +230 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +196 -0
- package/template/src/app/api/settings/google-sheet/route.ts +254 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +123 -0
- package/template/src/app/api/settings/meta-leads/route.ts +132 -0
- package/template/src/app/api/settings/profile/route.ts +42 -0
- package/template/src/app/api/settings/smtp/route.ts +130 -0
- package/template/src/app/api/settings/smtp/test/route.ts +121 -0
- package/template/src/app/api/settings/statuses/[id]/route.ts +101 -0
- package/template/src/app/api/settings/statuses/route.ts +83 -0
- package/template/src/app/api/statuses/route.ts +25 -0
- package/template/src/app/api/tasks/[id]/attendees/route.ts +76 -0
- package/template/src/app/api/tasks/[id]/route.ts +728 -0
- package/template/src/app/api/tasks/meet/route.ts +240 -0
- package/template/src/app/api/tasks/route.ts +417 -0
- package/template/src/app/api/templates/[id]/route.ts +140 -0
- package/template/src/app/api/templates/route.ts +91 -0
- package/template/src/app/api/users/[id]/route.ts +168 -0
- package/template/src/app/api/users/list/route.ts +45 -0
- package/template/src/app/api/users/me/route.ts +48 -0
- package/template/src/app/api/users/route.ts +250 -0
- package/template/src/app/api/webhooks/google-ads/route.ts +208 -0
- package/template/src/app/api/webhooks/meta-leads/route.ts +258 -0
- package/template/src/app/api/workflows/[id]/route.ts +192 -0
- package/template/src/app/api/workflows/process/route.ts +293 -0
- package/template/src/app/api/workflows/route.ts +124 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +1416 -0
- package/template/src/app/layout.tsx +31 -0
- package/template/src/app/page.tsx +32 -0
- package/template/src/components/dashboard/activity-chart.tsx +67 -0
- package/template/src/components/dashboard/contacts-chart.tsx +63 -0
- package/template/src/components/dashboard/recent-activity.tsx +164 -0
- package/template/src/components/dashboard/sales-analytics-chart.tsx +81 -0
- package/template/src/components/dashboard/stat-card.tsx +61 -0
- package/template/src/components/dashboard/status-distribution-chart.tsx +45 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +88 -0
- package/template/src/components/dashboard/top-contacts-list.tsx +129 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +126 -0
- package/template/src/components/editor.tsx +856 -0
- package/template/src/components/email-template.tsx +35 -0
- package/template/src/components/header.tsx +320 -0
- package/template/src/components/invitation-email-template.tsx +79 -0
- package/template/src/components/meet-cancellation-email-template.tsx +120 -0
- package/template/src/components/meet-confirmation-email-template.tsx +156 -0
- package/template/src/components/meet-update-email-template.tsx +209 -0
- package/template/src/components/page-header.tsx +61 -0
- package/template/src/components/reset-password-email-template.tsx +79 -0
- package/template/src/components/sidebar.tsx +294 -0
- package/template/src/components/skeleton.tsx +380 -0
- package/template/src/components/ui/commands.tsx +396 -0
- package/template/src/components/ui/components.tsx +150 -0
- package/template/src/components/ui/theme.tsx +5 -0
- package/template/src/components/view-as-banner.tsx +45 -0
- package/template/src/components/view-as-modal.tsx +186 -0
- package/template/src/contexts/mobile-menu-context.tsx +31 -0
- package/template/src/contexts/sidebar-context.tsx +107 -0
- package/template/src/contexts/task-reminder-context.tsx +239 -0
- package/template/src/contexts/view-as-context.tsx +84 -0
- package/template/src/hooks/use-user-role.ts +82 -0
- package/template/src/lib/audit-log.ts +45 -0
- package/template/src/lib/auth-client.ts +16 -0
- package/template/src/lib/auth.ts +35 -0
- package/template/src/lib/check-permission.ts +193 -0
- package/template/src/lib/contact-duplicate.ts +112 -0
- package/template/src/lib/contact-interactions.ts +371 -0
- package/template/src/lib/encryption.ts +99 -0
- package/template/src/lib/google-calendar.ts +300 -0
- package/template/src/lib/google-drive.ts +372 -0
- package/template/src/lib/permissions.ts +412 -0
- package/template/src/lib/prisma.ts +32 -0
- package/template/src/lib/roles.ts +120 -0
- package/template/src/lib/template-variables.ts +76 -0
- package/template/src/lib/utils.ts +46 -0
- package/template/src/lib/workflow-executor.ts +482 -0
- package/template/src/proxy.ts +91 -0
- package/template/tsconfig.json +34 -0
- 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
|
+
}
|