@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
|
@@ -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 } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET /api/support/chat-stream?session=xxx
|
|
@@ -30,8 +31,7 @@ export function createChatStreamEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
// Verify session belongs to user
|
|
33
|
-
const existing = await req.payload.
|
|
34
|
-
collection: slugs.chatMessages as any,
|
|
34
|
+
const existing = await dbFind(req.payload, slugs.chatMessages, {
|
|
35
35
|
where: {
|
|
36
36
|
session: { equals: sessionId },
|
|
37
37
|
client: { equals: userId },
|
|
@@ -57,8 +57,7 @@ export function createChatStreamEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
57
57
|
// Poll DB every 2 seconds for new messages
|
|
58
58
|
const interval = setInterval(async () => {
|
|
59
59
|
try {
|
|
60
|
-
const messages = await req.payload.
|
|
61
|
-
collection: slugs.chatMessages as any,
|
|
60
|
+
const messages = await dbFind(req.payload, slugs.chatMessages, {
|
|
62
61
|
where: {
|
|
63
62
|
session: { equals: sessionId },
|
|
64
63
|
createdAt: { greater_than: lastCheck },
|
package/src/endpoints/chat.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { CollectionSlugs } from '../utils/slugs'
|
|
|
4
4
|
import crypto from 'crypto'
|
|
5
5
|
import { RateLimiter } from '../utils/rateLimiter'
|
|
6
6
|
import { requireClient, handleAuthError } from '../utils/auth'
|
|
7
|
+
import { dbFind, dbCreate } from '../utils/db'
|
|
7
8
|
|
|
8
9
|
const chatSessionLimiter = new RateLimiter(3_600_000, 5) // 5 sessions per hour
|
|
9
10
|
const chatMessageLimiter = new RateLimiter(60_000, 15) // 15 messages per minute
|
|
@@ -39,8 +40,7 @@ export function createChatGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
39
40
|
where.createdAt = { greater_than: after }
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const messages = await payload.
|
|
43
|
-
collection: slugs.chatMessages as any,
|
|
43
|
+
const messages = await dbFind(payload, slugs.chatMessages, {
|
|
44
44
|
where,
|
|
45
45
|
sort: 'createdAt',
|
|
46
46
|
limit: 100,
|
|
@@ -95,8 +95,7 @@ export function createChatPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
95
95
|
|
|
96
96
|
const sessionId = `chat_${crypto.randomBytes(16).toString('hex')}`
|
|
97
97
|
|
|
98
|
-
const systemMsg = await payload.
|
|
99
|
-
collection: slugs.chatMessages as any,
|
|
98
|
+
const systemMsg = await dbCreate(payload, slugs.chatMessages, {
|
|
100
99
|
data: {
|
|
101
100
|
session: sessionId,
|
|
102
101
|
client: req.user.id,
|
|
@@ -122,8 +121,7 @@ export function createChatPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
// Verify session belongs to user
|
|
125
|
-
const existing = await payload.
|
|
126
|
-
collection: slugs.chatMessages as any,
|
|
124
|
+
const existing = await dbFind(payload, slugs.chatMessages, {
|
|
127
125
|
where: { session: { equals: session }, client: { equals: req.user.id } },
|
|
128
126
|
limit: 1,
|
|
129
127
|
overrideAccess: true,
|
|
@@ -133,8 +131,7 @@ export function createChatPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
133
131
|
return Response.json({ error: 'Session invalide' }, { status: 403 })
|
|
134
132
|
}
|
|
135
133
|
|
|
136
|
-
const newMsg = await payload.
|
|
137
|
-
collection: slugs.chatMessages as any,
|
|
134
|
+
const newMsg = await dbCreate(payload, slugs.chatMessages, {
|
|
138
135
|
data: {
|
|
139
136
|
session,
|
|
140
137
|
client: req.user.id,
|
|
@@ -150,8 +147,7 @@ export function createChatPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
150
147
|
|
|
151
148
|
// Close session
|
|
152
149
|
if (action === 'close' && session) {
|
|
153
|
-
const existing = await payload.
|
|
154
|
-
collection: slugs.chatMessages as any,
|
|
150
|
+
const existing = await dbFind(payload, slugs.chatMessages, {
|
|
155
151
|
where: { session: { equals: session }, client: { equals: req.user.id } },
|
|
156
152
|
limit: 1,
|
|
157
153
|
overrideAccess: true,
|
|
@@ -161,8 +157,7 @@ export function createChatPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
161
157
|
return Response.json({ error: 'Session invalide' }, { status: 403 })
|
|
162
158
|
}
|
|
163
159
|
|
|
164
|
-
await payload.
|
|
165
|
-
collection: slugs.chatMessages as any,
|
|
160
|
+
await dbCreate(payload, slugs.chatMessages, {
|
|
166
161
|
data: {
|
|
167
162
|
session,
|
|
168
163
|
client: req.user.id,
|
package/src/endpoints/chatbot.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { RateLimiter } from '../utils/rateLimiter'
|
|
4
|
+
import { dbFind } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
const chatbotLimiter = new RateLimiter(60_000, 10) // 10 requests per minute per IP
|
|
6
7
|
|
|
@@ -34,8 +35,7 @@ export function createChatbotEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
34
35
|
|
|
35
36
|
const payload = req.payload
|
|
36
37
|
|
|
37
|
-
const articles = await payload.
|
|
38
|
-
collection: slugs.knowledgeBase as any,
|
|
38
|
+
const articles = await dbFind(payload, slugs.knowledgeBase, {
|
|
39
39
|
where: { published: { equals: true } },
|
|
40
40
|
limit: 100,
|
|
41
41
|
depth: 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Endpoint } from 'payload'
|
|
1
|
+
import type { Endpoint, PayloadRequest } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
4
|
import { readSupportSettings, type SupportSettings } from '../utils/readSettings'
|
|
@@ -26,7 +26,7 @@ const CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
|
26
26
|
* Force-refreshes the summary.
|
|
27
27
|
*/
|
|
28
28
|
export function createClientIntelligenceEndpoint(slugs: CollectionSlugs): Endpoint[] {
|
|
29
|
-
const getHandler = async (req:
|
|
29
|
+
const getHandler = async (req: PayloadRequest) => {
|
|
30
30
|
try {
|
|
31
31
|
requireAdmin(req, slugs)
|
|
32
32
|
const payload = req.payload
|
|
@@ -61,7 +61,7 @@ export function createClientIntelligenceEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const postHandler = async (req:
|
|
64
|
+
const postHandler = async (req: PayloadRequest) => {
|
|
65
65
|
try {
|
|
66
66
|
requireAdmin(req, slugs)
|
|
67
67
|
const payload = req.payload
|
|
@@ -97,7 +97,7 @@ async function generateSummary(
|
|
|
97
97
|
payload: any,
|
|
98
98
|
clientId: string,
|
|
99
99
|
slugs: CollectionSlugs,
|
|
100
|
-
existingId?: number,
|
|
100
|
+
existingId?: number | string,
|
|
101
101
|
) {
|
|
102
102
|
const aiSettings = (await readSupportSettings(payload)).ai
|
|
103
103
|
if (!aiSettings.enableSynthesis) {
|
|
@@ -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, dbDelete } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* POST /api/support/delete-account
|
|
@@ -44,8 +45,7 @@ export function createDeleteAccountEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
44
45
|
const clientId = req.user.id
|
|
45
46
|
|
|
46
47
|
// 1. Find all ticket IDs for this client
|
|
47
|
-
const tickets = await payload.
|
|
48
|
-
collection: slugs.tickets as any,
|
|
48
|
+
const tickets = await dbFind(payload, slugs.tickets, {
|
|
49
49
|
where: { client: { equals: clientId } },
|
|
50
50
|
limit: 10000,
|
|
51
51
|
depth: 0,
|
|
@@ -57,49 +57,42 @@ export function createDeleteAccountEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
57
57
|
|
|
58
58
|
// 2. Batch delete related data using where clauses
|
|
59
59
|
if (ticketIds.length > 0) {
|
|
60
|
-
await payload.
|
|
61
|
-
collection: slugs.ticketMessages as any,
|
|
60
|
+
await dbDelete(payload, slugs.ticketMessages, {
|
|
62
61
|
where: { ticket: { in: ticketIds } },
|
|
63
62
|
overrideAccess: true,
|
|
64
63
|
})
|
|
65
64
|
|
|
66
|
-
await payload.
|
|
67
|
-
collection: slugs.ticketActivityLog as any,
|
|
65
|
+
await dbDelete(payload, slugs.ticketActivityLog, {
|
|
68
66
|
where: { ticket: { in: ticketIds } },
|
|
69
67
|
overrideAccess: true,
|
|
70
68
|
})
|
|
71
69
|
|
|
72
|
-
await payload.
|
|
73
|
-
collection: slugs.timeEntries as any,
|
|
70
|
+
await dbDelete(payload, slugs.timeEntries, {
|
|
74
71
|
where: { ticket: { in: ticketIds } },
|
|
75
72
|
overrideAccess: true,
|
|
76
73
|
})
|
|
77
74
|
|
|
78
75
|
// Delete all tickets
|
|
79
|
-
await payload.
|
|
80
|
-
collection: slugs.tickets as any,
|
|
76
|
+
await dbDelete(payload, slugs.tickets, {
|
|
81
77
|
where: { client: { equals: clientId } },
|
|
82
78
|
overrideAccess: true,
|
|
83
79
|
})
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
// 3. Batch delete satisfaction surveys
|
|
87
|
-
await payload.
|
|
88
|
-
collection: slugs.satisfactionSurveys as any,
|
|
83
|
+
await dbDelete(payload, slugs.satisfactionSurveys, {
|
|
89
84
|
where: { client: { equals: clientId } },
|
|
90
85
|
overrideAccess: true,
|
|
91
86
|
})
|
|
92
87
|
|
|
93
88
|
// 4. Batch delete chat messages
|
|
94
|
-
await payload.
|
|
95
|
-
collection: slugs.chatMessages as any,
|
|
89
|
+
await dbDelete(payload, slugs.chatMessages, {
|
|
96
90
|
where: { client: { equals: clientId } },
|
|
97
91
|
overrideAccess: true,
|
|
98
92
|
})
|
|
99
93
|
|
|
100
94
|
// 5. Delete the client account
|
|
101
|
-
await payload.
|
|
102
|
-
collection: slugs.supportClients as any,
|
|
95
|
+
await dbDelete(payload, slugs.supportClients, {
|
|
103
96
|
id: clientId,
|
|
104
97
|
overrideAccess: true,
|
|
105
98
|
})
|
|
@@ -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/email-stats?days=7
|
|
@@ -32,8 +33,7 @@ export function createEmailStatsEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
32
33
|
let hasMore = true
|
|
33
34
|
|
|
34
35
|
while (hasMore && page <= MAX_PAGES) {
|
|
35
|
-
const result = await payload.
|
|
36
|
-
collection: slugs.emailLogs as any,
|
|
36
|
+
const result = await dbFind(payload, slugs.emailLogs, {
|
|
37
37
|
where: { createdAt: { greater_than: cutoff } },
|
|
38
38
|
sort: '-createdAt',
|
|
39
39
|
limit: 500,
|
|
@@ -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 { dbUpdate } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* POST /api/support/tickets/:id/escalate
|
|
@@ -25,8 +26,7 @@ export function createEscalateEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
25
26
|
return Response.json({ error: 'invalid-id' }, { status: 400 })
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const ticket = await req.payload.
|
|
29
|
-
collection: slugs.tickets as any,
|
|
29
|
+
const ticket = await dbUpdate(req.payload, slugs.tickets, {
|
|
30
30
|
id: ticketId,
|
|
31
31
|
data: { status: 'escalated' },
|
|
32
32
|
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
|
/**
|
|
6
7
|
* GET /api/support/export-csv
|
|
@@ -22,8 +23,7 @@ export function createExportCsvEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
22
23
|
const allDocs: Array<Record<string, unknown>> = []
|
|
23
24
|
|
|
24
25
|
while (hasMore) {
|
|
25
|
-
const batch = await payload.
|
|
26
|
-
collection: slugs.tickets as any,
|
|
26
|
+
const batch = await dbFind(payload, slugs.tickets, {
|
|
27
27
|
limit: PAGE_SIZE,
|
|
28
28
|
page,
|
|
29
29
|
depth: 1,
|
|
@@ -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 } from '../utils/db'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* GET /api/support/export-data
|
|
@@ -17,21 +18,18 @@ export function createExportDataEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
17
18
|
requireClient(req, slugs)
|
|
18
19
|
|
|
19
20
|
const [clientData, ticketsResult, messagesResult, surveysResult] = await Promise.all([
|
|
20
|
-
payload.
|
|
21
|
-
collection: slugs.supportClients as any,
|
|
21
|
+
dbFindByID(payload, slugs.supportClients, {
|
|
22
22
|
id: req.user.id,
|
|
23
23
|
depth: 0,
|
|
24
24
|
overrideAccess: true,
|
|
25
25
|
}),
|
|
26
|
-
payload.
|
|
27
|
-
collection: slugs.tickets as any,
|
|
26
|
+
dbFind(payload, slugs.tickets, {
|
|
28
27
|
where: { client: { equals: req.user.id } },
|
|
29
28
|
limit: 1000,
|
|
30
29
|
depth: 0,
|
|
31
30
|
overrideAccess: true,
|
|
32
31
|
}),
|
|
33
|
-
payload.
|
|
34
|
-
collection: slugs.ticketMessages as any,
|
|
32
|
+
dbFind(payload, slugs.ticketMessages, {
|
|
35
33
|
where: {
|
|
36
34
|
'ticket.client': { equals: req.user.id },
|
|
37
35
|
authorType: { equals: 'client' },
|
|
@@ -40,8 +38,7 @@ export function createExportDataEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
40
38
|
depth: 0,
|
|
41
39
|
overrideAccess: true,
|
|
42
40
|
}),
|
|
43
|
-
payload.
|
|
44
|
-
collection: slugs.satisfactionSurveys as any,
|
|
41
|
+
dbFind(payload, slugs.satisfactionSurveys, {
|
|
45
42
|
where: { client: { equals: req.user.id } },
|
|
46
43
|
limit: 500,
|
|
47
44
|
depth: 0,
|
|
@@ -2,6 +2,7 @@ import type { Endpoint } from 'payload'
|
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { RateLimiter } from '../utils/rateLimiter'
|
|
4
4
|
import { readSupportSettings } from '../utils/readSettings'
|
|
5
|
+
import { dbFind, dbCreate } from '../utils/db'
|
|
5
6
|
import crypto from 'crypto'
|
|
6
7
|
|
|
7
8
|
interface ParsedConversation {
|
|
@@ -215,8 +216,7 @@ export function createImportConversationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
// Find or create support client
|
|
218
|
-
const clientResult = await payload.
|
|
219
|
-
collection: slugs.supportClients as any,
|
|
219
|
+
const clientResult = await dbFind(payload, slugs.supportClients, {
|
|
220
220
|
where: { email: { equals: conversation.client.email } },
|
|
221
221
|
limit: 1,
|
|
222
222
|
depth: 0,
|
|
@@ -230,8 +230,7 @@ export function createImportConversationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
230
230
|
const nameParts = conversation.client.name.split(' ')
|
|
231
231
|
const randomPassword = crypto.randomBytes(48).toString('base64url')
|
|
232
232
|
|
|
233
|
-
client = await payload.
|
|
234
|
-
collection: slugs.supportClients as any,
|
|
233
|
+
client = await dbCreate(payload, slugs.supportClients, {
|
|
235
234
|
data: {
|
|
236
235
|
email: conversation.client.email,
|
|
237
236
|
password: randomPassword,
|
|
@@ -245,8 +244,7 @@ export function createImportConversationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
245
244
|
}
|
|
246
245
|
|
|
247
246
|
// Find admin user for authorAdmin
|
|
248
|
-
const adminUsers = await payload.
|
|
249
|
-
collection: slugs.users as any,
|
|
247
|
+
const adminUsers = await dbFind(payload, slugs.users, {
|
|
250
248
|
limit: 1,
|
|
251
249
|
depth: 0,
|
|
252
250
|
overrideAccess: true,
|
|
@@ -254,8 +252,7 @@ export function createImportConversationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
254
252
|
const adminUserId = adminUsers.docs[0]?.id
|
|
255
253
|
|
|
256
254
|
// Create ticket
|
|
257
|
-
const ticket = await payload.
|
|
258
|
-
collection: slugs.tickets as any,
|
|
255
|
+
const ticket = await dbCreate(payload, slugs.tickets, {
|
|
259
256
|
data: {
|
|
260
257
|
subject: conversation.subject,
|
|
261
258
|
client: client.id,
|
|
@@ -271,8 +268,7 @@ export function createImportConversationEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
271
268
|
for (const msg of conversation.messages) {
|
|
272
269
|
const isAdmin = msg.from === 'admin'
|
|
273
270
|
|
|
274
|
-
await payload.
|
|
275
|
-
collection: slugs.ticketMessages as any,
|
|
271
|
+
await dbCreate(payload, slugs.ticketMessages, {
|
|
276
272
|
data: {
|
|
277
273
|
ticket: ticket.id,
|
|
278
274
|
body: msg.content,
|
package/src/endpoints/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { CollectionSlugs } from '../utils/slugs'
|
|
|
3
3
|
import type { SupportFeatures } from '../types'
|
|
4
4
|
|
|
5
5
|
import { createAiEndpoint } from './ai'
|
|
6
|
+
import { createAiAgentEndpoint } from './ai-agent'
|
|
6
7
|
import { createClientIntelligenceEndpoint } from './client-intelligence'
|
|
7
8
|
import { createSearchEndpoint } from './search'
|
|
8
9
|
import { createKbSearchEndpoint } from './kb-search'
|
|
@@ -27,6 +28,7 @@ import { createAdminChatGetEndpoint, createAdminChatPostEndpoint } from './admin
|
|
|
27
28
|
import { createAdminChatStreamEndpoint } from './admin-chat-stream'
|
|
28
29
|
import { createAdminStatsEndpoint } from './admin-stats'
|
|
29
30
|
import { createBillingEndpoint } from './billing'
|
|
31
|
+
import { createInvoiceEndpoint } from './invoice'
|
|
30
32
|
import { createTicketSynthesisEndpoint } from './ticket-synthesis'
|
|
31
33
|
import { createEmailStatsEndpoint } from './email-stats'
|
|
32
34
|
import { createSatisfactionEndpoint } from './satisfaction'
|
|
@@ -43,6 +45,10 @@ import { createDeleteAccountEndpoint } from './delete-account'
|
|
|
43
45
|
import { createMergeClientsEndpoint } from './merge-clients'
|
|
44
46
|
import { createImportConversationEndpoint } from './import-conversation'
|
|
45
47
|
import { createProcessScheduledEndpoint } from './process-scheduled'
|
|
48
|
+
import { createProcessSnoozeEndpoint } from './process-snooze'
|
|
49
|
+
import { createProcessDigestsEndpoint } from './process-digests'
|
|
50
|
+
import { createChannelsWebhookEndpoint } from './channels'
|
|
51
|
+
import { createVapidKeyEndpoint, createPushSubscribeEndpoint } from './push'
|
|
46
52
|
import { createUserPrefsGetEndpoint, createUserPrefsPostEndpoint } from './user-prefs'
|
|
47
53
|
import { createEscalateEndpoint } from './escalate'
|
|
48
54
|
import { createTicketFeedbackEndpoint } from './ticket-feedback'
|
|
@@ -51,6 +57,7 @@ import { createInviteCollaboratorEndpoint } from './invite-collaborator'
|
|
|
51
57
|
|
|
52
58
|
// Re-export all individual endpoint creators
|
|
53
59
|
export { createAiEndpoint } from './ai'
|
|
60
|
+
export { createAiAgentEndpoint } from './ai-agent'
|
|
54
61
|
export { createSearchEndpoint } from './search'
|
|
55
62
|
export { createKbSearchEndpoint } from './kb-search'
|
|
56
63
|
export { createBulkActionEndpoint } from './bulk-action'
|
|
@@ -74,6 +81,7 @@ export { createAdminChatGetEndpoint, createAdminChatPostEndpoint } from './admin
|
|
|
74
81
|
export { createAdminChatStreamEndpoint } from './admin-chat-stream'
|
|
75
82
|
export { createAdminStatsEndpoint } from './admin-stats'
|
|
76
83
|
export { createBillingEndpoint } from './billing'
|
|
84
|
+
export { createInvoiceEndpoint } from './invoice'
|
|
77
85
|
export { createTicketSynthesisEndpoint } from './ticket-synthesis'
|
|
78
86
|
export { createEmailStatsEndpoint } from './email-stats'
|
|
79
87
|
export { createSatisfactionEndpoint } from './satisfaction'
|
|
@@ -90,6 +98,10 @@ export { createDeleteAccountEndpoint } from './delete-account'
|
|
|
90
98
|
export { createMergeClientsEndpoint } from './merge-clients'
|
|
91
99
|
export { createImportConversationEndpoint } from './import-conversation'
|
|
92
100
|
export { createProcessScheduledEndpoint } from './process-scheduled'
|
|
101
|
+
export { createProcessSnoozeEndpoint } from './process-snooze'
|
|
102
|
+
export { createProcessDigestsEndpoint } from './process-digests'
|
|
103
|
+
export { createChannelsWebhookEndpoint } from './channels'
|
|
104
|
+
export { createVapidKeyEndpoint, createPushSubscribeEndpoint } from './push'
|
|
93
105
|
export { createUserPrefsGetEndpoint, createUserPrefsPostEndpoint } from './user-prefs'
|
|
94
106
|
export type { UserPrefs } from './user-prefs'
|
|
95
107
|
export { createEscalateEndpoint } from './escalate'
|
|
@@ -139,6 +151,7 @@ export function createSupportEndpoints(slugs: CollectionSlugs, options?: Support
|
|
|
139
151
|
// Conditional endpoints based on feature flags
|
|
140
152
|
if (!f || f.ai !== false) {
|
|
141
153
|
endpoints.push(createAiEndpoint(slugs))
|
|
154
|
+
endpoints.push(createAiAgentEndpoint(slugs))
|
|
142
155
|
endpoints.push(...createClientIntelligenceEndpoint(slugs))
|
|
143
156
|
endpoints.push(createTicketSynthesisEndpoint(slugs))
|
|
144
157
|
}
|
|
@@ -169,13 +182,21 @@ export function createSupportEndpoints(slugs: CollectionSlugs, options?: Support
|
|
|
169
182
|
endpoints.push(createAdminChatGetEndpoint(slugs), createAdminChatPostEndpoint(slugs))
|
|
170
183
|
endpoints.push(createAdminChatStreamEndpoint(slugs))
|
|
171
184
|
}
|
|
172
|
-
if (!f || f.timeTracking !== false)
|
|
185
|
+
if (!f || f.timeTracking !== false) {
|
|
186
|
+
endpoints.push(createBillingEndpoint(slugs))
|
|
187
|
+
endpoints.push(createInvoiceEndpoint(slugs))
|
|
188
|
+
}
|
|
173
189
|
if (!f || f.satisfaction !== false) endpoints.push(createSatisfactionEndpoint(slugs))
|
|
174
190
|
if (!f || f.emailTracking !== false) {
|
|
175
191
|
endpoints.push(createEmailStatsEndpoint(slugs), createTrackOpenEndpoint(slugs))
|
|
176
192
|
}
|
|
177
193
|
if (!f || f.pendingEmails !== false) endpoints.push(createPendingEmailsProcessEndpoint(slugs))
|
|
178
194
|
if (!f || f.scheduledReplies !== false) endpoints.push(createProcessScheduledEndpoint(slugs))
|
|
195
|
+
if (!f || f.snooze !== false) endpoints.push(createProcessSnoozeEndpoint(slugs))
|
|
196
|
+
endpoints.push(createProcessDigestsEndpoint(slugs))
|
|
197
|
+
endpoints.push(createChannelsWebhookEndpoint(slugs))
|
|
198
|
+
endpoints.push(createVapidKeyEndpoint())
|
|
199
|
+
endpoints.push(createPushSubscribeEndpoint(slugs))
|
|
179
200
|
|
|
180
201
|
return endpoints
|
|
181
202
|
}
|
|
@@ -4,9 +4,16 @@ import { handleAuthError, AuthError } from '../utils/auth'
|
|
|
4
4
|
import { escapeHtml, emailWrapper, emailButton, emailParagraph } from '../utils/emailTemplate'
|
|
5
5
|
import { readSupportSettings } from '../utils/readSettings'
|
|
6
6
|
import { randomBytes } from 'crypto'
|
|
7
|
+
import { RateLimiter } from '../utils/rateLimiter'
|
|
8
|
+
import { dbFindByID, dbFind, dbCreate, dbUpdate, dbCount } from '../utils/db'
|
|
7
9
|
|
|
8
10
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
9
11
|
|
|
12
|
+
// Anti-abuse limits: an invite can create a placeholder account AND send an
|
|
13
|
+
// email, so cap both the volume per user and the collaborators per ticket.
|
|
14
|
+
const MAX_COLLABORATORS_PER_TICKET = 20
|
|
15
|
+
const inviteLimiter = new RateLimiter(60 * 60 * 1000, 10)
|
|
16
|
+
|
|
10
17
|
/**
|
|
11
18
|
* POST /api/support/tickets/:id/invite
|
|
12
19
|
*
|
|
@@ -52,12 +59,11 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
52
59
|
|
|
53
60
|
const payload = req.payload
|
|
54
61
|
|
|
55
|
-
const ticket = await payload.
|
|
56
|
-
collection: slugs.tickets as any,
|
|
62
|
+
const ticket = await dbFindByID(payload, slugs.tickets, {
|
|
57
63
|
id: ticketId,
|
|
58
64
|
depth: 1,
|
|
59
65
|
overrideAccess: true,
|
|
60
|
-
})
|
|
66
|
+
})
|
|
61
67
|
if (!ticket) return Response.json({ error: 'Ticket introuvable' }, { status: 404 })
|
|
62
68
|
|
|
63
69
|
const isAdmin = req.user.collection === slugs.users
|
|
@@ -68,6 +74,21 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
68
74
|
return Response.json({ error: 'Forbidden' }, { status: 403 })
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
// Anti-abuse: cap invite volume per user (in-memory, fail-closed).
|
|
78
|
+
if (inviteLimiter.check(String(req.user.id))) {
|
|
79
|
+
return Response.json({ error: 'Trop d\'invitations. Réessayez plus tard.' }, { status: 429 })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Cap collaborators per ticket to prevent mass placeholder-account creation.
|
|
83
|
+
const collabCount = await dbCount(payload, 'ticket-collaborators', { where: { ticket: { equals: ticketId } }, overrideAccess: true })
|
|
84
|
+
.catch(() => ({ totalDocs: 0 }))
|
|
85
|
+
if (collabCount.totalDocs >= MAX_COLLABORATORS_PER_TICKET) {
|
|
86
|
+
return Response.json(
|
|
87
|
+
{ error: `Limite de ${MAX_COLLABORATORS_PER_TICKET} collaborateurs atteinte sur ce ticket` },
|
|
88
|
+
{ status: 429 },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
// Prevent inviting the primary owner (no-op)
|
|
72
93
|
const ownerEmail =
|
|
73
94
|
typeof ticket.client === 'object' ? (ticket.client?.email as string | undefined) : undefined
|
|
@@ -77,8 +98,7 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
77
98
|
|
|
78
99
|
// Find-or-create the invited support-client
|
|
79
100
|
let inviteeId: number | string | null = null
|
|
80
|
-
const existing = await payload.
|
|
81
|
-
collection: slugs.supportClients as any,
|
|
101
|
+
const existing = await dbFind(payload, slugs.supportClients, {
|
|
82
102
|
where: { email: { equals: email } },
|
|
83
103
|
limit: 1,
|
|
84
104
|
depth: 0,
|
|
@@ -90,8 +110,7 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
90
110
|
// Create a placeholder client; password is random — the invitee will reset
|
|
91
111
|
// via the forgotPassword flow triggered by the SupportClients afterChange hook.
|
|
92
112
|
const tempPassword = randomBytes(16).toString('hex')
|
|
93
|
-
const created = await payload.
|
|
94
|
-
collection: slugs.supportClients as any,
|
|
113
|
+
const created = await dbCreate(payload, slugs.supportClients, {
|
|
95
114
|
data: {
|
|
96
115
|
email,
|
|
97
116
|
firstName: 'Invité',
|
|
@@ -111,8 +130,7 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
// Upsert ticket-collaborator row (one per (ticket, client))
|
|
114
|
-
const dupe = await payload
|
|
115
|
-
collection: 'ticket-collaborators' as any,
|
|
133
|
+
const dupe = await dbFind(payload, 'ticket-collaborators', {
|
|
116
134
|
where: {
|
|
117
135
|
and: [
|
|
118
136
|
{ ticket: { equals: ticketId } },
|
|
@@ -127,15 +145,13 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
127
145
|
const invitationToken = randomBytes(24).toString('hex')
|
|
128
146
|
|
|
129
147
|
if (dupe.docs.length > 0) {
|
|
130
|
-
await payload
|
|
131
|
-
collection: 'ticket-collaborators' as any,
|
|
148
|
+
await dbUpdate(payload, 'ticket-collaborators', {
|
|
132
149
|
id: (dupe.docs[0] as any).id,
|
|
133
150
|
data: { role, invitationToken },
|
|
134
151
|
overrideAccess: true,
|
|
135
152
|
})
|
|
136
153
|
} else {
|
|
137
|
-
await payload
|
|
138
|
-
collection: 'ticket-collaborators' as any,
|
|
154
|
+
await dbCreate(payload, 'ticket-collaborators', {
|
|
139
155
|
data: {
|
|
140
156
|
ticket: ticketId,
|
|
141
157
|
client: inviteeId,
|
|
@@ -187,8 +203,7 @@ export function createInviteCollaboratorEndpoint(slugs: CollectionSlugs): Endpoi
|
|
|
187
203
|
|
|
188
204
|
// Best-effort email log
|
|
189
205
|
try {
|
|
190
|
-
await payload.
|
|
191
|
-
collection: slugs.emailLogs as any,
|
|
206
|
+
await dbCreate(payload, slugs.emailLogs, {
|
|
192
207
|
data: {
|
|
193
208
|
status: 'success',
|
|
194
209
|
action: 'invite',
|