@consilioweb/payload-support 0.5.1
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/LICENSE +21 -0
- package/README.md +525 -0
- package/dist/client.cjs +7 -0
- package/dist/client.d.cts +3 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +5 -0
- package/dist/index.cjs +7766 -0
- package/dist/index.d.cts +384 -0
- package/dist/index.d.ts +384 -0
- package/dist/index.js +7730 -0
- package/dist/views.d.cts +30 -0
- package/dist/views.d.ts +30 -0
- package/package.json +131 -0
- package/src/client.ts +1 -0
- package/src/collections/AuthLogs.ts +65 -0
- package/src/collections/CannedResponses.ts +69 -0
- package/src/collections/ChatMessages.ts +98 -0
- package/src/collections/EmailLogs.ts +94 -0
- package/src/collections/KnowledgeBase.ts +99 -0
- package/src/collections/Macros.ts +98 -0
- package/src/collections/PendingEmails.ts +122 -0
- package/src/collections/SatisfactionSurveys.ts +98 -0
- package/src/collections/SlaPolicies.ts +123 -0
- package/src/collections/SupportClients.ts +210 -0
- package/src/collections/TicketActivityLog.ts +81 -0
- package/src/collections/TicketMessages.ts +364 -0
- package/src/collections/TicketStatuses.ts +108 -0
- package/src/collections/Tickets.ts +704 -0
- package/src/collections/TimeEntries.ts +105 -0
- package/src/collections/WebhookEndpoints.ts +96 -0
- package/src/collections/index.ts +16 -0
- package/src/components/TicketConversation/components/AISummaryPanel.tsx +85 -0
- package/src/components/TicketConversation/components/ActionPanels.tsx +140 -0
- package/src/components/TicketConversation/components/ActivityLog.tsx +39 -0
- package/src/components/TicketConversation/components/ClientBar.tsx +37 -0
- package/src/components/TicketConversation/components/ClientHistory.tsx +117 -0
- package/src/components/TicketConversation/components/CodeBlock.tsx +186 -0
- package/src/components/TicketConversation/components/CodeBlockInserter.tsx +166 -0
- package/src/components/TicketConversation/components/QuickActions.tsx +82 -0
- package/src/components/TicketConversation/components/TicketHeader.tsx +91 -0
- package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +161 -0
- package/src/components/TicketConversation/config.ts +82 -0
- package/src/components/TicketConversation/constants.ts +74 -0
- package/src/components/TicketConversation/context.ts +63 -0
- package/src/components/TicketConversation/hooks/useAI.ts +180 -0
- package/src/components/TicketConversation/hooks/useMessageActions.ts +131 -0
- package/src/components/TicketConversation/hooks/useReply.ts +190 -0
- package/src/components/TicketConversation/hooks/useTicketActions.ts +205 -0
- package/src/components/TicketConversation/hooks/useTimeTracking.ts +107 -0
- package/src/components/TicketConversation/hooks/useTranslation.ts +116 -0
- package/src/components/TicketConversation/index.tsx +1110 -0
- package/src/components/TicketConversation/locales/en.json +878 -0
- package/src/components/TicketConversation/locales/fr.json +878 -0
- package/src/components/TicketConversation/types.ts +54 -0
- package/src/components/TicketConversation/utils.ts +25 -0
- package/src/endpoints/admin-chat-stream.ts +238 -0
- package/src/endpoints/admin-chat.ts +263 -0
- package/src/endpoints/admin-stats.ts +200 -0
- package/src/endpoints/ai.ts +199 -0
- package/src/endpoints/apply-macro.ts +144 -0
- package/src/endpoints/auth-2fa.ts +163 -0
- package/src/endpoints/auto-close.ts +175 -0
- package/src/endpoints/billing.ts +167 -0
- package/src/endpoints/bulk-action.ts +103 -0
- package/src/endpoints/chat-stream.ts +127 -0
- package/src/endpoints/chat.ts +188 -0
- package/src/endpoints/chatbot.ts +113 -0
- package/src/endpoints/delete-account.ts +129 -0
- package/src/endpoints/email-stats.ts +109 -0
- package/src/endpoints/export-csv.ts +84 -0
- package/src/endpoints/export-data.ts +104 -0
- package/src/endpoints/import-conversation.ts +307 -0
- package/src/endpoints/index.ts +154 -0
- package/src/endpoints/login.ts +92 -0
- package/src/endpoints/merge-clients.ts +132 -0
- package/src/endpoints/merge-tickets.ts +137 -0
- package/src/endpoints/oauth-google.ts +179 -0
- package/src/endpoints/pending-emails-process.ts +224 -0
- package/src/endpoints/presence.ts +104 -0
- package/src/endpoints/process-scheduled.ts +144 -0
- package/src/endpoints/purge-logs.ts +58 -0
- package/src/endpoints/resend-notification.ts +99 -0
- package/src/endpoints/round-robin-config.ts +92 -0
- package/src/endpoints/satisfaction.ts +93 -0
- package/src/endpoints/search.ts +106 -0
- package/src/endpoints/seed-kb.ts +153 -0
- package/src/endpoints/settings.ts +144 -0
- package/src/endpoints/signature.ts +93 -0
- package/src/endpoints/sla-check.ts +124 -0
- package/src/endpoints/split-ticket.ts +131 -0
- package/src/endpoints/statuses.ts +45 -0
- package/src/endpoints/track-open.ts +154 -0
- package/src/endpoints/typing.ts +101 -0
- package/src/endpoints/user-prefs.ts +125 -0
- package/src/hooks/checkSLA.ts +414 -0
- package/src/hooks/ticketStatusEmail.ts +182 -0
- package/src/index.ts +51 -0
- package/src/plugin.ts +157 -0
- package/src/portal/LiveChat.tsx +1353 -0
- package/src/portal/auth/ChatWidget.tsx +350 -0
- package/src/portal/auth/ChatbotWidget.tsx +285 -0
- package/src/portal/auth/SupportHeader.tsx +409 -0
- package/src/portal/auth/dashboard/DashboardClient.tsx +650 -0
- package/src/portal/auth/dashboard/page.tsx +84 -0
- package/src/portal/auth/faq/FAQSearch.tsx +117 -0
- package/src/portal/auth/faq/page.tsx +199 -0
- package/src/portal/auth/layout.tsx +61 -0
- package/src/portal/auth/profile/page.tsx +705 -0
- package/src/portal/auth/tickets/detail/CloseTicketButton.tsx +74 -0
- package/src/portal/auth/tickets/detail/CollapsibleMessages.tsx +46 -0
- package/src/portal/auth/tickets/detail/MarkSolutionButton.tsx +50 -0
- package/src/portal/auth/tickets/detail/MessageActions.tsx +158 -0
- package/src/portal/auth/tickets/detail/PrintButton.tsx +16 -0
- package/src/portal/auth/tickets/detail/ReadReceipt.tsx +34 -0
- package/src/portal/auth/tickets/detail/ReopenTicketButton.tsx +74 -0
- package/src/portal/auth/tickets/detail/SatisfactionForm.tsx +156 -0
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +57 -0
- package/src/portal/auth/tickets/detail/TicketReplyForm.tsx +294 -0
- package/src/portal/auth/tickets/detail/TypingIndicator.tsx +58 -0
- package/src/portal/auth/tickets/detail/page.tsx +738 -0
- package/src/portal/auth/tickets/new/page.tsx +515 -0
- package/src/portal/forgot-password/page.tsx +114 -0
- package/src/portal/layout.tsx +26 -0
- package/src/portal/locales/en.json +374 -0
- package/src/portal/locales/fr.json +374 -0
- package/src/portal/login/page.tsx +351 -0
- package/src/portal/page.tsx +162 -0
- package/src/portal/register/page.tsx +281 -0
- package/src/portal/reset-password/page.tsx +152 -0
- package/src/styles/BillingView.module.scss +311 -0
- package/src/styles/ChatView.module.scss +438 -0
- package/src/styles/CommandPalette.module.scss +160 -0
- package/src/styles/CrmView.module.scss +554 -0
- package/src/styles/EmailTracking.module.scss +238 -0
- package/src/styles/ImportConversation.module.scss +267 -0
- package/src/styles/Layout.module.scss +55 -0
- package/src/styles/Logs.module.scss +164 -0
- package/src/styles/NewTicket.module.scss +143 -0
- package/src/styles/PendingEmails.module.scss +629 -0
- package/src/styles/SupportDashboard.module.scss +649 -0
- package/src/styles/TicketDetail.module.scss +1043 -0
- package/src/styles/TicketInbox.module.scss +296 -0
- package/src/styles/TicketingSettings.module.scss +358 -0
- package/src/styles/TimeDashboard.module.scss +287 -0
- package/src/styles/_tokens.scss +78 -0
- package/src/styles/theme.css +633 -0
- package/src/types.ts +255 -0
- package/src/utils/adminNotification.ts +38 -0
- package/src/utils/auth.ts +46 -0
- package/src/utils/emailTemplate.ts +343 -0
- package/src/utils/fireWebhooks.ts +84 -0
- package/src/utils/index.ts +22 -0
- package/src/utils/rateLimiter.ts +52 -0
- package/src/utils/readSettings.ts +67 -0
- package/src/utils/slugs.ts +54 -0
- package/src/utils/webhookDispatcher.ts +120 -0
- package/src/views/BillingView/client.tsx +137 -0
- package/src/views/BillingView/index.tsx +33 -0
- package/src/views/ChatView/client.tsx +294 -0
- package/src/views/ChatView/index.tsx +33 -0
- package/src/views/CrmView/client.tsx +206 -0
- package/src/views/CrmView/index.tsx +33 -0
- package/src/views/EmailTrackingView/client.tsx +124 -0
- package/src/views/EmailTrackingView/index.tsx +33 -0
- package/src/views/ImportConversationView/client.tsx +133 -0
- package/src/views/ImportConversationView/index.tsx +33 -0
- package/src/views/LogsView/client.tsx +151 -0
- package/src/views/LogsView/index.tsx +30 -0
- package/src/views/NewTicketView/client.tsx +227 -0
- package/src/views/NewTicketView/index.tsx +30 -0
- package/src/views/PendingEmailsView/client.tsx +177 -0
- package/src/views/PendingEmailsView/index.tsx +33 -0
- package/src/views/SupportDashboardView/client.tsx +424 -0
- package/src/views/SupportDashboardView/index.tsx +33 -0
- package/src/views/TicketDetailView/client.tsx +775 -0
- package/src/views/TicketDetailView/index.tsx +33 -0
- package/src/views/TicketInboxView/client.tsx +313 -0
- package/src/views/TicketInboxView/index.tsx +30 -0
- package/src/views/TicketingSettingsView/client.tsx +866 -0
- package/src/views/TicketingSettingsView/index.tsx +33 -0
- package/src/views/TimeDashboardView/client.tsx +144 -0
- package/src/views/TimeDashboardView/index.tsx +33 -0
- package/src/views/shared/AdminViewHeader.tsx +69 -0
- package/src/views/shared/ErrorBoundary.tsx +68 -0
- package/src/views/shared/Skeleton.tsx +125 -0
- package/src/views/shared/adminTokens.ts +37 -0
- package/src/views/shared/config.ts +82 -0
- package/src/views/shared/index.ts +6 -0
- package/src/views.ts +16 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GET /api/support/export-csv
|
|
7
|
+
* Admin-only endpoint to export all tickets as CSV.
|
|
8
|
+
*/
|
|
9
|
+
export function createExportCsvEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
10
|
+
return {
|
|
11
|
+
path: '/support/export-csv',
|
|
12
|
+
method: 'get',
|
|
13
|
+
handler: async (req) => {
|
|
14
|
+
try {
|
|
15
|
+
const payload = req.payload
|
|
16
|
+
|
|
17
|
+
requireAdmin(req, slugs)
|
|
18
|
+
|
|
19
|
+
const PAGE_SIZE = 500
|
|
20
|
+
let page = 1
|
|
21
|
+
let hasMore = true
|
|
22
|
+
const allDocs: Array<Record<string, unknown>> = []
|
|
23
|
+
|
|
24
|
+
while (hasMore) {
|
|
25
|
+
const batch = await payload.find({
|
|
26
|
+
collection: slugs.tickets as any,
|
|
27
|
+
limit: PAGE_SIZE,
|
|
28
|
+
page,
|
|
29
|
+
depth: 1,
|
|
30
|
+
sort: '-createdAt',
|
|
31
|
+
overrideAccess: true,
|
|
32
|
+
})
|
|
33
|
+
allDocs.push(...(batch.docs as unknown as Array<Record<string, unknown>>))
|
|
34
|
+
hasMore = batch.hasNextPage ?? false
|
|
35
|
+
page++
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const csvHeaders = [
|
|
39
|
+
'N° Ticket', 'Sujet', 'Statut', 'Priorité', 'Catégorie',
|
|
40
|
+
'Client', 'Email Client', 'Projet', 'Tags', 'Assigné à',
|
|
41
|
+
'Temps (min)', 'Créé le', 'Première réponse', 'Résolu le',
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const csvRows = allDocs.map((t) => {
|
|
45
|
+
const client = typeof t.client === 'object' ? (t.client as Record<string, unknown>) : null
|
|
46
|
+
const project = typeof t.project === 'object' ? (t.project as Record<string, unknown>) : null
|
|
47
|
+
const assignedTo = typeof t.assignedTo === 'object' ? (t.assignedTo as Record<string, unknown>) : null
|
|
48
|
+
const tags = Array.isArray(t.tags) ? t.tags.join(', ') : ''
|
|
49
|
+
|
|
50
|
+
return [
|
|
51
|
+
String(t.ticketNumber || ''),
|
|
52
|
+
`"${String(t.subject || '').replace(/"/g, '""')}"`,
|
|
53
|
+
String(t.status || ''),
|
|
54
|
+
String(t.priority || ''),
|
|
55
|
+
String(t.category || ''),
|
|
56
|
+
client ? `"${String(client.company || '').replace(/"/g, '""')}"` : '',
|
|
57
|
+
client ? String(client.email || '') : '',
|
|
58
|
+
project ? `"${String(project.name || '').replace(/"/g, '""')}"` : '',
|
|
59
|
+
`"${tags}"`,
|
|
60
|
+
assignedTo ? String(assignedTo.email || '') : '',
|
|
61
|
+
String(t.totalTimeMinutes || 0),
|
|
62
|
+
t.createdAt ? new Date(String(t.createdAt)).toISOString() : '',
|
|
63
|
+
t.firstResponseAt ? new Date(String(t.firstResponseAt)).toISOString() : '',
|
|
64
|
+
t.resolvedAt ? new Date(String(t.resolvedAt)).toISOString() : '',
|
|
65
|
+
].join(',')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const csv = [csvHeaders.join(','), ...csvRows].join('\n')
|
|
69
|
+
|
|
70
|
+
return new Response(csv, {
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'text/csv; charset=utf-8',
|
|
73
|
+
'Content-Disposition': `attachment; filename="tickets-export-${new Date().toISOString().split('T')[0]}.csv"`,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const authResponse = handleAuthError(error)
|
|
78
|
+
if (authResponse) return authResponse
|
|
79
|
+
console.error('[export-csv] Error:', error)
|
|
80
|
+
return Response.json({ error: 'Internal error' }, { status: 500 })
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { requireClient, handleAuthError } from '../utils/auth'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GET /api/support/export-data
|
|
7
|
+
* RGPD Data Export — allows support clients to download all their personal data.
|
|
8
|
+
*/
|
|
9
|
+
export function createExportDataEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
10
|
+
return {
|
|
11
|
+
path: '/support/export-data',
|
|
12
|
+
method: 'get',
|
|
13
|
+
handler: async (req) => {
|
|
14
|
+
try {
|
|
15
|
+
const payload = req.payload
|
|
16
|
+
|
|
17
|
+
requireClient(req, slugs)
|
|
18
|
+
|
|
19
|
+
const [clientData, ticketsResult, messagesResult, surveysResult] = await Promise.all([
|
|
20
|
+
payload.findByID({
|
|
21
|
+
collection: slugs.supportClients as any,
|
|
22
|
+
id: req.user.id,
|
|
23
|
+
depth: 0,
|
|
24
|
+
overrideAccess: true,
|
|
25
|
+
}),
|
|
26
|
+
payload.find({
|
|
27
|
+
collection: slugs.tickets as any,
|
|
28
|
+
where: { client: { equals: req.user.id } },
|
|
29
|
+
limit: 1000,
|
|
30
|
+
depth: 0,
|
|
31
|
+
overrideAccess: true,
|
|
32
|
+
}),
|
|
33
|
+
payload.find({
|
|
34
|
+
collection: slugs.ticketMessages as any,
|
|
35
|
+
where: {
|
|
36
|
+
'ticket.client': { equals: req.user.id },
|
|
37
|
+
authorType: { equals: 'client' },
|
|
38
|
+
},
|
|
39
|
+
limit: 5000,
|
|
40
|
+
depth: 0,
|
|
41
|
+
overrideAccess: true,
|
|
42
|
+
}),
|
|
43
|
+
payload.find({
|
|
44
|
+
collection: slugs.satisfactionSurveys as any,
|
|
45
|
+
where: { client: { equals: req.user.id } },
|
|
46
|
+
limit: 500,
|
|
47
|
+
depth: 0,
|
|
48
|
+
overrideAccess: true,
|
|
49
|
+
}),
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
const c = clientData as any
|
|
53
|
+
const exportData = {
|
|
54
|
+
exportDate: new Date().toISOString(),
|
|
55
|
+
exportType: 'RGPD - Export des données personnelles',
|
|
56
|
+
profile: {
|
|
57
|
+
email: c.email,
|
|
58
|
+
firstName: c.firstName,
|
|
59
|
+
lastName: c.lastName,
|
|
60
|
+
company: c.company,
|
|
61
|
+
phone: c.phone || null,
|
|
62
|
+
createdAt: c.createdAt,
|
|
63
|
+
updatedAt: c.updatedAt,
|
|
64
|
+
},
|
|
65
|
+
tickets: ticketsResult.docs.map((t: any) => ({
|
|
66
|
+
ticketNumber: t.ticketNumber,
|
|
67
|
+
subject: t.subject,
|
|
68
|
+
status: t.status,
|
|
69
|
+
priority: t.priority,
|
|
70
|
+
category: t.category,
|
|
71
|
+
createdAt: t.createdAt,
|
|
72
|
+
updatedAt: t.updatedAt,
|
|
73
|
+
})),
|
|
74
|
+
messages: messagesResult.docs.map((m: any) => ({
|
|
75
|
+
ticketId: m.ticket,
|
|
76
|
+
body: m.body,
|
|
77
|
+
createdAt: m.createdAt,
|
|
78
|
+
})),
|
|
79
|
+
surveys: surveysResult.docs.map((s: any) => ({
|
|
80
|
+
ticketId: s.ticket,
|
|
81
|
+
rating: s.rating,
|
|
82
|
+
comment: s.comment,
|
|
83
|
+
createdAt: s.createdAt,
|
|
84
|
+
})),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const json = JSON.stringify(exportData, null, 2)
|
|
88
|
+
|
|
89
|
+
return new Response(json, {
|
|
90
|
+
status: 200,
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
'Content-Disposition': `attachment; filename="support-export-${req.user.id}-${new Date().toISOString().slice(0, 10)}.json"`,
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const authResponse = handleAuthError(error)
|
|
98
|
+
if (authResponse) return authResponse
|
|
99
|
+
console.error('[export-data] Error:', error)
|
|
100
|
+
return Response.json({ error: 'Erreur interne' }, { status: 500 })
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { RateLimiter } from '../utils/rateLimiter'
|
|
4
|
+
import { readSupportSettings } from '../utils/readSettings'
|
|
5
|
+
import crypto from 'crypto'
|
|
6
|
+
|
|
7
|
+
interface ParsedConversation {
|
|
8
|
+
client: { email: string; name: string; company: string }
|
|
9
|
+
subject: string
|
|
10
|
+
messages: { from: 'client' | 'admin'; name: string; date: string; content: string }[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const importLimiter = new RateLimiter(3_600_000, 10) // 10 per hour
|
|
14
|
+
|
|
15
|
+
function parseStructuredMarkdown(markdown: string): ParsedConversation | null {
|
|
16
|
+
const clientMatch = markdown.match(
|
|
17
|
+
/\*\*Client\s*:\*\*\s*(.+?)\s*[—–-]\s*(.+?)\s*\(([^)]+@[^)]+)\)/i,
|
|
18
|
+
)
|
|
19
|
+
const subjectMatch = markdown.match(/\*\*Sujet\s*:\*\*\s*(.+)/i)
|
|
20
|
+
|
|
21
|
+
if (!clientMatch || !subjectMatch) return null
|
|
22
|
+
|
|
23
|
+
const client = {
|
|
24
|
+
name: clientMatch[1]!.trim(),
|
|
25
|
+
company: clientMatch[2]!.trim(),
|
|
26
|
+
email: clientMatch[3]!.trim().toLowerCase(),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const subject = subjectMatch[1]!.trim()
|
|
30
|
+
const adminEmail = (process.env.CONTACT_EMAIL || 'admin@example.com').toLowerCase()
|
|
31
|
+
|
|
32
|
+
const blocks = markdown.split(/## Message \d+/).slice(1)
|
|
33
|
+
const messages: ParsedConversation['messages'] = []
|
|
34
|
+
|
|
35
|
+
for (const block of blocks) {
|
|
36
|
+
const fromMatch = block.match(/\*\*De\s*:\*\*\s*(.+?)\s*\(([^)]+)\)/)
|
|
37
|
+
const dateMatch = block.match(/\*\*Date\s*:\*\*\s*(.+)/)
|
|
38
|
+
|
|
39
|
+
if (!fromMatch) continue
|
|
40
|
+
|
|
41
|
+
const name = fromMatch[1]!.trim()
|
|
42
|
+
const email = fromMatch[2]!.trim().toLowerCase()
|
|
43
|
+
const date = dateMatch ? dateMatch[1]!.trim() : ''
|
|
44
|
+
|
|
45
|
+
const lines = block.split('\n')
|
|
46
|
+
let contentStart = 0
|
|
47
|
+
let foundDate = false
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
if (lines[i]!.startsWith('**Date')) { foundDate = true; continue }
|
|
50
|
+
if (foundDate && lines[i]!.trim() === '') { contentStart = i + 1; break }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const content = lines.slice(contentStart).join('\n').replace(/\n---\s*$/s, '').trim()
|
|
54
|
+
if (!content) continue
|
|
55
|
+
|
|
56
|
+
messages.push({
|
|
57
|
+
from: email === adminEmail ? 'admin' : 'client',
|
|
58
|
+
name,
|
|
59
|
+
date,
|
|
60
|
+
content,
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (messages.length === 0) return null
|
|
65
|
+
return { client, subject, messages }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function parseMarkdownWithAI(markdown: string): Promise<ParsedConversation | null> {
|
|
69
|
+
if (!process.env.ANTHROPIC_API_KEY) return null
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const Anthropic = require('@anthropic-ai/sdk').default
|
|
73
|
+
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
|
|
74
|
+
|
|
75
|
+
const response = await anthropic.messages.create({
|
|
76
|
+
model: 'claude-haiku-4-5-20251001',
|
|
77
|
+
max_tokens: 4096,
|
|
78
|
+
messages: [
|
|
79
|
+
{
|
|
80
|
+
role: 'user',
|
|
81
|
+
content: `Parse this email conversation markdown and return strict JSON.
|
|
82
|
+
|
|
83
|
+
Extract:
|
|
84
|
+
- Client info (the external person, NOT the admin)
|
|
85
|
+
- Subject
|
|
86
|
+
- All messages chronologically
|
|
87
|
+
|
|
88
|
+
The admin email is: ${process.env.CONTACT_EMAIL || 'admin@example.com'}
|
|
89
|
+
|
|
90
|
+
Return JSON: { "client": { "email": "", "name": "", "company": "" }, "subject": "", "messages": [{ "from": "client"|"admin", "name": "", "date": "", "content": "" }] }
|
|
91
|
+
|
|
92
|
+
Markdown:
|
|
93
|
+
---
|
|
94
|
+
${markdown}
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
ONLY JSON, nothing else.`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const text = response.content[0]?.type === 'text' ? response.content[0].text.trim() : ''
|
|
103
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/)
|
|
104
|
+
if (!jsonMatch) return null
|
|
105
|
+
|
|
106
|
+
const parsed = JSON.parse(jsonMatch[0])
|
|
107
|
+
if (!parsed.client?.email || !parsed.messages?.length) return null
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
client: {
|
|
111
|
+
email: parsed.client.email.toLowerCase().trim(),
|
|
112
|
+
name: parsed.client.name || parsed.client.email.split('@')[0],
|
|
113
|
+
company: parsed.client.company || 'Non renseigné',
|
|
114
|
+
},
|
|
115
|
+
subject: parsed.subject || 'Conversation importée',
|
|
116
|
+
messages: parsed.messages.map((m: any) => ({
|
|
117
|
+
from: m.from === 'admin' ? 'admin' : 'client',
|
|
118
|
+
name: m.name || '',
|
|
119
|
+
date: m.date || '',
|
|
120
|
+
content: m.content || '',
|
|
121
|
+
})),
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error('[import-conversation] AI parsing failed:', err)
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* POST /api/support/import-conversation
|
|
131
|
+
* Import a conversation from markdown into the ticket system.
|
|
132
|
+
*/
|
|
133
|
+
export function createImportConversationEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
134
|
+
return {
|
|
135
|
+
path: '/support/import-conversation',
|
|
136
|
+
method: 'post',
|
|
137
|
+
handler: async (req) => {
|
|
138
|
+
try {
|
|
139
|
+
const payload = req.payload
|
|
140
|
+
|
|
141
|
+
// Auth: admin session OR webhook secret
|
|
142
|
+
const webhookSecret = req.headers.get('x-webhook-secret')
|
|
143
|
+
let isAuthed = false
|
|
144
|
+
|
|
145
|
+
if (webhookSecret && process.env.SUPPORT_WEBHOOK_SECRET && webhookSecret === process.env.SUPPORT_WEBHOOK_SECRET) {
|
|
146
|
+
isAuthed = true
|
|
147
|
+
} else if (req.user && req.user.collection === slugs.users) {
|
|
148
|
+
isAuthed = true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!isAuthed) {
|
|
152
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || 'unknown'
|
|
156
|
+
if (importLimiter.check(ip)) {
|
|
157
|
+
return Response.json({ error: 'Rate limit exceeded. Maximum 10 imports per hour.' }, { status: 429 })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let body: { markdown?: string; previewOnly?: boolean }
|
|
161
|
+
try {
|
|
162
|
+
body = await req.json!()
|
|
163
|
+
} catch {
|
|
164
|
+
return Response.json({ error: 'Invalid JSON body' }, { status: 400 })
|
|
165
|
+
}
|
|
166
|
+
const { markdown, previewOnly } = body
|
|
167
|
+
|
|
168
|
+
if (!markdown || typeof markdown !== 'string') {
|
|
169
|
+
return Response.json({ error: 'markdown field is required (string)' }, { status: 400 })
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (markdown.length > 512_000) {
|
|
173
|
+
return Response.json({ error: 'Markdown too large (max 500KB)' }, { status: 400 })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let conversation = parseStructuredMarkdown(markdown)
|
|
177
|
+
let parseMethod = 'structured'
|
|
178
|
+
|
|
179
|
+
if (!conversation) {
|
|
180
|
+
conversation = await parseMarkdownWithAI(markdown)
|
|
181
|
+
parseMethod = 'ai'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!conversation) {
|
|
185
|
+
return Response.json({
|
|
186
|
+
error: 'Could not parse conversation. Expected format: **Client :** Name — Company (email), **Sujet :** Subject, ## Message N blocks.',
|
|
187
|
+
}, { status: 422 })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (previewOnly) {
|
|
191
|
+
return Response.json({
|
|
192
|
+
action: 'preview',
|
|
193
|
+
parseMethod,
|
|
194
|
+
client: conversation.client,
|
|
195
|
+
subject: conversation.subject,
|
|
196
|
+
messageCount: conversation.messages.length,
|
|
197
|
+
messages: conversation.messages.map((m) => ({
|
|
198
|
+
from: m.from,
|
|
199
|
+
name: m.name,
|
|
200
|
+
date: m.date,
|
|
201
|
+
preview: m.content.length > 100 ? m.content.substring(0, 100) + '...' : m.content,
|
|
202
|
+
})),
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!conversation.client.email.includes('@')) {
|
|
207
|
+
return Response.json({ error: 'Invalid client email extracted' }, { status: 422 })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const settings = await readSupportSettings(payload)
|
|
211
|
+
const adminEmail = (process.env.CONTACT_EMAIL || '').toLowerCase()
|
|
212
|
+
const blockedEmails = [adminEmail, settings.email.replyToAddress || process.env.SUPPORT_REPLY_TO || ''].filter(Boolean)
|
|
213
|
+
if (blockedEmails.includes(conversation.client.email)) {
|
|
214
|
+
return Response.json({ error: 'Cannot create ticket from system email address' }, { status: 400 })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Find or create support client
|
|
218
|
+
const clientResult = await payload.find({
|
|
219
|
+
collection: slugs.supportClients as any,
|
|
220
|
+
where: { email: { equals: conversation.client.email } },
|
|
221
|
+
limit: 1,
|
|
222
|
+
depth: 0,
|
|
223
|
+
overrideAccess: true,
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
let client = clientResult.docs[0]
|
|
227
|
+
let isNewClient = false
|
|
228
|
+
|
|
229
|
+
if (!client) {
|
|
230
|
+
const nameParts = conversation.client.name.split(' ')
|
|
231
|
+
const randomPassword = crypto.randomBytes(48).toString('base64url')
|
|
232
|
+
|
|
233
|
+
client = await payload.create({
|
|
234
|
+
collection: slugs.supportClients as any,
|
|
235
|
+
data: {
|
|
236
|
+
email: conversation.client.email,
|
|
237
|
+
password: randomPassword,
|
|
238
|
+
firstName: nameParts[0] || 'Inconnu',
|
|
239
|
+
lastName: nameParts.slice(1).join(' ') || conversation.client.email.split('@')[0] || 'Inconnu',
|
|
240
|
+
company: conversation.client.company || 'Non renseigné',
|
|
241
|
+
},
|
|
242
|
+
overrideAccess: true,
|
|
243
|
+
})
|
|
244
|
+
isNewClient = true
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Find admin user for authorAdmin
|
|
248
|
+
const adminUsers = await payload.find({
|
|
249
|
+
collection: slugs.users as any,
|
|
250
|
+
limit: 1,
|
|
251
|
+
depth: 0,
|
|
252
|
+
overrideAccess: true,
|
|
253
|
+
})
|
|
254
|
+
const adminUserId = adminUsers.docs[0]?.id
|
|
255
|
+
|
|
256
|
+
// Create ticket
|
|
257
|
+
const ticket = await payload.create({
|
|
258
|
+
collection: slugs.tickets as any,
|
|
259
|
+
data: {
|
|
260
|
+
subject: conversation.subject,
|
|
261
|
+
client: client.id,
|
|
262
|
+
status: 'open',
|
|
263
|
+
priority: 'normal',
|
|
264
|
+
category: 'question',
|
|
265
|
+
},
|
|
266
|
+
overrideAccess: true,
|
|
267
|
+
}) as any
|
|
268
|
+
|
|
269
|
+
// Import messages
|
|
270
|
+
let importedCount = 0
|
|
271
|
+
for (const msg of conversation.messages) {
|
|
272
|
+
const isAdmin = msg.from === 'admin'
|
|
273
|
+
|
|
274
|
+
await payload.create({
|
|
275
|
+
collection: slugs.ticketMessages as any,
|
|
276
|
+
data: {
|
|
277
|
+
ticket: ticket.id,
|
|
278
|
+
body: msg.content,
|
|
279
|
+
authorType: isAdmin ? 'admin' : 'email',
|
|
280
|
+
...(isAdmin && adminUserId ? { authorAdmin: adminUserId } : {}),
|
|
281
|
+
...(!isAdmin ? { authorClient: client.id } : {}),
|
|
282
|
+
isInternal: false,
|
|
283
|
+
skipNotification: true,
|
|
284
|
+
},
|
|
285
|
+
overrideAccess: true,
|
|
286
|
+
})
|
|
287
|
+
importedCount++
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return Response.json({
|
|
291
|
+
action: 'conversation_imported',
|
|
292
|
+
parseMethod,
|
|
293
|
+
ticketNumber: ticket.ticketNumber,
|
|
294
|
+
ticketId: ticket.id,
|
|
295
|
+
clientEmail: conversation.client.email,
|
|
296
|
+
clientName: conversation.client.name,
|
|
297
|
+
clientCompany: conversation.client.company,
|
|
298
|
+
isNewClient,
|
|
299
|
+
messagesImported: importedCount,
|
|
300
|
+
})
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error('[import-conversation] Error:', error)
|
|
303
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 })
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import type { SupportFeatures } from '../types'
|
|
4
|
+
|
|
5
|
+
import { createAiEndpoint } from './ai'
|
|
6
|
+
import { createSearchEndpoint } from './search'
|
|
7
|
+
import { createBulkActionEndpoint } from './bulk-action'
|
|
8
|
+
import { createMergeTicketsEndpoint } from './merge-tickets'
|
|
9
|
+
import { createSplitTicketEndpoint } from './split-ticket'
|
|
10
|
+
import { createTypingPostEndpoint, createTypingGetEndpoint } from './typing'
|
|
11
|
+
import { createPresencePostEndpoint, createPresenceGetEndpoint } from './presence'
|
|
12
|
+
import { createSettingsGetEndpoint, createSettingsPostEndpoint } from './settings'
|
|
13
|
+
import { createSignatureGetEndpoint, createSignaturePostEndpoint } from './signature'
|
|
14
|
+
import { createRoundRobinConfigGetEndpoint, createRoundRobinConfigPostEndpoint } from './round-robin-config'
|
|
15
|
+
import { createSlaCheckEndpoint } from './sla-check'
|
|
16
|
+
import { createAutoCloseEndpoint } from './auto-close'
|
|
17
|
+
import { createStatusesEndpoint } from './statuses'
|
|
18
|
+
import { createApplyMacroEndpoint } from './apply-macro'
|
|
19
|
+
import { createPurgeLogsEndpoint } from './purge-logs'
|
|
20
|
+
import { createChatbotEndpoint } from './chatbot'
|
|
21
|
+
import { createChatGetEndpoint, createChatPostEndpoint } from './chat'
|
|
22
|
+
import { createChatStreamEndpoint } from './chat-stream'
|
|
23
|
+
import { createAdminChatGetEndpoint, createAdminChatPostEndpoint } from './admin-chat'
|
|
24
|
+
import { createAdminChatStreamEndpoint } from './admin-chat-stream'
|
|
25
|
+
import { createAdminStatsEndpoint } from './admin-stats'
|
|
26
|
+
import { createBillingEndpoint } from './billing'
|
|
27
|
+
import { createEmailStatsEndpoint } from './email-stats'
|
|
28
|
+
import { createSatisfactionEndpoint } from './satisfaction'
|
|
29
|
+
import { createTrackOpenEndpoint } from './track-open'
|
|
30
|
+
import { createExportCsvEndpoint } from './export-csv'
|
|
31
|
+
import { createExportDataEndpoint } from './export-data'
|
|
32
|
+
import { createPendingEmailsProcessEndpoint } from './pending-emails-process'
|
|
33
|
+
import { createResendNotificationEndpoint } from './resend-notification'
|
|
34
|
+
import { createSeedKbEndpoint } from './seed-kb'
|
|
35
|
+
import { createLoginEndpoint } from './login'
|
|
36
|
+
import { createAuth2faEndpoint } from './auth-2fa'
|
|
37
|
+
import { createOAuthGoogleEndpoint, type OAuthGoogleOptions } from './oauth-google'
|
|
38
|
+
import { createDeleteAccountEndpoint } from './delete-account'
|
|
39
|
+
import { createMergeClientsEndpoint } from './merge-clients'
|
|
40
|
+
import { createImportConversationEndpoint } from './import-conversation'
|
|
41
|
+
import { createProcessScheduledEndpoint } from './process-scheduled'
|
|
42
|
+
import { createUserPrefsGetEndpoint, createUserPrefsPostEndpoint } from './user-prefs'
|
|
43
|
+
|
|
44
|
+
// Re-export all individual endpoint creators
|
|
45
|
+
export { createAiEndpoint } from './ai'
|
|
46
|
+
export { createSearchEndpoint } from './search'
|
|
47
|
+
export { createBulkActionEndpoint } from './bulk-action'
|
|
48
|
+
export { createMergeTicketsEndpoint } from './merge-tickets'
|
|
49
|
+
export { createSplitTicketEndpoint } from './split-ticket'
|
|
50
|
+
export { createTypingPostEndpoint, createTypingGetEndpoint } from './typing'
|
|
51
|
+
export { createPresencePostEndpoint, createPresenceGetEndpoint } from './presence'
|
|
52
|
+
export { createSettingsGetEndpoint, createSettingsPostEndpoint } from './settings'
|
|
53
|
+
export { createSignatureGetEndpoint, createSignaturePostEndpoint } from './signature'
|
|
54
|
+
export { createRoundRobinConfigGetEndpoint, createRoundRobinConfigPostEndpoint } from './round-robin-config'
|
|
55
|
+
export { createSlaCheckEndpoint } from './sla-check'
|
|
56
|
+
export { createAutoCloseEndpoint } from './auto-close'
|
|
57
|
+
export { createStatusesEndpoint } from './statuses'
|
|
58
|
+
export { createApplyMacroEndpoint } from './apply-macro'
|
|
59
|
+
export { createPurgeLogsEndpoint } from './purge-logs'
|
|
60
|
+
export { createChatbotEndpoint } from './chatbot'
|
|
61
|
+
export { createChatGetEndpoint, createChatPostEndpoint } from './chat'
|
|
62
|
+
export { createChatStreamEndpoint } from './chat-stream'
|
|
63
|
+
export { createAdminChatGetEndpoint, createAdminChatPostEndpoint } from './admin-chat'
|
|
64
|
+
export { createAdminChatStreamEndpoint } from './admin-chat-stream'
|
|
65
|
+
export { createAdminStatsEndpoint } from './admin-stats'
|
|
66
|
+
export { createBillingEndpoint } from './billing'
|
|
67
|
+
export { createEmailStatsEndpoint } from './email-stats'
|
|
68
|
+
export { createSatisfactionEndpoint } from './satisfaction'
|
|
69
|
+
export { createTrackOpenEndpoint } from './track-open'
|
|
70
|
+
export { createExportCsvEndpoint } from './export-csv'
|
|
71
|
+
export { createExportDataEndpoint } from './export-data'
|
|
72
|
+
export { createPendingEmailsProcessEndpoint } from './pending-emails-process'
|
|
73
|
+
export { createResendNotificationEndpoint } from './resend-notification'
|
|
74
|
+
export { createSeedKbEndpoint } from './seed-kb'
|
|
75
|
+
export { createLoginEndpoint } from './login'
|
|
76
|
+
export { createAuth2faEndpoint } from './auth-2fa'
|
|
77
|
+
export { createOAuthGoogleEndpoint } from './oauth-google'
|
|
78
|
+
export { createDeleteAccountEndpoint } from './delete-account'
|
|
79
|
+
export { createMergeClientsEndpoint } from './merge-clients'
|
|
80
|
+
export { createImportConversationEndpoint } from './import-conversation'
|
|
81
|
+
export { createProcessScheduledEndpoint } from './process-scheduled'
|
|
82
|
+
export { createUserPrefsGetEndpoint, createUserPrefsPostEndpoint } from './user-prefs'
|
|
83
|
+
export type { UserPrefs } from './user-prefs'
|
|
84
|
+
|
|
85
|
+
export interface SupportEndpointOptions {
|
|
86
|
+
oauth?: OAuthGoogleOptions
|
|
87
|
+
features?: Required<SupportFeatures>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create all support endpoints for the Payload plugin.
|
|
92
|
+
* Returns an array of Endpoint objects to be registered via `endpoints` in the plugin config.
|
|
93
|
+
* Endpoints are conditionally included based on feature flags.
|
|
94
|
+
*/
|
|
95
|
+
export function createSupportEndpoints(slugs: CollectionSlugs, options?: SupportEndpointOptions): Endpoint[] {
|
|
96
|
+
const f = options?.features
|
|
97
|
+
|
|
98
|
+
// Core endpoints (always present)
|
|
99
|
+
const endpoints: Endpoint[] = [
|
|
100
|
+
createSearchEndpoint(slugs),
|
|
101
|
+
createSettingsGetEndpoint(slugs),
|
|
102
|
+
createSettingsPostEndpoint(slugs),
|
|
103
|
+
createAdminStatsEndpoint(slugs),
|
|
104
|
+
createExportCsvEndpoint(slugs),
|
|
105
|
+
createExportDataEndpoint(slugs),
|
|
106
|
+
createSeedKbEndpoint(slugs),
|
|
107
|
+
createLoginEndpoint(slugs),
|
|
108
|
+
createAuth2faEndpoint(slugs),
|
|
109
|
+
createOAuthGoogleEndpoint(slugs, options?.oauth),
|
|
110
|
+
createDeleteAccountEndpoint(slugs),
|
|
111
|
+
createMergeClientsEndpoint(slugs),
|
|
112
|
+
createImportConversationEndpoint(slugs),
|
|
113
|
+
createPurgeLogsEndpoint(slugs),
|
|
114
|
+
createResendNotificationEndpoint(slugs),
|
|
115
|
+
createUserPrefsGetEndpoint(slugs),
|
|
116
|
+
createUserPrefsPostEndpoint(slugs),
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
// Conditional endpoints based on feature flags
|
|
120
|
+
if (!f || f.ai !== false) endpoints.push(createAiEndpoint(slugs))
|
|
121
|
+
if (!f || f.bulkActions !== false) endpoints.push(createBulkActionEndpoint(slugs))
|
|
122
|
+
if (!f || f.merge !== false) endpoints.push(createMergeTicketsEndpoint(slugs))
|
|
123
|
+
if (!f || f.splitTicket !== false) endpoints.push(createSplitTicketEndpoint(slugs))
|
|
124
|
+
if (!f || f.collisionDetection !== false) {
|
|
125
|
+
endpoints.push(createTypingPostEndpoint(slugs), createTypingGetEndpoint(slugs))
|
|
126
|
+
endpoints.push(createPresencePostEndpoint(slugs), createPresenceGetEndpoint(slugs))
|
|
127
|
+
}
|
|
128
|
+
if (!f || f.signatures !== false) {
|
|
129
|
+
endpoints.push(createSignatureGetEndpoint(slugs), createSignaturePostEndpoint(slugs))
|
|
130
|
+
}
|
|
131
|
+
if (!f || f.sla !== false) endpoints.push(createSlaCheckEndpoint(slugs))
|
|
132
|
+
if (!f || f.autoClose !== false) endpoints.push(createAutoCloseEndpoint(slugs))
|
|
133
|
+
if (!f || f.customStatuses !== false) endpoints.push(createStatusesEndpoint(slugs))
|
|
134
|
+
if (!f || f.macros !== false) endpoints.push(createApplyMacroEndpoint(slugs))
|
|
135
|
+
if (!f || f.roundRobin !== false) {
|
|
136
|
+
endpoints.push(createRoundRobinConfigGetEndpoint(slugs), createRoundRobinConfigPostEndpoint(slugs))
|
|
137
|
+
}
|
|
138
|
+
if (!f || f.chatbot !== false) endpoints.push(createChatbotEndpoint(slugs))
|
|
139
|
+
if (!f || f.chat !== false) {
|
|
140
|
+
endpoints.push(createChatGetEndpoint(slugs), createChatPostEndpoint(slugs))
|
|
141
|
+
endpoints.push(createChatStreamEndpoint(slugs))
|
|
142
|
+
endpoints.push(createAdminChatGetEndpoint(slugs), createAdminChatPostEndpoint(slugs))
|
|
143
|
+
endpoints.push(createAdminChatStreamEndpoint(slugs))
|
|
144
|
+
}
|
|
145
|
+
if (!f || f.timeTracking !== false) endpoints.push(createBillingEndpoint(slugs))
|
|
146
|
+
if (!f || f.satisfaction !== false) endpoints.push(createSatisfactionEndpoint(slugs))
|
|
147
|
+
if (!f || f.emailTracking !== false) {
|
|
148
|
+
endpoints.push(createEmailStatsEndpoint(slugs), createTrackOpenEndpoint(slugs))
|
|
149
|
+
}
|
|
150
|
+
if (!f || f.pendingEmails !== false) endpoints.push(createPendingEmailsProcessEndpoint(slugs))
|
|
151
|
+
if (!f || f.scheduledReplies !== false) endpoints.push(createProcessScheduledEndpoint(slugs))
|
|
152
|
+
|
|
153
|
+
return endpoints
|
|
154
|
+
}
|