@consilioweb/payload-support 0.16.0 → 1.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/README.md +357 -435
- package/dist/components/TicketConversation/RewriteDropdown.cjs +98 -0
- package/dist/components/TicketConversation/RewriteDropdown.js +93 -0
- package/dist/components/TicketConversation/SkeletonText.cjs +21 -0
- package/dist/components/TicketConversation/SkeletonText.js +20 -0
- package/dist/components/TicketConversation/constants.cjs +27 -0
- package/dist/components/TicketConversation/constants.js +26 -1
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +20 -0
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.js +19 -0
- package/dist/components/TicketConversation/index.cjs +10 -147
- package/dist/components/TicketConversation/index.js +4 -141
- package/dist/index.cjs +1763 -677
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1760 -677
- package/dist/views/BillingView/client.cjs +40 -54
- package/dist/views/BillingView/client.js +40 -54
- package/dist/views/ChatView/client.cjs +4 -3
- package/dist/views/ChatView/client.js +4 -3
- package/dist/views/CrmView/client.cjs +7 -7
- package/dist/views/CrmView/client.js +7 -7
- package/dist/views/EmailTrackingView/client.cjs +6 -5
- package/dist/views/EmailTrackingView/client.js +6 -5
- package/dist/views/ImportConversationView/client.cjs +4 -4
- package/dist/views/ImportConversationView/client.js +4 -4
- package/dist/views/LogsView/client.cjs +3 -2
- package/dist/views/LogsView/client.js +3 -2
- package/dist/views/NewTicketView/client.cjs +1 -1
- package/dist/views/NewTicketView/client.js +1 -1
- package/dist/views/PendingEmailsView/client.cjs +7 -6
- package/dist/views/PendingEmailsView/client.js +7 -6
- package/dist/views/SupportDashboardView/client.cjs +19 -22
- package/dist/views/SupportDashboardView/client.js +19 -22
- package/dist/views/TicketDetailView/NextActionItem.cjs +40 -0
- package/dist/views/TicketDetailView/NextActionItem.js +34 -0
- package/dist/views/TicketDetailView/RewriteDropdown.cjs +80 -0
- package/dist/views/TicketDetailView/RewriteDropdown.js +78 -0
- package/dist/views/TicketDetailView/client.cjs +59 -190
- package/dist/views/TicketDetailView/client.js +52 -183
- package/dist/views/TicketDetailView/constants.cjs +20 -0
- package/dist/views/TicketDetailView/constants.js +16 -0
- package/dist/views/TicketDetailView/helpers.cjs +23 -0
- package/dist/views/TicketDetailView/helpers.js +20 -0
- package/dist/views/TicketDetailView/types.cjs +2 -0
- package/dist/views/TicketDetailView/types.js +1 -0
- package/dist/views/TicketInboxView/client.cjs +14 -10
- package/dist/views/TicketInboxView/client.js +14 -10
- package/dist/views/TicketingSettingsView/client.cjs +101 -96
- package/dist/views/TicketingSettingsView/client.js +92 -88
- package/dist/views/TimeDashboardView/client.cjs +2 -2
- package/dist/views/TimeDashboardView/client.js +2 -2
- package/dist/views/shared/StatusPill.cjs +23 -12
- package/dist/views/shared/StatusPill.js +23 -12
- package/dist/views/shared/dateLocale.cjs +5 -0
- package/dist/views/shared/dateLocale.js +3 -0
- package/package.json +28 -15
- package/src/__tests__/generateTrackingToken.test.ts +53 -0
- package/src/__tests__/integration/ai-agent.test.ts +46 -0
- package/src/__tests__/integration/auth.test.ts +43 -0
- package/src/__tests__/integration/automation-rules.test.ts +68 -0
- package/src/__tests__/integration/buildTestPayload.ts +52 -0
- package/src/__tests__/integration/channels.test.ts +52 -0
- package/src/__tests__/integration/core-behaviors.test.ts +58 -0
- package/src/__tests__/integration/dashboard-volume.test.ts +31 -0
- package/src/__tests__/integration/digest.test.ts +48 -0
- package/src/__tests__/integration/invoice-pdf.test.ts +30 -0
- package/src/__tests__/integration/isolation.test.ts +81 -0
- package/src/__tests__/integration/mentions-invoice.test.ts +50 -0
- package/src/__tests__/integration/nps.test.ts +37 -0
- package/src/__tests__/integration/per-team-sla.test.ts +44 -0
- package/src/__tests__/integration/push.test.ts +75 -0
- package/src/__tests__/integration/sanitization.test.ts +35 -0
- package/src/__tests__/integration/sla-pause.test.ts +39 -0
- package/src/__tests__/integration/smoke.test.ts +26 -0
- package/src/__tests__/integration/snooze.test.ts +68 -0
- package/src/__tests__/integration/teams.test.ts +38 -0
- package/src/__tests__/rateLimiter.test.ts +89 -0
- package/src/__tests__/sanitizeHtml.test.ts +165 -0
- package/src/__tests__/sla.test.ts +258 -0
- package/src/collections/AutomationRules.ts +66 -0
- package/src/collections/NotificationQueue.ts +30 -0
- package/src/collections/PushSubscription.ts +28 -0
- package/src/collections/SatisfactionSurveys.ts +11 -2
- package/src/collections/SlaPolicies.ts +10 -0
- package/src/collections/SupportClients.ts +58 -1
- package/src/collections/SupportTeam.ts +27 -0
- package/src/collections/TicketActivityLog.ts +9 -4
- package/src/collections/TicketMessages.ts +140 -31
- package/src/collections/Tickets.ts +31 -73
- package/src/collections/index.ts +4 -0
- package/src/components/TicketConversation/RewriteDropdown.tsx +86 -0
- package/src/components/TicketConversation/SkeletonText.tsx +23 -0
- package/src/components/TicketConversation/constants.ts +29 -0
- package/src/components/TicketConversation/hooks/useDocumentIdFromUrl.ts +21 -0
- package/src/components/TicketConversation/index.tsx +4 -152
- package/src/endpoints/admin-chat-stream.ts +3 -4
- package/src/endpoints/admin-chat.ts +11 -20
- package/src/endpoints/admin-stats.ts +77 -35
- package/src/endpoints/ai-agent.ts +36 -0
- package/src/endpoints/apply-macro.ts +8 -14
- package/src/endpoints/auth-2fa.ts +12 -12
- package/src/endpoints/auto-close.ts +6 -10
- package/src/endpoints/billing.ts +17 -4
- package/src/endpoints/bulk-action.ts +7 -12
- package/src/endpoints/channels.ts +89 -0
- package/src/endpoints/chat-stream.ts +3 -4
- package/src/endpoints/chat.ts +7 -12
- package/src/endpoints/chatbot.ts +2 -2
- package/src/endpoints/client-intelligence.ts +4 -4
- package/src/endpoints/delete-account.ts +9 -16
- package/src/endpoints/email-stats.ts +2 -2
- package/src/endpoints/escalate.ts +2 -2
- package/src/endpoints/export-csv.ts +2 -2
- package/src/endpoints/export-data.ts +5 -8
- package/src/endpoints/import-conversation.ts +6 -10
- package/src/endpoints/index.ts +22 -1
- package/src/endpoints/invite-collaborator.ts +30 -15
- package/src/endpoints/invoice.ts +127 -0
- package/src/endpoints/kb-search.ts +2 -2
- package/src/endpoints/login.ts +12 -4
- package/src/endpoints/merge-clients.ts +14 -19
- package/src/endpoints/merge-tickets.ts +11 -18
- package/src/endpoints/oauth-google.ts +86 -39
- package/src/endpoints/pending-emails-process.ts +15 -25
- package/src/endpoints/process-digests.ts +93 -0
- package/src/endpoints/process-scheduled.ts +6 -10
- package/src/endpoints/process-snooze.ts +78 -0
- package/src/endpoints/purge-logs.ts +2 -2
- package/src/endpoints/push.ts +49 -0
- package/src/endpoints/resend-notification.ts +3 -4
- package/src/endpoints/round-robin-config.ts +3 -4
- package/src/endpoints/satisfaction.ts +21 -14
- package/src/endpoints/search.ts +5 -8
- package/src/endpoints/seed-kb.ts +3 -4
- package/src/endpoints/send-reminder.ts +6 -9
- package/src/endpoints/settings.ts +5 -2
- package/src/endpoints/signature.ts +3 -4
- package/src/endpoints/sla-check.ts +2 -2
- package/src/endpoints/split-ticket.ts +7 -12
- package/src/endpoints/statuses.ts +2 -2
- package/src/endpoints/ticket-feedback.ts +4 -6
- package/src/endpoints/ticket-synthesis.ts +2 -2
- package/src/endpoints/track-open.ts +16 -28
- package/src/endpoints/transfer-ticket.ts +21 -9
- package/src/endpoints/user-prefs.ts +3 -4
- package/src/hooks/automationRules.ts +84 -0
- package/src/hooks/checkSLA.ts +63 -17
- package/src/plugin.ts +8 -0
- package/src/portal/LiveChat.tsx +2 -63
- package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +10 -0
- package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +14 -2
- package/src/portal/auth/tickets/detail/MessageActions.tsx +37 -18
- package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +10 -0
- package/src/portal/auth/tickets/detail/TransferAndInviteActions.tsx +3 -2
- package/src/portal/auth/tickets/detail/page.tsx +2 -2
- package/src/portal/helpers.ts +46 -0
- package/src/portal/login/page.tsx +3 -8
- package/src/portal/types.ts +15 -0
- package/src/types/lucide-react.d.ts +34 -0
- package/src/types/payload-config.d.ts +10 -0
- package/src/utils/adminNotification.ts +2 -2
- package/src/utils/aiAgent.ts +147 -0
- package/src/utils/auth.ts +5 -4
- package/src/utils/channels.ts +36 -0
- package/src/utils/db.ts +73 -0
- package/src/utils/emailTemplate.ts +8 -1
- package/src/utils/fireWebhooks.ts +4 -6
- package/src/utils/generateTicketSynthesis.ts +7 -12
- package/src/utils/invoicePdf.ts +88 -0
- package/src/utils/notificationQueue.ts +36 -0
- package/src/utils/push.ts +63 -0
- package/src/utils/readSettings.ts +22 -6
- package/src/utils/sanitizeHtml.ts +57 -0
- package/src/utils/slugs.ts +8 -0
- package/src/utils/teamAccess.ts +27 -0
- package/src/utils/ticketAccess.ts +53 -0
- package/src/utils/webhookDispatcher.ts +4 -6
- package/src/views/BillingView/client.tsx +49 -37
- package/src/views/ChatView/client.tsx +4 -3
- package/src/views/CrmView/client.tsx +7 -7
- package/src/views/EmailTrackingView/client.tsx +7 -6
- package/src/views/ImportConversationView/client.tsx +4 -4
- package/src/views/LogsView/client.tsx +3 -2
- package/src/views/NewTicketView/client.tsx +1 -1
- package/src/views/PendingEmailsView/client.tsx +7 -6
- package/src/views/SupportDashboardView/client.tsx +21 -21
- package/src/views/TicketDetailView/NextActionItem.tsx +30 -0
- package/src/views/TicketDetailView/RewriteDropdown.tsx +63 -0
- package/src/views/TicketDetailView/client.tsx +54 -179
- package/src/views/TicketDetailView/constants.ts +23 -0
- package/src/views/TicketDetailView/helpers.ts +17 -0
- package/src/views/TicketDetailView/types.ts +10 -0
- package/src/views/TicketInboxView/client.tsx +16 -10
- package/src/views/TicketingSettingsView/client.tsx +90 -87
- package/src/views/TimeDashboardView/client.tsx +2 -2
- package/src/views/shared/StatusPill.tsx +24 -12
- package/src/views/shared/dateLocale.ts +5 -0
- package/src/views/shared/locales/en.json +708 -0
- package/src/views/shared/locales/fr.json +708 -0
|
@@ -3,6 +3,7 @@ import type { CollectionSlugs } from '../utils/slugs'
|
|
|
3
3
|
import { escapeHtml } from '../utils/emailTemplate'
|
|
4
4
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
5
5
|
import { readSupportSettings } from '../utils/readSettings'
|
|
6
|
+
import { dbFindByID, dbCreate, dbUpdate } from '../utils/db'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* POST /api/support/pending-emails/:id/process
|
|
@@ -30,12 +31,11 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
30
31
|
return Response.json({ error: 'Invalid action' }, { status: 400 })
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const pendingEmail = await payload.
|
|
34
|
-
collection: slugs.pendingEmails as any,
|
|
34
|
+
const pendingEmail = await dbFindByID(payload, slugs.pendingEmails, {
|
|
35
35
|
id: Number(id),
|
|
36
36
|
depth: 1,
|
|
37
37
|
overrideAccess: true,
|
|
38
|
-
})
|
|
38
|
+
})
|
|
39
39
|
|
|
40
40
|
if (!pendingEmail) {
|
|
41
41
|
return Response.json({ error: 'Pending email not found' }, { status: 404 })
|
|
@@ -49,15 +49,13 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
49
49
|
let clientDoc = typeof pendingEmail.client === 'object' ? pendingEmail.client : null
|
|
50
50
|
|
|
51
51
|
if (overrideClientId && overrideClientId !== clientDoc?.id) {
|
|
52
|
-
clientDoc = await payload.
|
|
53
|
-
collection: slugs.supportClients as any,
|
|
52
|
+
clientDoc = await dbFindByID(payload, slugs.supportClients, {
|
|
54
53
|
id: overrideClientId,
|
|
55
54
|
depth: 0,
|
|
56
55
|
overrideAccess: true,
|
|
57
56
|
})
|
|
58
57
|
|
|
59
|
-
await payload.
|
|
60
|
-
collection: slugs.pendingEmails as any,
|
|
58
|
+
await dbUpdate(payload, slugs.pendingEmails, {
|
|
61
59
|
id: Number(id),
|
|
62
60
|
data: { client: overrideClientId },
|
|
63
61
|
overrideAccess: true,
|
|
@@ -78,8 +76,7 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
78
76
|
})) || []
|
|
79
77
|
|
|
80
78
|
if (action === 'ignore') {
|
|
81
|
-
await payload.
|
|
82
|
-
collection: slugs.pendingEmails as any,
|
|
79
|
+
await dbUpdate(payload, slugs.pendingEmails, {
|
|
83
80
|
id: Number(id),
|
|
84
81
|
data: {
|
|
85
82
|
status: 'ignored',
|
|
@@ -92,8 +89,7 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
if (action === 'create_ticket') {
|
|
95
|
-
const newTicket = await payload.
|
|
96
|
-
collection: slugs.tickets as any,
|
|
92
|
+
const newTicket = await dbCreate(payload, slugs.tickets, {
|
|
97
93
|
data: {
|
|
98
94
|
subject: pendingEmail.subject,
|
|
99
95
|
client: clientId!,
|
|
@@ -103,10 +99,9 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
103
99
|
source: 'email',
|
|
104
100
|
},
|
|
105
101
|
overrideAccess: true,
|
|
106
|
-
})
|
|
102
|
+
})
|
|
107
103
|
|
|
108
|
-
await payload.
|
|
109
|
-
collection: slugs.ticketMessages as any,
|
|
104
|
+
await dbCreate(payload, slugs.ticketMessages, {
|
|
110
105
|
data: {
|
|
111
106
|
ticket: newTicket.id,
|
|
112
107
|
body: pendingEmail.body,
|
|
@@ -136,8 +131,7 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
136
131
|
console.error('[pending-email-process] Failed to send notification:', err)
|
|
137
132
|
}
|
|
138
133
|
|
|
139
|
-
await payload.
|
|
140
|
-
collection: slugs.pendingEmails as any,
|
|
134
|
+
await dbUpdate(payload, slugs.pendingEmails, {
|
|
141
135
|
id: Number(id),
|
|
142
136
|
data: {
|
|
143
137
|
status: 'processed',
|
|
@@ -160,19 +154,17 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
160
154
|
return Response.json({ error: 'ticketId is required for add_to_ticket' }, { status: 400 })
|
|
161
155
|
}
|
|
162
156
|
|
|
163
|
-
const targetTicket = await payload.
|
|
164
|
-
collection: slugs.tickets as any,
|
|
157
|
+
const targetTicket = await dbFindByID(payload, slugs.tickets, {
|
|
165
158
|
id: ticketId,
|
|
166
159
|
depth: 0,
|
|
167
160
|
overrideAccess: true,
|
|
168
|
-
})
|
|
161
|
+
})
|
|
169
162
|
|
|
170
163
|
if (!targetTicket) {
|
|
171
164
|
return Response.json({ error: 'Target ticket not found' }, { status: 404 })
|
|
172
165
|
}
|
|
173
166
|
|
|
174
|
-
await payload.
|
|
175
|
-
collection: slugs.ticketMessages as any,
|
|
167
|
+
await dbCreate(payload, slugs.ticketMessages, {
|
|
176
168
|
data: {
|
|
177
169
|
ticket: ticketId,
|
|
178
170
|
body: pendingEmail.body,
|
|
@@ -185,16 +177,14 @@ export function createPendingEmailsProcessEndpoint(slugs: CollectionSlugs): Endp
|
|
|
185
177
|
})
|
|
186
178
|
|
|
187
179
|
if (targetTicket.status === 'resolved') {
|
|
188
|
-
await payload.
|
|
189
|
-
collection: slugs.tickets as any,
|
|
180
|
+
await dbUpdate(payload, slugs.tickets, {
|
|
190
181
|
id: ticketId,
|
|
191
182
|
data: { status: 'open' },
|
|
192
183
|
overrideAccess: true,
|
|
193
184
|
})
|
|
194
185
|
}
|
|
195
186
|
|
|
196
|
-
await payload.
|
|
197
|
-
collection: slugs.pendingEmails as any,
|
|
187
|
+
await dbUpdate(payload, slugs.pendingEmails, {
|
|
198
188
|
id: Number(id),
|
|
199
189
|
data: {
|
|
200
190
|
status: 'processed',
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { dbFind, dbFindByID, dbDelete } from '../utils/db'
|
|
4
|
+
import { emailWrapper, emailParagraph, escapeHtml } from '../utils/emailTemplate'
|
|
5
|
+
import { readSupportSettings } from '../utils/readSettings'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* POST /api/support/process-digests body: { frequency?: 'daily' | 'weekly' }
|
|
9
|
+
*
|
|
10
|
+
* Drains the notification queue for clients whose `notificationFrequency` matches
|
|
11
|
+
* the requested cadence, sending each one a single recap email. Run a daily job
|
|
12
|
+
* with `frequency=daily` and a weekly job with `frequency=weekly`.
|
|
13
|
+
* Protected by the x-cron-secret header.
|
|
14
|
+
*/
|
|
15
|
+
export function createProcessDigestsEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
16
|
+
return {
|
|
17
|
+
path: '/support/process-digests',
|
|
18
|
+
method: 'post',
|
|
19
|
+
handler: async (req) => {
|
|
20
|
+
const secret = req.headers.get('x-cron-secret')
|
|
21
|
+
if (!process.env.CRON_SECRET || secret !== process.env.CRON_SECRET) {
|
|
22
|
+
return Response.json({ error: 'Non autorisé' }, { status: 401 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const payload = req.payload
|
|
27
|
+
let body: { frequency?: string } = {}
|
|
28
|
+
try { body = (await req.json!()) as { frequency?: string } } catch { /* no body */ }
|
|
29
|
+
const frequency = body.frequency === 'weekly' ? 'weekly' : 'daily'
|
|
30
|
+
|
|
31
|
+
const settings = await readSupportSettings(payload)
|
|
32
|
+
const replyTo = settings.email.replyToAddress || process.env.SUPPORT_EMAIL || ''
|
|
33
|
+
const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL || ''
|
|
34
|
+
|
|
35
|
+
// Gather all queued items, grouped by client.
|
|
36
|
+
type Item = { id: number | string; title?: string; message?: string }
|
|
37
|
+
const itemsByClient = new Map<string, Item[]>()
|
|
38
|
+
let page = 1
|
|
39
|
+
let hasMore = true
|
|
40
|
+
while (hasMore && page <= 50) {
|
|
41
|
+
const batch = await dbFind(payload, slugs.notificationQueue, { limit: 200, page, depth: 0, overrideAccess: true, sort: 'createdAt' })
|
|
42
|
+
for (const it of batch.docs) {
|
|
43
|
+
const raw = it as { client?: number | string | { id?: number | string } } & Item
|
|
44
|
+
const clientId = typeof raw.client === 'object' ? raw.client?.id : raw.client
|
|
45
|
+
if (clientId === undefined || clientId === null) continue
|
|
46
|
+
const key = String(clientId)
|
|
47
|
+
if (!itemsByClient.has(key)) itemsByClient.set(key, [])
|
|
48
|
+
itemsByClient.get(key)!.push({ id: raw.id, title: raw.title, message: raw.message })
|
|
49
|
+
}
|
|
50
|
+
hasMore = batch.hasNextPage ?? false
|
|
51
|
+
page++
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const results = { clients: 0, items: 0, errors: 0 }
|
|
55
|
+
for (const [clientId, items] of itemsByClient) {
|
|
56
|
+
try {
|
|
57
|
+
const client = await dbFindByID(payload, slugs.supportClients, { id: clientId, depth: 0, overrideAccess: true })
|
|
58
|
+
// Only deliver for clients whose cadence matches this run.
|
|
59
|
+
if (!client || client.notificationFrequency !== frequency) continue
|
|
60
|
+
|
|
61
|
+
if (client.email) {
|
|
62
|
+
const rows = items
|
|
63
|
+
.map((i) => `<tr><td style="padding:8px 0;font-size:14px;color:#1e293b;border-bottom:1px solid #f1f5f9;"><strong>${escapeHtml(i.title || '')}</strong><br/><span style="color:#64748b;font-size:13px;">${escapeHtml(i.message || '')}</span></td></tr>`)
|
|
64
|
+
.join('')
|
|
65
|
+
const html = emailWrapper('Recapitulatif de vos tickets', [
|
|
66
|
+
emailParagraph(`Bonjour <strong>${escapeHtml(client.firstName || '')}</strong>,`),
|
|
67
|
+
emailParagraph(`Voici le recapitulatif de l'activite sur vos tickets (${items.length} notification${items.length > 1 ? 's' : ''}) :`),
|
|
68
|
+
`<table cellpadding="0" cellspacing="0" border="0" width="100%">${rows}</table>`,
|
|
69
|
+
...(baseUrl ? [emailParagraph(`<a href="${baseUrl}/support/dashboard">Acceder a votre espace support</a>`)] : []),
|
|
70
|
+
].join(''), { kind: 'system' })
|
|
71
|
+
await payload.sendEmail({ to: client.email, ...(replyTo ? { replyTo } : {}), subject: `Recapitulatif support (${items.length})`, html })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Drain the queue regardless of email success (avoid resend loops).
|
|
75
|
+
for (const i of items) {
|
|
76
|
+
try { await dbDelete(payload, slugs.notificationQueue, { id: i.id, overrideAccess: true }) } catch { /* ignore */ }
|
|
77
|
+
}
|
|
78
|
+
results.clients++
|
|
79
|
+
results.items += items.length
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error('[process-digests] client error:', err)
|
|
82
|
+
results.errors++
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return Response.json({ ok: true, frequency, ...results })
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('[process-digests] Error:', err)
|
|
89
|
+
return Response.json({ error: 'Erreur interne' }, { status: 500 })
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { CollectionSlugs } from '../utils/slugs'
|
|
|
3
3
|
import { escapeHtml } from '../utils/emailTemplate'
|
|
4
4
|
import { fireWebhooks } from '../utils/fireWebhooks'
|
|
5
5
|
import { readSupportSettings } from '../utils/readSettings'
|
|
6
|
+
import { dbFind, dbFindByID, dbUpdate } from '../utils/db'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* POST /api/support/process-scheduled
|
|
@@ -29,8 +30,7 @@ export function createProcessScheduledEndpoint(slugs: CollectionSlugs): Endpoint
|
|
|
29
30
|
const results = { processed: 0, errors: 0 }
|
|
30
31
|
|
|
31
32
|
// Find all messages where scheduledAt is in the past and scheduledSent is not true
|
|
32
|
-
const scheduled = await payload.
|
|
33
|
-
collection: slugs.ticketMessages as any,
|
|
33
|
+
const scheduled = await dbFind(payload, slugs.ticketMessages, {
|
|
34
34
|
where: {
|
|
35
35
|
and: [
|
|
36
36
|
{ scheduledAt: { less_than_equal: now.toISOString() } },
|
|
@@ -53,8 +53,7 @@ export function createProcessScheduledEndpoint(slugs: CollectionSlugs): Endpoint
|
|
|
53
53
|
const ticketId = typeof msg.ticket === 'object' ? msg.ticket.id : msg.ticket
|
|
54
54
|
|
|
55
55
|
// Fetch the ticket with client data
|
|
56
|
-
const ticket = await payload.
|
|
57
|
-
collection: slugs.tickets as any,
|
|
56
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
58
57
|
id: ticketId,
|
|
59
58
|
depth: 1,
|
|
60
59
|
overrideAccess: true,
|
|
@@ -84,8 +83,7 @@ export function createProcessScheduledEndpoint(slugs: CollectionSlugs): Endpoint
|
|
|
84
83
|
})
|
|
85
84
|
|
|
86
85
|
// Record email delivery info
|
|
87
|
-
await payload.
|
|
88
|
-
collection: slugs.ticketMessages as any,
|
|
86
|
+
await dbUpdate(payload, slugs.ticketMessages, {
|
|
89
87
|
id: msg.id,
|
|
90
88
|
data: {
|
|
91
89
|
scheduledSent: true,
|
|
@@ -96,8 +94,7 @@ export function createProcessScheduledEndpoint(slugs: CollectionSlugs): Endpoint
|
|
|
96
94
|
})
|
|
97
95
|
} else {
|
|
98
96
|
// No email needed (internal note or no client email), just mark as sent
|
|
99
|
-
await payload.
|
|
100
|
-
collection: slugs.ticketMessages as any,
|
|
97
|
+
await dbUpdate(payload, slugs.ticketMessages, {
|
|
101
98
|
id: msg.id,
|
|
102
99
|
data: { scheduledSent: true },
|
|
103
100
|
overrideAccess: true,
|
|
@@ -106,8 +103,7 @@ export function createProcessScheduledEndpoint(slugs: CollectionSlugs): Endpoint
|
|
|
106
103
|
|
|
107
104
|
// Update ticket status if this is an admin reply
|
|
108
105
|
if (msg.authorType === 'admin' && !msg.isInternal) {
|
|
109
|
-
await payload.
|
|
110
|
-
collection: slugs.tickets as any,
|
|
106
|
+
await dbUpdate(payload, slugs.tickets, {
|
|
111
107
|
id: ticketId,
|
|
112
108
|
data: { status: 'waiting_client' },
|
|
113
109
|
overrideAccess: true,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { dbFind, dbUpdate, dbCreate } from '../utils/db'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* POST /api/support/process-snooze
|
|
7
|
+
*
|
|
8
|
+
* Wakes up tickets whose snooze deadline has passed: clears `snoozeUntil` so the
|
|
9
|
+
* ticket stops being filtered out of the inbox, and records a `snooze_expired`
|
|
10
|
+
* activity entry. Tickets also resurface automatically once `snoozeUntil` is in
|
|
11
|
+
* the past (the inbox query hides only future snoozes) — this cron just cleans up
|
|
12
|
+
* the stale field and leaves an audit trail.
|
|
13
|
+
*
|
|
14
|
+
* Protected by the `x-cron-secret` header (same pattern as process-scheduled).
|
|
15
|
+
*/
|
|
16
|
+
export function createProcessSnoozeEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
17
|
+
return {
|
|
18
|
+
path: '/support/process-snooze',
|
|
19
|
+
method: 'post',
|
|
20
|
+
handler: async (req) => {
|
|
21
|
+
const secret = req.headers.get('x-cron-secret')
|
|
22
|
+
const expectedSecret = process.env.CRON_SECRET
|
|
23
|
+
if (!expectedSecret || secret !== expectedSecret) {
|
|
24
|
+
return Response.json({ error: 'Non autorisé' }, { status: 401 })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const payload = req.payload
|
|
29
|
+
const nowIso = new Date().toISOString()
|
|
30
|
+
const results = { processed: 0, errors: 0 }
|
|
31
|
+
|
|
32
|
+
const due = await dbFind(payload, slugs.tickets, {
|
|
33
|
+
where: {
|
|
34
|
+
and: [
|
|
35
|
+
{ snoozeUntil: { exists: true } },
|
|
36
|
+
{ snoozeUntil: { less_than_equal: nowIso } },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
limit: 500,
|
|
40
|
+
depth: 0,
|
|
41
|
+
overrideAccess: true,
|
|
42
|
+
select: { id: true, ticketNumber: true },
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
for (const ticket of due.docs) {
|
|
46
|
+
try {
|
|
47
|
+
await dbUpdate(payload, slugs.tickets, {
|
|
48
|
+
id: (ticket as { id: number | string }).id,
|
|
49
|
+
data: { snoozeUntil: null },
|
|
50
|
+
overrideAccess: true,
|
|
51
|
+
})
|
|
52
|
+
// Audit trail — best-effort (activity-log may be unavailable).
|
|
53
|
+
try {
|
|
54
|
+
await dbCreate(payload, slugs.ticketActivityLog, {
|
|
55
|
+
data: {
|
|
56
|
+
ticket: (ticket as { id: number | string }).id,
|
|
57
|
+
action: 'snooze_expired',
|
|
58
|
+
detail: 'Snooze échu — ticket réactivé',
|
|
59
|
+
actorType: 'system',
|
|
60
|
+
},
|
|
61
|
+
overrideAccess: true,
|
|
62
|
+
})
|
|
63
|
+
} catch { /* activity log optional */ }
|
|
64
|
+
results.processed++
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('[support] Failed to wake snoozed ticket:', err)
|
|
67
|
+
results.errors++
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Response.json({ ok: true, ...results })
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('[process-snooze] Error:', err)
|
|
74
|
+
return Response.json({ error: 'Erreur interne' }, { status: 500 })
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
import { dbDelete } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* DELETE /api/support/purge-logs?collection=email-logs&days=30
|
|
@@ -32,8 +33,7 @@ export function createPurgeLogsEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
32
33
|
|
|
33
34
|
const cutoff = days > 0 ? new Date(Date.now() - days * 86400000).toISOString() : null
|
|
34
35
|
|
|
35
|
-
const result = await payload
|
|
36
|
-
collection: allowedCollections[collection] as any,
|
|
36
|
+
const result = await dbDelete(payload, allowedCollections[collection], {
|
|
37
37
|
where: cutoff
|
|
38
38
|
? { createdAt: { less_than: cutoff } }
|
|
39
39
|
: { id: { exists: true } },
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
import { dbFind, dbCreate, dbUpdate } from '../utils/db'
|
|
5
|
+
import { getVapidPublicKey } from '../utils/push'
|
|
6
|
+
|
|
7
|
+
/** GET /api/support/push/vapid-public-key — public key the browser uses to subscribe. */
|
|
8
|
+
export function createVapidKeyEndpoint(): Endpoint {
|
|
9
|
+
return {
|
|
10
|
+
path: '/support/push/vapid-public-key',
|
|
11
|
+
method: 'get',
|
|
12
|
+
handler: async () => Response.json({ publicKey: getVapidPublicKey() }),
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** POST /api/support/push/subscribe — register the current agent's browser push subscription. */
|
|
17
|
+
export function createPushSubscribeEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
18
|
+
return {
|
|
19
|
+
path: '/support/push/subscribe',
|
|
20
|
+
method: 'post',
|
|
21
|
+
handler: async (req) => {
|
|
22
|
+
try {
|
|
23
|
+
requireAdmin(req, slugs)
|
|
24
|
+
let body: { subscription?: { endpoint?: string; keys?: { p256dh?: string; auth?: string } } }
|
|
25
|
+
try { body = (await req.json!()) as typeof body } catch { return Response.json({ error: 'Invalid JSON body' }, { status: 400 }) }
|
|
26
|
+
const sub = body?.subscription
|
|
27
|
+
const endpoint = sub?.endpoint
|
|
28
|
+
const p256dh = sub?.keys?.p256dh
|
|
29
|
+
const auth = sub?.keys?.auth
|
|
30
|
+
if (!endpoint || !p256dh || !auth) {
|
|
31
|
+
return Response.json({ error: 'subscription invalide (endpoint + keys requis).' }, { status: 400 })
|
|
32
|
+
}
|
|
33
|
+
const data = { user: req.user!.id, endpoint, p256dh, auth, userAgent: req.headers.get('user-agent') || '' }
|
|
34
|
+
const existing = await dbFind(req.payload, slugs.pushSubscriptions, { where: { endpoint: { equals: endpoint } }, limit: 1, depth: 0, overrideAccess: true })
|
|
35
|
+
if (existing.docs.length > 0) {
|
|
36
|
+
await dbUpdate(req.payload, slugs.pushSubscriptions, { id: (existing.docs[0] as { id: number | string }).id, data, overrideAccess: true })
|
|
37
|
+
} else {
|
|
38
|
+
await dbCreate(req.payload, slugs.pushSubscriptions, { data, overrideAccess: true })
|
|
39
|
+
}
|
|
40
|
+
return Response.json({ ok: true })
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const authResponse = handleAuthError(error)
|
|
43
|
+
if (authResponse) return authResponse
|
|
44
|
+
console.error('[push] Error:', error)
|
|
45
|
+
return Response.json({ error: 'Erreur interne' }, { status: 500 })
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -4,6 +4,7 @@ import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
|
4
4
|
import { RateLimiter } from '../utils/rateLimiter'
|
|
5
5
|
import { escapeHtml } from '../utils/emailTemplate'
|
|
6
6
|
import { readSupportSettings } from '../utils/readSettings'
|
|
7
|
+
import { dbFindByID } from '../utils/db'
|
|
7
8
|
|
|
8
9
|
const resendLimiter = new RateLimiter(60 * 60 * 1000, 10) // 10 per hour
|
|
9
10
|
|
|
@@ -40,8 +41,7 @@ export function createResendNotificationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
40
41
|
return Response.json({ error: 'messageId requis' }, { status: 400 })
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const message = await payload.
|
|
44
|
-
collection: slugs.ticketMessages as any,
|
|
44
|
+
const message = await dbFindByID(payload, slugs.ticketMessages, {
|
|
45
45
|
id: messageId,
|
|
46
46
|
depth: 0,
|
|
47
47
|
overrideAccess: true,
|
|
@@ -52,8 +52,7 @@ export function createResendNotificationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const ticketId = typeof message.ticket === 'object' ? message.ticket.id : message.ticket
|
|
55
|
-
const ticket = await payload.
|
|
56
|
-
collection: slugs.tickets as any,
|
|
55
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
57
56
|
id: ticketId,
|
|
58
57
|
depth: 1,
|
|
59
58
|
overrideAccess: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
import { dbFind } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
const PREF_KEY = 'support-round-robin'
|
|
6
7
|
|
|
@@ -17,8 +18,7 @@ export function createRoundRobinConfigGetEndpoint(slugs: CollectionSlugs): Endpo
|
|
|
17
18
|
|
|
18
19
|
requireAdmin(req, slugs)
|
|
19
20
|
|
|
20
|
-
const prefs = await payload
|
|
21
|
-
collection: 'payload-preferences' as any,
|
|
21
|
+
const prefs = await dbFind(payload, 'payload-preferences', {
|
|
22
22
|
where: { key: { equals: PREF_KEY } },
|
|
23
23
|
limit: 1,
|
|
24
24
|
depth: 0,
|
|
@@ -55,8 +55,7 @@ export function createRoundRobinConfigPostEndpoint(slugs: CollectionSlugs): Endp
|
|
|
55
55
|
|
|
56
56
|
const { enabled } = (await req.json!()) as { enabled: boolean }
|
|
57
57
|
|
|
58
|
-
const existing = await payload
|
|
59
|
-
collection: 'payload-preferences' as any,
|
|
58
|
+
const existing = await dbFind(payload, 'payload-preferences', {
|
|
60
59
|
where: { key: { equals: PREF_KEY } },
|
|
61
60
|
limit: 1,
|
|
62
61
|
depth: 0,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireClient, handleAuthError } from '../utils/auth'
|
|
4
|
+
import { dbFindByID, dbFind, dbCreate } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* POST /api/support/satisfaction
|
|
@@ -17,13 +18,21 @@ export function createSatisfactionEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
17
18
|
requireClient(req, slugs)
|
|
18
19
|
|
|
19
20
|
const body = await req.json!()
|
|
20
|
-
const { ticketId, rating, comment } = body
|
|
21
|
+
const { ticketId, rating, nps, comment } = body
|
|
21
22
|
|
|
22
|
-
if (!ticketId
|
|
23
|
-
return Response.json(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (!ticketId) {
|
|
24
|
+
return Response.json({ error: 'ticketId est requis.' }, { status: 400 })
|
|
25
|
+
}
|
|
26
|
+
const hasRating = rating !== undefined && rating !== null
|
|
27
|
+
const hasNps = nps !== undefined && nps !== null
|
|
28
|
+
if (!hasRating && !hasNps) {
|
|
29
|
+
return Response.json({ error: 'Un rating (1-5) et/ou un nps (0-10) est requis.' }, { status: 400 })
|
|
30
|
+
}
|
|
31
|
+
if (hasRating && (!Number.isInteger(rating) || rating < 1 || rating > 5)) {
|
|
32
|
+
return Response.json({ error: 'rating doit être un entier 1-5.' }, { status: 400 })
|
|
33
|
+
}
|
|
34
|
+
if (hasNps && (!Number.isInteger(nps) || nps < 0 || nps > 10)) {
|
|
35
|
+
return Response.json({ error: 'nps doit être un entier 0-10.' }, { status: 400 })
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
if (comment && typeof comment === 'string' && comment.length > 5000) {
|
|
@@ -34,13 +43,12 @@ export function createSatisfactionEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
// Verify ticket belongs to client and is resolved
|
|
37
|
-
const ticket = await payload.
|
|
38
|
-
collection: slugs.tickets as any,
|
|
46
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
39
47
|
id: ticketId,
|
|
40
48
|
depth: 0,
|
|
41
49
|
overrideAccess: false,
|
|
42
50
|
user: req.user,
|
|
43
|
-
})
|
|
51
|
+
})
|
|
44
52
|
|
|
45
53
|
if (!ticket) {
|
|
46
54
|
return Response.json({ error: 'Ticket introuvable.' }, { status: 404 })
|
|
@@ -54,8 +62,7 @@ export function createSatisfactionEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
// Check if survey already exists
|
|
57
|
-
const existing = await payload.
|
|
58
|
-
collection: slugs.satisfactionSurveys as any,
|
|
65
|
+
const existing = await dbFind(payload, slugs.satisfactionSurveys, {
|
|
59
66
|
where: { ticket: { equals: ticketId } },
|
|
60
67
|
limit: 1,
|
|
61
68
|
depth: 0,
|
|
@@ -69,13 +76,13 @@ export function createSatisfactionEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
69
76
|
)
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
const survey = await payload.
|
|
73
|
-
collection: slugs.satisfactionSurveys as any,
|
|
79
|
+
const survey = await dbCreate(payload, slugs.satisfactionSurveys, {
|
|
74
80
|
data: {
|
|
75
81
|
source: 'ticket',
|
|
76
82
|
ticket: ticketId,
|
|
77
83
|
client: req.user.id,
|
|
78
|
-
rating: Math.round(rating),
|
|
84
|
+
...(hasRating ? { rating: Math.round(rating) } : {}),
|
|
85
|
+
...(hasNps ? { nps: Math.round(nps) } : {}),
|
|
79
86
|
...(comment ? { comment: comment.trim() } : {}),
|
|
80
87
|
},
|
|
81
88
|
overrideAccess: true,
|
package/src/endpoints/search.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
import { dbFind } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET /api/support/search?q=term
|
|
@@ -24,8 +25,7 @@ export function createSearchEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const [ticketsRes, clientsRes, messagesRes, articlesRes] = await Promise.all([
|
|
27
|
-
payload.
|
|
28
|
-
collection: slugs.tickets as any,
|
|
28
|
+
dbFind(payload, slugs.tickets, {
|
|
29
29
|
where: {
|
|
30
30
|
or: [
|
|
31
31
|
{ ticketNumber: { contains: q } },
|
|
@@ -37,8 +37,7 @@ export function createSearchEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
37
37
|
depth: 1,
|
|
38
38
|
overrideAccess: true,
|
|
39
39
|
}),
|
|
40
|
-
payload.
|
|
41
|
-
collection: slugs.supportClients as any,
|
|
40
|
+
dbFind(payload, slugs.supportClients, {
|
|
42
41
|
where: {
|
|
43
42
|
or: [
|
|
44
43
|
{ firstName: { contains: q } },
|
|
@@ -51,16 +50,14 @@ export function createSearchEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
51
50
|
depth: 0,
|
|
52
51
|
overrideAccess: true,
|
|
53
52
|
}),
|
|
54
|
-
payload.
|
|
55
|
-
collection: slugs.ticketMessages as any,
|
|
53
|
+
dbFind(payload, slugs.ticketMessages, {
|
|
56
54
|
where: { body: { contains: q } },
|
|
57
55
|
sort: '-createdAt',
|
|
58
56
|
limit: 5,
|
|
59
57
|
depth: 1,
|
|
60
58
|
overrideAccess: true,
|
|
61
59
|
}),
|
|
62
|
-
payload.
|
|
63
|
-
collection: slugs.knowledgeBase as any,
|
|
60
|
+
dbFind(payload, slugs.knowledgeBase, {
|
|
64
61
|
where: { title: { contains: q } },
|
|
65
62
|
limit: 5,
|
|
66
63
|
depth: 0,
|
package/src/endpoints/seed-kb.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
import { dbFind, dbCreate } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
const KB_ARTICLES = [
|
|
6
7
|
{
|
|
@@ -95,8 +96,7 @@ export function createSeedKbEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
95
96
|
let skipped = 0
|
|
96
97
|
|
|
97
98
|
for (const article of KB_ARTICLES) {
|
|
98
|
-
const existing = await payload.
|
|
99
|
-
collection: slugs.knowledgeBase as any,
|
|
99
|
+
const existing = await dbFind(payload, slugs.knowledgeBase, {
|
|
100
100
|
where: { slug: { equals: article.slug } },
|
|
101
101
|
limit: 1,
|
|
102
102
|
depth: 0,
|
|
@@ -126,8 +126,7 @@ export function createSeedKbEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
126
126
|
},
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
await payload.
|
|
130
|
-
collection: slugs.knowledgeBase as any,
|
|
129
|
+
await dbCreate(payload, slugs.knowledgeBase, {
|
|
131
130
|
data: {
|
|
132
131
|
title: article.title,
|
|
133
132
|
slug: article.slug,
|