create-crm-tmp 2.0.0 → 2.1.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 +56 -35
- package/package.json +1 -1
- package/template/README.md +230 -115
- package/template/eslint.config.mjs +13 -0
- package/template/next.config.ts +14 -0
- package/template/package.json +15 -2
- package/template/prisma/migrations/20260318095700_init_db/migration.sql +978 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +132 -637
- package/template/src/app/(auth)/invite/[token]/page.tsx +10 -8
- package/template/src/app/(auth)/layout.tsx +1 -1
- package/template/src/app/(auth)/reset-password/complete/page.tsx +11 -8
- package/template/src/app/(auth)/reset-password/page.tsx +4 -4
- package/template/src/app/(auth)/reset-password/verify/page.tsx +4 -4
- package/template/src/app/(auth)/signin/page.tsx +14 -6
- package/template/src/app/(dashboard)/agenda/page.tsx +2243 -988
- package/template/src/app/(dashboard)/automatisation/_components/workflow-editor.tsx +18 -104
- package/template/src/app/(dashboard)/automatisation/page.tsx +10 -26
- package/template/src/app/(dashboard)/closing/page.tsx +78 -62
- package/template/src/app/(dashboard)/contacts/[id]/page.tsx +2082 -1080
- package/template/src/app/(dashboard)/contacts/companies/[id]/page.tsx +46 -47
- package/template/src/app/(dashboard)/contacts/page.tsx +1062 -780
- package/template/src/app/(dashboard)/dashboard/page.tsx +533 -37
- package/template/src/app/(dashboard)/dev/page.tsx +1291 -0
- package/template/src/app/(dashboard)/layout.tsx +6 -2
- package/template/src/app/(dashboard)/settings/page.tsx +797 -2582
- package/template/src/app/(dashboard)/templates/page.tsx +55 -54
- package/template/src/app/(dashboard)/users/list/page.tsx +51 -48
- package/template/src/app/(dashboard)/users/page.tsx +1 -1
- package/template/src/app/(dashboard)/users/permissions/page.tsx +2 -2
- package/template/src/app/(dashboard)/users/roles/page.tsx +7 -5
- package/template/src/app/api/agenda/google-events/route.ts +92 -0
- package/template/src/app/api/auth/check-active/route.ts +3 -2
- package/template/src/app/api/auth/google/route.ts +2 -1
- package/template/src/app/api/auth/google/status/route.ts +7 -31
- package/template/src/app/api/companies/[id]/activities/route.ts +1 -3
- package/template/src/app/api/companies/[id]/route.ts +1 -2
- package/template/src/app/api/companies/route.ts +42 -12
- package/template/src/app/api/contacts/[id]/files/[fileId]/preview/route.ts +9 -31
- package/template/src/app/api/contacts/[id]/files/[fileId]/route.ts +14 -32
- package/template/src/app/api/contacts/[id]/files/route.ts +112 -212
- package/template/src/app/api/contacts/[id]/interactions/[interactionId]/route.ts +27 -1
- package/template/src/app/api/contacts/[id]/interactions/route.ts +16 -16
- package/template/src/app/api/contacts/[id]/kyc/route.ts +21 -11
- package/template/src/app/api/contacts/[id]/meet/route.ts +19 -2
- package/template/src/app/api/contacts/[id]/route.ts +106 -34
- package/template/src/app/api/contacts/[id]/send-email/route.ts +27 -11
- package/template/src/app/api/contacts/[id]/workflows/run/route.ts +6 -0
- package/template/src/app/api/contacts/export/route.ts +9 -13
- package/template/src/app/api/contacts/import/route.ts +55 -25
- package/template/src/app/api/contacts/import-preview/route.ts +1 -1
- package/template/src/app/api/contacts/origins/route.ts +63 -0
- package/template/src/app/api/contacts/route.ts +153 -41
- package/template/src/app/api/cron/cleanup-editor-images/route.ts +166 -0
- package/template/src/app/api/dashboard/widgets/[id]/route.ts +44 -0
- package/template/src/app/api/dashboard/widgets/route.ts +181 -0
- package/template/src/app/api/dev/reminders/test/route.ts +114 -0
- package/template/src/app/api/editor/upload-image/route.ts +61 -0
- package/template/src/app/api/integrations/google-sheet/jobs/[jobId]/route.ts +47 -0
- package/template/src/app/api/integrations/google-sheet/jobs/usage/route.ts +50 -0
- package/template/src/app/api/integrations/google-sheet/sync/route.ts +24 -556
- package/template/src/app/api/jobs/google-sheet/process/route.ts +84 -0
- package/template/src/app/api/jobs/google-sheet/schedule/route.ts +50 -0
- package/template/src/app/api/reminders/clear/route.ts +120 -0
- package/template/src/app/api/reminders/clear/undo/route.ts +112 -0
- package/template/src/app/api/reminders/route.ts +164 -39
- package/template/src/app/api/reminders/state/route.ts +164 -0
- package/template/src/app/api/reset-password/request/route.ts +1 -1
- package/template/src/app/api/reset-password/verify/route.ts +1 -1
- package/template/src/app/api/send/route.ts +16 -4
- package/template/src/app/api/settings/google-ads/route.ts +14 -0
- package/template/src/app/api/settings/google-calendar/calendars/route.ts +97 -0
- package/template/src/app/api/settings/google-calendar/route.ts +124 -0
- package/template/src/app/api/settings/google-sheet/[id]/route.ts +28 -0
- package/template/src/app/api/settings/google-sheet/auto-map/route.ts +37 -4
- package/template/src/app/api/settings/google-sheet/preview/route.ts +9 -3
- package/template/src/app/api/settings/google-sheet/route.ts +14 -0
- package/template/src/app/api/settings/integrations/logs/route.ts +93 -0
- package/template/src/app/api/settings/integrations/notifications/route.ts +67 -0
- package/template/src/app/api/settings/meta-leads/[id]/route.ts +0 -1
- package/template/src/app/api/settings/meta-leads/route.ts +14 -2
- package/template/src/app/api/settings/smtp/route.ts +53 -6
- package/template/src/app/api/tasks/[id]/attendees/route.ts +24 -8
- package/template/src/app/api/tasks/[id]/route.ts +234 -58
- package/template/src/app/api/tasks/meet/route.ts +27 -19
- package/template/src/app/api/tasks/route.ts +62 -17
- package/template/src/app/api/users/[id]/route.ts +20 -14
- package/template/src/app/api/users/list/route.ts +57 -19
- package/template/src/app/api/webhooks/google-ads/route.ts +34 -14
- package/template/src/app/api/webhooks/meta-leads/route.ts +32 -12
- package/template/src/app/api/workflows/[id]/route.ts +0 -4
- package/template/src/app/api/workflows/process/route.ts +22 -51
- package/template/src/app/api/workflows/route.ts +0 -4
- package/template/src/app/globals.css +342 -4
- package/template/src/app/layout.tsx +11 -3
- package/template/src/app/page.tsx +1 -1
- package/template/src/components/address-autocomplete.tsx +7 -6
- package/template/src/components/config-error-alert.tsx +46 -0
- package/template/src/components/contacts/filter-bar.tsx +12 -3
- package/template/src/components/contacts/filter-builder.tsx +28 -43
- package/template/src/components/contacts/save-view-dialog.tsx +1 -1
- package/template/src/components/contacts/views-tab-bar.tsx +15 -6
- package/template/src/components/dashboard/activity-chart.tsx +41 -28
- package/template/src/components/dashboard/add-widget-dialog.tsx +157 -0
- package/template/src/components/dashboard/color-picker.tsx +64 -0
- package/template/src/components/dashboard/contacts-chart.tsx +69 -0
- package/template/src/components/dashboard/interactions-by-type-chart.tsx +121 -0
- package/template/src/components/dashboard/recent-activity.tsx +154 -0
- package/template/src/components/dashboard/stat-card.tsx +40 -40
- package/template/src/components/dashboard/status-distribution-chart.tsx +81 -0
- package/template/src/components/dashboard/tasks-pie-chart.tsx +37 -34
- package/template/src/components/dashboard/top-contacts-list.tsx +113 -0
- package/template/src/components/dashboard/upcoming-tasks-list.tsx +72 -81
- package/template/src/components/dashboard/widget-wrapper.tsx +36 -0
- package/template/src/components/date-picker.tsx +9 -6
- package/template/src/components/editor/upload-editor-image.ts +42 -0
- package/template/src/components/editor.tsx +161 -22
- package/template/src/components/email-template.tsx +2 -2
- package/template/src/components/global-search.tsx +30 -28
- package/template/src/components/header.tsx +178 -80
- package/template/src/components/inactive-account-guard.tsx +58 -0
- package/template/src/components/integration-notifications-listener.tsx +12 -0
- package/template/src/components/invitation-email-template.tsx +2 -2
- package/template/src/components/meet-cancellation-email-template.tsx +3 -3
- package/template/src/components/meet-confirmation-email-template.tsx +3 -3
- package/template/src/components/meet-update-email-template.tsx +3 -3
- package/template/src/components/page-header.tsx +5 -5
- package/template/src/components/protected-page.tsx +1 -1
- package/template/src/components/reset-password-email-template.tsx +2 -2
- package/template/src/components/settings/integrations/GoogleAdsIntegration.tsx +428 -0
- package/template/src/components/settings/integrations/GoogleSheetConfigMonitoringModal.tsx +680 -0
- package/template/src/components/settings/integrations/GoogleSheetIntegration.tsx +809 -0
- package/template/src/components/settings/integrations/ImportResultDialog.tsx +124 -0
- package/template/src/components/settings/integrations/IntegrationLogPanel.tsx +57 -0
- package/template/src/components/settings/integrations/IntegrationLogsTable.tsx +186 -0
- package/template/src/components/settings/integrations/MetaLeadIntegration.tsx +451 -0
- package/template/src/components/sidebar.tsx +45 -26
- package/template/src/components/skeleton.tsx +40 -43
- package/template/src/components/ui/accordion.tsx +2 -2
- package/template/src/components/ui/alert-dialog.tsx +1 -1
- package/template/src/components/ui/button.tsx +20 -9
- package/template/src/components/ui/components.tsx +1 -1
- package/template/src/components/ui/date-picker.tsx +422 -0
- package/template/src/components/ui/datetime-picker.tsx +338 -0
- package/template/src/components/ui/status-select.tsx +271 -0
- package/template/src/components/ui/tooltip.tsx +37 -0
- package/template/src/components/view-as-modal.tsx +13 -7
- package/template/src/contexts/app-toast-context.tsx +245 -57
- package/template/src/contexts/dashboard-theme-context.tsx +53 -0
- package/template/src/contexts/sidebar-context.tsx +22 -17
- package/template/src/contexts/task-reminder-context.tsx +134 -160
- package/template/src/contexts/view-as-context.tsx +33 -6
- package/template/src/hooks/use-focus-trap.ts +2 -2
- package/template/src/hooks/useIntegrationNotifications.ts +49 -0
- package/template/src/lib/auth.ts +8 -1
- package/template/src/lib/config-links.ts +14 -0
- package/template/src/lib/contact-duplicate.ts +79 -61
- package/template/src/lib/contact-interactions.ts +21 -21
- package/template/src/lib/contact-view-filters.ts +24 -64
- package/template/src/lib/contacts-list-url.ts +190 -0
- package/template/src/lib/dashboard-stats.ts +65 -7
- package/template/src/lib/dashboard-themes.ts +135 -0
- package/template/src/lib/date-utils.ts +127 -0
- package/template/src/lib/default-widgets.ts +12 -0
- package/template/src/lib/editor-html-image-dimensions.ts +172 -0
- package/template/src/lib/editor-image-limits.ts +19 -0
- package/template/src/lib/email-html-sanitize.ts +19 -0
- package/template/src/lib/encryption.ts +9 -6
- package/template/src/lib/fr-geography.ts +192 -0
- package/template/src/lib/google-calendar-agenda.ts +201 -0
- package/template/src/lib/google-calendar.ts +255 -5
- package/template/src/lib/google-sheet-sync-jobs.ts +96 -0
- package/template/src/lib/google-sheet-sync-runner.ts +514 -0
- package/template/src/lib/integration-import-log.ts +21 -0
- package/template/src/lib/permissions.ts +40 -10
- package/template/src/lib/prisma.ts +4 -1
- package/template/src/lib/qstash.ts +65 -0
- package/template/src/lib/reminder-state-server.ts +80 -0
- package/template/src/lib/reminder-state.ts +29 -0
- package/template/src/lib/supabase-storage.ts +113 -0
- package/template/src/lib/template-variables.ts +164 -23
- package/template/src/lib/utils.ts +45 -0
- package/template/src/lib/widget-registry.ts +173 -0
- package/template/src/lib/workflow-executor.ts +16 -70
- package/template/src/proxy.ts +1 -0
- package/template/vercel.json +3 -10
- package/template/skills-lock.json +0 -25
- package/template/src/components/dashboard/dashboard-content.tsx +0 -79
- package/template/src/lib/google-drive.ts +0 -1101
- package/template/src/types/yousign.ts +0 -52
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { headers } from 'next/headers';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
6
|
+
|
|
7
|
+
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
try {
|
|
9
|
+
const session = await auth.api.getSession({
|
|
10
|
+
headers: await headers(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (!session) {
|
|
14
|
+
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const canManage = await checkPermission('dashboard.widgets.manage');
|
|
18
|
+
if (!canManage) {
|
|
19
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { id } = await params;
|
|
23
|
+
|
|
24
|
+
const widget = await prisma.dashboardWidget.findFirst({
|
|
25
|
+
where: {
|
|
26
|
+
id,
|
|
27
|
+
userId: session.user.id,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!widget) {
|
|
32
|
+
return NextResponse.json({ error: 'Widget non trouvé' }, { status: 404 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await prisma.dashboardWidget.delete({
|
|
36
|
+
where: { id },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return NextResponse.json({ success: true });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('Erreur lors de la suppression du widget:', error);
|
|
42
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { headers } from 'next/headers';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
import { DEFAULT_WIDGETS } from '@/lib/default-widgets';
|
|
6
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
7
|
+
|
|
8
|
+
export async function GET() {
|
|
9
|
+
try {
|
|
10
|
+
const session = await auth.api.getSession({
|
|
11
|
+
headers: await headers(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (!session) {
|
|
15
|
+
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const canView = await checkPermission('dashboard.view');
|
|
19
|
+
if (!canView) {
|
|
20
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let widgets = await prisma.dashboardWidget.findMany({
|
|
24
|
+
where: { userId: session.user.id },
|
|
25
|
+
orderBy: [{ y: 'asc' }, { x: 'asc' }],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (widgets.length === 0) {
|
|
29
|
+
widgets = await prisma.$transaction(
|
|
30
|
+
DEFAULT_WIDGETS.map((dw) =>
|
|
31
|
+
prisma.dashboardWidget.create({
|
|
32
|
+
data: {
|
|
33
|
+
userId: session.user.id,
|
|
34
|
+
type: dw.type,
|
|
35
|
+
x: dw.x,
|
|
36
|
+
y: dw.y,
|
|
37
|
+
w: dw.w,
|
|
38
|
+
h: dw.h,
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return NextResponse.json(widgets);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Erreur lors de la récupération des widgets:', error);
|
|
48
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function POST(req: NextRequest) {
|
|
53
|
+
try {
|
|
54
|
+
const session = await auth.api.getSession({
|
|
55
|
+
headers: await headers(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!session) {
|
|
59
|
+
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const canManage = await checkPermission('dashboard.widgets.manage');
|
|
63
|
+
if (!canManage) {
|
|
64
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const body = await req.json();
|
|
68
|
+
|
|
69
|
+
if (body.initDefault) {
|
|
70
|
+
await prisma.dashboardWidget.deleteMany({
|
|
71
|
+
where: { userId: session.user.id },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const created = await prisma.$transaction(
|
|
75
|
+
DEFAULT_WIDGETS.map((dw) =>
|
|
76
|
+
prisma.dashboardWidget.create({
|
|
77
|
+
data: {
|
|
78
|
+
userId: session.user.id,
|
|
79
|
+
type: dw.type,
|
|
80
|
+
x: dw.x,
|
|
81
|
+
y: dw.y,
|
|
82
|
+
w: dw.w,
|
|
83
|
+
h: dw.h,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return NextResponse.json(created, { status: 201 });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const {
|
|
93
|
+
type,
|
|
94
|
+
x,
|
|
95
|
+
y,
|
|
96
|
+
w: width,
|
|
97
|
+
h: height,
|
|
98
|
+
} = body as {
|
|
99
|
+
type?: string;
|
|
100
|
+
x?: number;
|
|
101
|
+
y?: number;
|
|
102
|
+
w?: number;
|
|
103
|
+
h?: number;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (!type) {
|
|
107
|
+
return NextResponse.json({ error: 'Le type de widget est requis' }, { status: 400 });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const lastWidget = await prisma.dashboardWidget.findFirst({
|
|
111
|
+
where: { userId: session.user.id },
|
|
112
|
+
orderBy: { y: 'desc' },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const newY = lastWidget ? lastWidget.y + lastWidget.h : 0;
|
|
116
|
+
|
|
117
|
+
const widget = await prisma.dashboardWidget.create({
|
|
118
|
+
data: {
|
|
119
|
+
userId: session.user.id,
|
|
120
|
+
type,
|
|
121
|
+
x: x ?? 0,
|
|
122
|
+
y: y ?? newY,
|
|
123
|
+
w: width ?? 6,
|
|
124
|
+
h: height ?? 4,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return NextResponse.json(widget, { status: 201 });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Erreur lors de la création du widget:', error);
|
|
131
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function PUT(req: NextRequest) {
|
|
136
|
+
try {
|
|
137
|
+
const session = await auth.api.getSession({
|
|
138
|
+
headers: await headers(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!session) {
|
|
142
|
+
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const canManage = await checkPermission('dashboard.widgets.manage');
|
|
146
|
+
if (!canManage) {
|
|
147
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const body = await req.json();
|
|
151
|
+
const { widgets } = body as {
|
|
152
|
+
widgets: Array<{ id: string; x: number; y: number; w: number; h: number }>;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (!widgets || !Array.isArray(widgets)) {
|
|
156
|
+
return NextResponse.json({ error: 'Format invalide' }, { status: 400 });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await prisma.$transaction(
|
|
160
|
+
widgets.map((w) =>
|
|
161
|
+
prisma.dashboardWidget.updateMany({
|
|
162
|
+
where: {
|
|
163
|
+
id: w.id,
|
|
164
|
+
userId: session.user.id,
|
|
165
|
+
},
|
|
166
|
+
data: {
|
|
167
|
+
x: w.x,
|
|
168
|
+
y: w.y,
|
|
169
|
+
w: w.w,
|
|
170
|
+
h: w.h,
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return NextResponse.json({ success: true });
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Erreur lors de la mise à jour des widgets:', error);
|
|
179
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
|
|
5
|
+
type TestReminderBody = {
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
scheduledAt?: string;
|
|
9
|
+
reminderMinutesBefore?: number;
|
|
10
|
+
sendNow?: boolean;
|
|
11
|
+
contactId?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// WARNING: This endpoint is for local development only.
|
|
15
|
+
// It is protected by NODE_ENV and an optional DEV_API_SECRET.
|
|
16
|
+
// Never expose dev endpoints in production builds.
|
|
17
|
+
export async function POST(request: NextRequest) {
|
|
18
|
+
if (
|
|
19
|
+
process.env.NODE_ENV !== 'development' ||
|
|
20
|
+
(process.env.DEV_API_SECRET &&
|
|
21
|
+
process.env.DEV_API_SECRET !== request.headers.get('x-dev-secret'))
|
|
22
|
+
) {
|
|
23
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
28
|
+
if (!session) {
|
|
29
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const body = (await request.json().catch(() => ({}))) as TestReminderBody;
|
|
33
|
+
const title = body.title?.trim() || 'Rappel de test';
|
|
34
|
+
const description = body.description?.trim() || 'Rappel généré en mode développement.';
|
|
35
|
+
const reminderMinutesBefore =
|
|
36
|
+
typeof body.reminderMinutesBefore === 'number' &&
|
|
37
|
+
Number.isInteger(body.reminderMinutesBefore) &&
|
|
38
|
+
body.reminderMinutesBefore > 0
|
|
39
|
+
? body.reminderMinutesBefore
|
|
40
|
+
: 5;
|
|
41
|
+
|
|
42
|
+
const parsedDate = body.scheduledAt ? new Date(body.scheduledAt) : null;
|
|
43
|
+
const scheduledAt =
|
|
44
|
+
parsedDate && !Number.isNaN(parsedDate.getTime())
|
|
45
|
+
? parsedDate
|
|
46
|
+
: new Date(Date.now() + (reminderMinutesBefore + 1) * 60 * 1000);
|
|
47
|
+
|
|
48
|
+
const normalizedContactId =
|
|
49
|
+
typeof body.contactId === 'string' && body.contactId.trim() !== '' ? body.contactId.trim() : null;
|
|
50
|
+
let contact:
|
|
51
|
+
| {
|
|
52
|
+
id: string;
|
|
53
|
+
firstName: string | null;
|
|
54
|
+
lastName: string | null;
|
|
55
|
+
}
|
|
56
|
+
| null = null;
|
|
57
|
+
if (normalizedContactId) {
|
|
58
|
+
contact = await prisma.contact.findUnique({
|
|
59
|
+
where: { id: normalizedContactId },
|
|
60
|
+
select: { id: true, firstName: true, lastName: true },
|
|
61
|
+
});
|
|
62
|
+
if (!contact) {
|
|
63
|
+
return NextResponse.json({ error: 'Contact introuvable.' }, { status: 404 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const task = await prisma.task.create({
|
|
68
|
+
data: {
|
|
69
|
+
type: 'OTHER',
|
|
70
|
+
title,
|
|
71
|
+
description,
|
|
72
|
+
priority: 'MEDIUM',
|
|
73
|
+
scheduledAt,
|
|
74
|
+
reminderMinutesBefore,
|
|
75
|
+
assignedUserId: session.user.id,
|
|
76
|
+
createdById: session.user.id,
|
|
77
|
+
contactId: contact?.id ?? null,
|
|
78
|
+
},
|
|
79
|
+
select: {
|
|
80
|
+
id: true,
|
|
81
|
+
title: true,
|
|
82
|
+
scheduledAt: true,
|
|
83
|
+
reminderMinutesBefore: true,
|
|
84
|
+
contact: {
|
|
85
|
+
select: {
|
|
86
|
+
id: true,
|
|
87
|
+
firstName: true,
|
|
88
|
+
lastName: true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const sendNow = body.sendNow === true;
|
|
95
|
+
const immediateNotification = sendNow
|
|
96
|
+
? {
|
|
97
|
+
tone: 'warning' as const,
|
|
98
|
+
message: `Test immédiat: ${task.title || 'Rappel'} (${task.reminderMinutesBefore} min avant)`,
|
|
99
|
+
link: task.contact ? `/contacts/${task.contact.id}` : undefined,
|
|
100
|
+
actionLabel: task.contact ? 'Ouvrir le contact' : undefined,
|
|
101
|
+
}
|
|
102
|
+
: null;
|
|
103
|
+
|
|
104
|
+
return NextResponse.json({
|
|
105
|
+
success: true,
|
|
106
|
+
task,
|
|
107
|
+
immediateNotification,
|
|
108
|
+
});
|
|
109
|
+
} catch (error: any) {
|
|
110
|
+
console.error('Erreur lors de la création du rappel de test:', error);
|
|
111
|
+
return NextResponse.json({ error: error.message || 'Erreur serveur' }, { status: 500 });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import {
|
|
4
|
+
BUCKETS,
|
|
5
|
+
buildEditorImagePath,
|
|
6
|
+
createSignedUploadUrl,
|
|
7
|
+
} from '@/lib/supabase-storage';
|
|
8
|
+
|
|
9
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
10
|
+
|
|
11
|
+
// POST /api/editor/upload-image - Créer une URL d'upload signée pour une image éditeur
|
|
12
|
+
export async function POST(request: NextRequest) {
|
|
13
|
+
try {
|
|
14
|
+
const session = await auth.api.getSession({
|
|
15
|
+
headers: request.headers,
|
|
16
|
+
});
|
|
17
|
+
if (!session) {
|
|
18
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const body = await request.json();
|
|
22
|
+
const { fileName, fileSize, mimeType } = body;
|
|
23
|
+
|
|
24
|
+
if (!fileName || typeof fileSize !== 'number' || !mimeType) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Champs requis: fileName, fileSize (number), mimeType' },
|
|
27
|
+
{ status: 400 },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
|
|
32
|
+
if (!ALLOWED_IMAGE_TYPES.includes(mimeType)) {
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: 'Seuls les formats JPEG, PNG, WebP et GIF sont acceptés' },
|
|
35
|
+
{ status: 400 },
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (fileSize > MAX_FILE_SIZE) {
|
|
40
|
+
return NextResponse.json(
|
|
41
|
+
{ error: 'Image trop volumineuse. Taille maximale: 10MB' },
|
|
42
|
+
{ status: 400 },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const storagePath = buildEditorImagePath(fileName);
|
|
47
|
+
const result = await createSignedUploadUrl(BUCKETS.EDITOR_IMAGES, storagePath);
|
|
48
|
+
|
|
49
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
50
|
+
const publicUrl = `${supabaseUrl}/storage/v1/object/public/${BUCKETS.EDITOR_IMAGES}/${storagePath}`;
|
|
51
|
+
|
|
52
|
+
return NextResponse.json({
|
|
53
|
+
...result,
|
|
54
|
+
publicUrl,
|
|
55
|
+
});
|
|
56
|
+
} catch (error: unknown) {
|
|
57
|
+
console.error("Erreur lors de la création de l'URL d'upload image:", error);
|
|
58
|
+
const message = error instanceof Error ? error.message : 'Erreur serveur';
|
|
59
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
|
|
6
|
+
export async function GET(
|
|
7
|
+
request: NextRequest,
|
|
8
|
+
{ params }: { params: Promise<{ jobId: string }> },
|
|
9
|
+
) {
|
|
10
|
+
try {
|
|
11
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
12
|
+
if (!session) {
|
|
13
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { jobId } = await params;
|
|
17
|
+
const job = await prisma.googleSheetSyncJob.findUnique({
|
|
18
|
+
where: { id: jobId },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (!job) {
|
|
22
|
+
return NextResponse.json({ error: 'Job introuvable' }, { status: 404 });
|
|
23
|
+
}
|
|
24
|
+
const isOwner = job.requestedByUserId === session.user.id;
|
|
25
|
+
const isScheduledOrSystem = job.requestedByUserId === null;
|
|
26
|
+
const canManageIntegrations = await checkPermission('integrations.google_sheets.manage');
|
|
27
|
+
if (!isOwner && !isScheduledOrSystem && !canManageIntegrations) {
|
|
28
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return NextResponse.json({
|
|
32
|
+
id: job.id,
|
|
33
|
+
status: job.status,
|
|
34
|
+
triggerType: job.triggerType,
|
|
35
|
+
configId: job.configId,
|
|
36
|
+
startedAt: job.startedAt,
|
|
37
|
+
finishedAt: job.finishedAt,
|
|
38
|
+
error: job.error,
|
|
39
|
+
result: job.result,
|
|
40
|
+
createdAt: job.createdAt,
|
|
41
|
+
updatedAt: job.updatedAt,
|
|
42
|
+
});
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error('Erreur lecture job Google Sheet:', error);
|
|
45
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { auth } from '@/lib/auth';
|
|
3
|
+
import { prisma } from '@/lib/prisma';
|
|
4
|
+
import { checkPermission } from '@/lib/check-permission';
|
|
5
|
+
|
|
6
|
+
const DAILY_SOFT_LIMIT = 800;
|
|
7
|
+
const DAILY_HARD_TARGET = 950;
|
|
8
|
+
|
|
9
|
+
export async function GET(request: NextRequest) {
|
|
10
|
+
try {
|
|
11
|
+
const session = await auth.api.getSession({ headers: request.headers });
|
|
12
|
+
if (!session) {
|
|
13
|
+
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
|
14
|
+
}
|
|
15
|
+
const canManageIntegrations = await checkPermission('integrations.google_sheets.manage');
|
|
16
|
+
if (!canManageIntegrations) {
|
|
17
|
+
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const since = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
21
|
+
const grouped = await prisma.googleSheetSyncJob.groupBy({
|
|
22
|
+
by: ['triggerType'],
|
|
23
|
+
where: { createdAt: { gte: since } },
|
|
24
|
+
_count: { _all: true },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const scheduled = grouped.find((g) => g.triggerType === 'SCHEDULED')?._count._all ?? 0;
|
|
28
|
+
const manual = grouped.find((g) => g.triggerType === 'MANUAL')?._count._all ?? 0;
|
|
29
|
+
const total = scheduled + manual;
|
|
30
|
+
|
|
31
|
+
if (total > DAILY_SOFT_LIMIT) {
|
|
32
|
+
console.warn(
|
|
33
|
+
`[GoogleSheetSync] usage 24h élevé: total=${total}, scheduled=${scheduled}, manual=${manual}, target=${DAILY_HARD_TARGET}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return NextResponse.json({
|
|
38
|
+
windowHours: 24,
|
|
39
|
+
total,
|
|
40
|
+
scheduled,
|
|
41
|
+
manual,
|
|
42
|
+
softLimit: DAILY_SOFT_LIMIT,
|
|
43
|
+
hardTarget: DAILY_HARD_TARGET,
|
|
44
|
+
remainingBeforeHardTarget: Math.max(0, DAILY_HARD_TARGET - total),
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Erreur usage jobs Google Sheet:', error);
|
|
48
|
+
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
|
49
|
+
}
|
|
50
|
+
}
|