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