@consilioweb/payload-support 0.16.0 → 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/README.md +347 -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 +1604 -671
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1602 -671
- 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 +23 -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/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/SatisfactionSurveys.ts +11 -2
- 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 +129 -31
- package/src/collections/Tickets.ts +31 -73
- package/src/collections/index.ts +3 -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 +70 -32
- 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 +18 -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/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 +48 -16
- package/src/plugin.ts +6 -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/readSettings.ts +22 -6
- package/src/utils/sanitizeHtml.ts +57 -0
- package/src/utils/slugs.ts +6 -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
|
@@ -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,
|
|
@@ -4,6 +4,7 @@ import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
|
4
4
|
import { RateLimiter } from '../utils/rateLimiter'
|
|
5
5
|
import { escapeHtml, emailWrapper, emailParagraph, emailButton } from '../utils/emailTemplate'
|
|
6
6
|
import { readSupportSettings } from '../utils/readSettings'
|
|
7
|
+
import { dbFindByID, dbFind, dbCreate, dbUpdate } from '../utils/db'
|
|
7
8
|
|
|
8
9
|
// Manual reminders are a deliberate admin action, so the cap is generous —
|
|
9
10
|
// it only exists to stop a runaway script, not normal usage.
|
|
@@ -71,12 +72,11 @@ export function createSendReminderEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
71
72
|
? Math.min(MAX_HOURS, Math.max(MIN_HOURS, Math.round(rawHours)))
|
|
72
73
|
: 24
|
|
73
74
|
|
|
74
|
-
const ticket = await payload.
|
|
75
|
-
collection: slugs.tickets as any,
|
|
75
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
76
76
|
id: ticketId,
|
|
77
77
|
depth: 1,
|
|
78
78
|
overrideAccess: true,
|
|
79
|
-
})
|
|
79
|
+
})
|
|
80
80
|
|
|
81
81
|
if (!ticket) {
|
|
82
82
|
return Response.json({ error: 'Ticket introuvable' }, { status: 404 })
|
|
@@ -102,8 +102,7 @@ export function createSendReminderEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
102
102
|
// response then to ticket creation.
|
|
103
103
|
let waitingSince: string | undefined = ticket.firstResponseAt || ticket.createdAt
|
|
104
104
|
try {
|
|
105
|
-
const lastAdminMsg = await payload.
|
|
106
|
-
collection: slugs.ticketMessages as any,
|
|
105
|
+
const lastAdminMsg = await dbFind(payload, slugs.ticketMessages, {
|
|
107
106
|
where: {
|
|
108
107
|
and: [
|
|
109
108
|
{ ticket: { equals: ticketId } },
|
|
@@ -147,8 +146,7 @@ export function createSendReminderEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
147
146
|
})
|
|
148
147
|
|
|
149
148
|
// Internal note for the conversation timeline (never sent to the client).
|
|
150
|
-
await payload.
|
|
151
|
-
collection: slugs.ticketMessages as any,
|
|
149
|
+
await dbCreate(payload, slugs.ticketMessages, {
|
|
152
150
|
data: {
|
|
153
151
|
ticket: ticketId,
|
|
154
152
|
body: `Relance envoyée à ${client.email}. Fermeture automatique programmée le ${deadlineLabel} sans réponse du client.`,
|
|
@@ -162,8 +160,7 @@ export function createSendReminderEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
162
160
|
// Arm the ticket. `autoCloseRemindedAt: now` keeps the day-based cron
|
|
163
161
|
// reminder from firing a second time; `autoCloseScheduledAt` is the hard
|
|
164
162
|
// 24h-style deadline the cron close step honours.
|
|
165
|
-
await payload.
|
|
166
|
-
collection: slugs.tickets as any,
|
|
163
|
+
await dbUpdate(payload, slugs.tickets, {
|
|
167
164
|
id: ticketId,
|
|
168
165
|
data: {
|
|
169
166
|
status: 'waiting_client',
|
|
@@ -1,6 +1,8 @@
|
|
|
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 { invalidateSupportSettingsCache } from '../utils/readSettings'
|
|
5
|
+
import { dbFind } from '../utils/db'
|
|
4
6
|
|
|
5
7
|
const PREF_KEY = 'support-settings'
|
|
6
8
|
|
|
@@ -63,8 +65,7 @@ export function createSettingsGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
63
65
|
|
|
64
66
|
requireAdmin(req, slugs)
|
|
65
67
|
|
|
66
|
-
const prefs = await payload
|
|
67
|
-
collection: 'payload-preferences' as any,
|
|
68
|
+
const prefs = await dbFind(payload, 'payload-preferences', {
|
|
68
69
|
where: { key: { equals: PREF_KEY } },
|
|
69
70
|
limit: 1,
|
|
70
71
|
depth: 0,
|
|
@@ -132,6 +133,8 @@ export function createSettingsPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
132
133
|
},
|
|
133
134
|
})
|
|
134
135
|
|
|
136
|
+
invalidateSupportSettingsCache()
|
|
137
|
+
|
|
135
138
|
return Response.json(merged)
|
|
136
139
|
} catch (error) {
|
|
137
140
|
const authResponse = handleAuthError(error)
|
|
@@ -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 = 'email-signature'
|
|
6
7
|
|
|
@@ -17,8 +18,7 @@ export function createSignatureGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
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}-${req.user.id}` } },
|
|
23
23
|
limit: 1,
|
|
24
24
|
depth: 0,
|
|
@@ -56,8 +56,7 @@ export function createSignaturePostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
56
56
|
const { signature } = (await req.json!()) as { signature: string }
|
|
57
57
|
const key = `${PREF_KEY}-${req.user.id}`
|
|
58
58
|
|
|
59
|
-
const existing = await payload
|
|
60
|
-
collection: 'payload-preferences' as any,
|
|
59
|
+
const existing = await dbFind(payload, 'payload-preferences', {
|
|
61
60
|
where: { key: { equals: key } },
|
|
62
61
|
limit: 1,
|
|
63
62
|
depth: 0,
|
|
@@ -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/sla-check
|
|
@@ -19,8 +20,7 @@ export function createSlaCheckEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
19
20
|
const now = new Date()
|
|
20
21
|
const nowISO = now.toISOString()
|
|
21
22
|
|
|
22
|
-
const { docs: tickets } = await payload.
|
|
23
|
-
collection: slugs.tickets as any,
|
|
23
|
+
const { docs: tickets } = await dbFind(payload, slugs.tickets, {
|
|
24
24
|
where: {
|
|
25
25
|
and: [
|
|
26
26
|
{ status: { in: ['open', 'waiting_client'] } },
|
|
@@ -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 { dbFindByID, dbCreate, dbUpdate } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* POST /api/support/split-ticket
|
|
@@ -21,8 +22,7 @@ export function createSplitTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
21
22
|
return Response.json({ error: 'messageId required' }, { status: 400 })
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const message = await payload.
|
|
25
|
-
collection: slugs.ticketMessages as any,
|
|
25
|
+
const message = await dbFindByID(payload, slugs.ticketMessages, {
|
|
26
26
|
id: messageId,
|
|
27
27
|
depth: 1,
|
|
28
28
|
overrideAccess: true,
|
|
@@ -42,8 +42,7 @@ export function createSplitTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
42
42
|
: sourceTicket.client
|
|
43
43
|
|
|
44
44
|
// Create new ticket
|
|
45
|
-
const newTicket = await payload.
|
|
46
|
-
collection: slugs.tickets as any,
|
|
45
|
+
const newTicket = await dbCreate(payload, slugs.tickets, {
|
|
47
46
|
data: {
|
|
48
47
|
subject: subject || `Split: ${sourceTicket.subject}`,
|
|
49
48
|
client: clientId,
|
|
@@ -61,8 +60,7 @@ export function createSplitTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
61
60
|
file: typeof a.file === 'object' ? a.file.id : a.file,
|
|
62
61
|
})) || []
|
|
63
62
|
|
|
64
|
-
await payload.
|
|
65
|
-
collection: slugs.ticketMessages as any,
|
|
63
|
+
await dbCreate(payload, slugs.ticketMessages, {
|
|
66
64
|
data: {
|
|
67
65
|
ticket: newTicket.id,
|
|
68
66
|
body: message.body,
|
|
@@ -77,8 +75,7 @@ export function createSplitTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
77
75
|
})
|
|
78
76
|
|
|
79
77
|
// Add system note on source ticket
|
|
80
|
-
await payload.
|
|
81
|
-
collection: slugs.ticketMessages as any,
|
|
78
|
+
await dbCreate(payload, slugs.ticketMessages, {
|
|
82
79
|
data: {
|
|
83
80
|
ticket: sourceTicket.id,
|
|
84
81
|
body: `Message extrait vers le ticket ${newTicket.ticketNumber}`,
|
|
@@ -95,8 +92,7 @@ export function createSplitTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
95
92
|
: []
|
|
96
93
|
|
|
97
94
|
if (!existingRelated.includes(newTicket.id)) {
|
|
98
|
-
await payload.
|
|
99
|
-
collection: slugs.tickets as any,
|
|
95
|
+
await dbUpdate(payload, slugs.tickets, {
|
|
100
96
|
id: sourceTicket.id,
|
|
101
97
|
data: { relatedTickets: [...existingRelated, newTicket.id] },
|
|
102
98
|
overrideAccess: true,
|
|
@@ -104,8 +100,7 @@ export function createSplitTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
// Log activity
|
|
107
|
-
await payload.
|
|
108
|
-
collection: slugs.ticketActivityLog as any,
|
|
103
|
+
await dbCreate(payload, slugs.ticketActivityLog, {
|
|
109
104
|
data: {
|
|
110
105
|
ticket: sourceTicket.id,
|
|
111
106
|
action: 'split',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { dbFind } from '../utils/db'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* GET /api/support/statuses
|
|
@@ -17,8 +18,7 @@ export function createStatusesEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
17
18
|
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
const { docs } = await payload.
|
|
21
|
-
collection: slugs.ticketStatuses as any,
|
|
21
|
+
const { docs } = await dbFind(payload, slugs.ticketStatuses, {
|
|
22
22
|
sort: 'sortOrder',
|
|
23
23
|
limit: 100,
|
|
24
24
|
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 { dbFind, dbFindByID, dbCreate } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* POST /api/support/tickets/:id/feedback
|
|
@@ -46,8 +47,7 @@ export function createTicketFeedbackEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Find ticket and check it belongs to the requesting client
|
|
49
|
-
const ticket = (await payload.
|
|
50
|
-
collection: slugs.tickets as any,
|
|
50
|
+
const ticket = (await dbFindByID(payload, slugs.tickets, {
|
|
51
51
|
id: ticketId,
|
|
52
52
|
depth: 0,
|
|
53
53
|
overrideAccess: true,
|
|
@@ -66,8 +66,7 @@ export function createTicketFeedbackEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// Check no feedback already exists for this ticket
|
|
69
|
-
const existing = await payload
|
|
70
|
-
collection: 'ticket-feedback' as any,
|
|
69
|
+
const existing = await dbFind(payload, 'ticket-feedback', {
|
|
71
70
|
where: { ticket: { equals: ticketId } },
|
|
72
71
|
limit: 1,
|
|
73
72
|
depth: 0,
|
|
@@ -80,8 +79,7 @@ export function createTicketFeedbackEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
80
79
|
)
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
const feedback = await payload
|
|
84
|
-
collection: 'ticket-feedback' as any,
|
|
82
|
+
const feedback = await dbCreate(payload, 'ticket-feedback', {
|
|
85
83
|
data: {
|
|
86
84
|
ticket: ticketId,
|
|
87
85
|
client: req.user.id,
|
|
@@ -2,6 +2,7 @@ import type { Endpoint } from 'payload'
|
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
4
|
import { generateTicketSynthesis } from '../utils/generateTicketSynthesis'
|
|
5
|
+
import { dbFindByID } from '../utils/db'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* POST /api/support/ticket-synthesis?ticketId=X[&force=true]
|
|
@@ -38,8 +39,7 @@ export function createTicketSynthesisEndpoint(slugs: CollectionSlugs): Endpoint
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
if (!force) {
|
|
41
|
-
const existing = await payload.
|
|
42
|
-
collection: slugs.tickets as any,
|
|
42
|
+
const existing = await dbFindByID(payload, slugs.tickets, {
|
|
43
43
|
id: ticketId,
|
|
44
44
|
depth: 0,
|
|
45
45
|
overrideAccess: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { createHmac } from 'crypto'
|
|
4
|
+
import { dbFindByID, dbUpdate, dbCreate } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
// 1x1 transparent GIF (43 bytes)
|
|
6
7
|
const TRANSPARENT_GIF = Buffer.from(
|
|
@@ -34,23 +35,16 @@ export function createTrackOpenEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
34
35
|
const parsedId = ticketId ? Number(ticketId) : NaN
|
|
35
36
|
const parsedMsgId = messageId ? Number(messageId) : NaN
|
|
36
37
|
|
|
37
|
-
//
|
|
38
|
+
// Open-tracking REQUIRES a valid HMAC signature. Without one we never
|
|
39
|
+
// process (no DB writes) — this prevents enumeration/abuse of t/m params
|
|
40
|
+
// and the silent "tracking writes without auth" hole.
|
|
38
41
|
const secret = process.env.PAYLOAD_SECRET || ''
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
headers: {
|
|
46
|
-
'Content-Type': 'image/gif',
|
|
47
|
-
'Content-Length': String(TRANSPARENT_GIF.length),
|
|
48
|
-
'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
|
|
49
|
-
},
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
} else if (secret && (!sig || !ticketId || !messageId)) {
|
|
53
|
-
// Missing signature param: return GIF but don't process
|
|
42
|
+
const validSig =
|
|
43
|
+
!!secret && !!ticketId && !!messageId && !!sig &&
|
|
44
|
+
sig === generateTrackingToken(ticketId, messageId, secret)
|
|
45
|
+
|
|
46
|
+
if (!validSig) {
|
|
47
|
+
// Return transparent GIF silently (don't leak information, don't process)
|
|
54
48
|
return new Response(TRANSPARENT_GIF, {
|
|
55
49
|
status: 200,
|
|
56
50
|
headers: {
|
|
@@ -65,8 +59,7 @@ export function createTrackOpenEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
65
59
|
try {
|
|
66
60
|
const payload = req.payload
|
|
67
61
|
|
|
68
|
-
const ticket = await payload.
|
|
69
|
-
collection: slugs.tickets as any,
|
|
62
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
70
63
|
id: parsedId,
|
|
71
64
|
depth: 0,
|
|
72
65
|
overrideAccess: true,
|
|
@@ -78,8 +71,7 @@ export function createTrackOpenEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
78
71
|
const fiveMinAgo = Date.now() - 5 * 60 * 1000
|
|
79
72
|
|
|
80
73
|
if (lastRead < fiveMinAgo) {
|
|
81
|
-
await payload.
|
|
82
|
-
collection: slugs.tickets as any,
|
|
74
|
+
await dbUpdate(payload, slugs.tickets, {
|
|
83
75
|
id: parsedId,
|
|
84
76
|
data: { lastClientReadAt: new Date().toISOString() },
|
|
85
77
|
overrideAccess: true,
|
|
@@ -89,8 +81,7 @@ export function createTrackOpenEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
89
81
|
|
|
90
82
|
// Track at message level
|
|
91
83
|
if (Number.isInteger(parsedMsgId) && parsedMsgId > 0) {
|
|
92
|
-
const msg = await payload.
|
|
93
|
-
collection: slugs.ticketMessages as any,
|
|
84
|
+
const msg = await dbFindByID(payload, slugs.ticketMessages, {
|
|
94
85
|
id: parsedMsgId,
|
|
95
86
|
depth: 0,
|
|
96
87
|
overrideAccess: true,
|
|
@@ -98,15 +89,13 @@ export function createTrackOpenEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
98
89
|
}) as any
|
|
99
90
|
|
|
100
91
|
if (msg && !msg.emailOpenedAt) {
|
|
101
|
-
await payload.
|
|
102
|
-
collection: slugs.ticketMessages as any,
|
|
92
|
+
await dbUpdate(payload, slugs.ticketMessages, {
|
|
103
93
|
id: parsedMsgId,
|
|
104
94
|
data: { emailOpenedAt: new Date().toISOString() },
|
|
105
95
|
overrideAccess: true,
|
|
106
96
|
})
|
|
107
97
|
|
|
108
|
-
const ticketInfo = await payload.
|
|
109
|
-
collection: slugs.tickets as any,
|
|
98
|
+
const ticketInfo = await dbFindByID(payload, slugs.tickets, {
|
|
110
99
|
id: parsedId,
|
|
111
100
|
depth: 1,
|
|
112
101
|
overrideAccess: true,
|
|
@@ -119,8 +108,7 @@ export function createTrackOpenEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
119
108
|
|
|
120
109
|
// Try to create admin notification (collection may not exist)
|
|
121
110
|
try {
|
|
122
|
-
await payload
|
|
123
|
-
collection: 'admin-notifications' as any,
|
|
111
|
+
await dbCreate(payload, 'admin-notifications', {
|
|
124
112
|
data: {
|
|
125
113
|
title: `Email ouvert — ${ticketInfo?.ticketNumber || 'TK-????'}`,
|
|
126
114
|
message: `${clientName} a ouvert votre email pour "${ticketInfo?.subject || 'ticket'}"`,
|
|
@@ -3,6 +3,8 @@ import type { CollectionSlugs } from '../utils/slugs'
|
|
|
3
3
|
import { handleAuthError, AuthError } from '../utils/auth'
|
|
4
4
|
import { escapeHtml, emailWrapper, emailButton, emailParagraph } from '../utils/emailTemplate'
|
|
5
5
|
import { readSupportSettings } from '../utils/readSettings'
|
|
6
|
+
import { RateLimiter } from '../utils/rateLimiter'
|
|
7
|
+
import { dbFindByID, dbFind, dbCreate, dbCount } from '../utils/db'
|
|
6
8
|
|
|
7
9
|
// Simple RFC-5322 style sanity check — same level of strictness as the
|
|
8
10
|
// portal forms used elsewhere in the plugin. Server-side validation only;
|
|
@@ -11,6 +13,11 @@ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
|
11
13
|
|
|
12
14
|
const MAX_TRANSFERS_PER_DAY = 5
|
|
13
15
|
|
|
16
|
+
// In-memory fail-closed rate limiter — enforced even when the persistent
|
|
17
|
+
// emailLogs-based check is unavailable (collection disabled). Prevents the
|
|
18
|
+
// endpoint from becoming an unbounded outbound mailer.
|
|
19
|
+
const transferLimiter = new RateLimiter(24 * 60 * 60 * 1000, MAX_TRANSFERS_PER_DAY)
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* POST /api/support/tickets/:id/transfer
|
|
16
23
|
*
|
|
@@ -56,12 +63,11 @@ export function createTransferTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
56
63
|
const payload = req.payload
|
|
57
64
|
|
|
58
65
|
// Load ticket (ownership check happens here too)
|
|
59
|
-
const ticket = await payload.
|
|
60
|
-
collection: slugs.tickets as any,
|
|
66
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
61
67
|
id: ticketId,
|
|
62
68
|
depth: 1,
|
|
63
69
|
overrideAccess: true,
|
|
64
|
-
})
|
|
70
|
+
})
|
|
65
71
|
if (!ticket) return Response.json({ error: 'Ticket introuvable' }, { status: 404 })
|
|
66
72
|
|
|
67
73
|
const isAdmin = req.user.collection === slugs.users
|
|
@@ -74,11 +80,19 @@ export function createTransferTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
74
80
|
return Response.json({ error: 'Forbidden' }, { status: 403 })
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
// Fail-closed in-memory rate limit (per user + ticket): enforced even
|
|
84
|
+
// when the persistent emailLogs check below cannot run.
|
|
85
|
+
if (transferLimiter.check(`${req.user.id}:${ticketId}`)) {
|
|
86
|
+
return Response.json(
|
|
87
|
+
{ error: `Limite atteinte (${MAX_TRANSFERS_PER_DAY} transferts par 24h sur ce ticket)` },
|
|
88
|
+
{ status: 429 },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
// Rate limit: count transfers in last 24h for this ticket
|
|
78
93
|
try {
|
|
79
94
|
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
|
|
80
|
-
const existing = await payload.
|
|
81
|
-
collection: slugs.emailLogs as any,
|
|
95
|
+
const existing = await dbCount(payload, slugs.emailLogs, {
|
|
82
96
|
where: {
|
|
83
97
|
and: [
|
|
84
98
|
{ action: { equals: 'transfer' } },
|
|
@@ -99,8 +113,7 @@ export function createTransferTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
// Fetch non-internal messages, ordered chronologically
|
|
102
|
-
const messages = await payload.
|
|
103
|
-
collection: slugs.ticketMessages as any,
|
|
116
|
+
const messages = await dbFind(payload, slugs.ticketMessages, {
|
|
104
117
|
where: {
|
|
105
118
|
and: [
|
|
106
119
|
{ ticket: { equals: ticketId } },
|
|
@@ -206,8 +219,7 @@ export function createTransferTicketEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
206
219
|
|
|
207
220
|
// Log the transfer (best-effort)
|
|
208
221
|
try {
|
|
209
|
-
await payload.
|
|
210
|
-
collection: slugs.emailLogs as any,
|
|
222
|
+
await dbCreate(payload, slugs.emailLogs, {
|
|
211
223
|
data: {
|
|
212
224
|
status: 'success',
|
|
213
225
|
action: 'transfer',
|
|
@@ -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_PREFIX = 'support-user-prefs'
|
|
6
7
|
|
|
@@ -29,8 +30,7 @@ export function createUserPrefsGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
29
30
|
|
|
30
31
|
const key = `${PREF_KEY_PREFIX}-${req.user!.id}`
|
|
31
32
|
|
|
32
|
-
const prefs = await payload
|
|
33
|
-
collection: 'payload-preferences' as any,
|
|
33
|
+
const prefs = await dbFind(payload, 'payload-preferences', {
|
|
34
34
|
where: { key: { equals: key } },
|
|
35
35
|
limit: 1,
|
|
36
36
|
depth: 0,
|
|
@@ -74,8 +74,7 @@ export function createUserPrefsPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
74
74
|
const key = `${PREF_KEY_PREFIX}-${req.user!.id}`
|
|
75
75
|
|
|
76
76
|
// Read existing prefs to merge
|
|
77
|
-
const existing = await payload
|
|
78
|
-
collection: 'payload-preferences' as any,
|
|
77
|
+
const existing = await dbFind(payload, 'payload-preferences', {
|
|
79
78
|
where: { key: { equals: key } },
|
|
80
79
|
limit: 1,
|
|
81
80
|
depth: 0,
|