@consilioweb/payload-support 5.0.0 → 6.0.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.
Files changed (120) hide show
  1. package/README.md +249 -22
  2. package/dist/components/RichTextEditor/index.js +1 -1
  3. package/dist/components/TicketConversation/RewriteDropdown.js +1 -1
  4. package/dist/components/TicketConversation/components/AISummaryPanel.js +3 -0
  5. package/dist/components/TicketConversation/components/ActionPanels.js +13 -8
  6. package/dist/components/TicketConversation/components/ClientHistory.js +1 -0
  7. package/dist/components/TicketConversation/components/CodeBlock.js +1 -0
  8. package/dist/components/TicketConversation/components/CodeBlockInserter.js +1 -1
  9. package/dist/components/TicketConversation/components/QuickActions.js +10 -8
  10. package/dist/components/TicketConversation/components/TicketHeader.js +2 -0
  11. package/dist/components/TicketConversation/components/TimeTrackingPanel.js +15 -9
  12. package/dist/components/TicketConversation/index.d.ts +10 -0
  13. package/dist/components/TicketConversation/index.js +25 -7
  14. package/dist/index.cjs +306 -60
  15. package/dist/index.d.cts +107 -38
  16. package/dist/index.d.ts +107 -38
  17. package/dist/index.js +300 -59
  18. package/dist/styles/BillingView.module.scss +11 -11
  19. package/dist/styles/ChatView.module.scss +7 -7
  20. package/dist/styles/CommandPalette.module.scss +1 -1
  21. package/dist/styles/CrmView.module.scss +9 -9
  22. package/dist/styles/EmailTracking.module.scss +6 -6
  23. package/dist/styles/ImportConversation.module.scss +5 -5
  24. package/dist/styles/Logs.module.scss +5 -5
  25. package/dist/styles/NewTicket.module.scss +2 -2
  26. package/dist/styles/PendingEmails.module.scss +13 -13
  27. package/dist/styles/SupportDashboard.module.scss +24 -8
  28. package/dist/styles/TicketDetail.module.scss +20 -20
  29. package/dist/styles/TicketInbox.module.scss +6 -6
  30. package/dist/styles/TicketingSettings.module.scss +10 -10
  31. package/dist/styles/TimeDashboard.module.scss +6 -6
  32. package/dist/styles/_tokens.scss +3 -1
  33. package/dist/utils/db.js +20 -0
  34. package/dist/utils/readSettings.js +150 -0
  35. package/dist/views/BillingView/client.js +15 -8
  36. package/dist/views/ChatView/client.js +2 -0
  37. package/dist/views/CrmView/client.js +2 -0
  38. package/dist/views/EmailTrackingView/client.js +23 -11
  39. package/dist/views/ImportConversationView/client.js +1 -0
  40. package/dist/views/LogsView/client.js +1 -1
  41. package/dist/views/NewTicketView/client.js +20 -13
  42. package/dist/views/PendingEmailsView/client.js +9 -3
  43. package/dist/views/SupportDashboardView/client.js +17 -19
  44. package/dist/views/TicketDetailView/client.js +23 -11
  45. package/dist/views/TicketInboxView/client.js +52 -26
  46. package/dist/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  47. package/dist/views/TicketingSettingsView/client.js +44 -26
  48. package/dist/views/TimeDashboardView/client.js +10 -7
  49. package/dist/views/shared/ErrorBoundary.d.ts +36 -1
  50. package/dist/views/shared/ErrorBoundary.js +59 -28
  51. package/dist/views/shared/adminTokens.d.ts +15 -5
  52. package/dist/views/shared/adminTokens.js +23 -7
  53. package/dist/views/shared/icons.d.ts +49 -0
  54. package/dist/views/shared/icons.js +92 -0
  55. package/dist/views/shared/locales/en.json +15 -1
  56. package/dist/views/shared/locales/fr.json +15 -1
  57. package/package.json +9 -4
  58. package/scripts/copy-subpath-types.mjs +103 -0
  59. package/scripts/uninstall-data.mjs +178 -0
  60. package/scripts/uninstall.mjs +184 -0
  61. package/scripts/verify-dist-imports.mjs +84 -0
  62. package/src/collections/Tickets.ts +7 -0
  63. package/src/collections/TimeEntries.ts +11 -3
  64. package/src/components/RichTextEditor/index.tsx +1 -1
  65. package/src/components/TicketConversation/RewriteDropdown.tsx +1 -1
  66. package/src/components/TicketConversation/components/AISummaryPanel.tsx +3 -0
  67. package/src/components/TicketConversation/components/ActionPanels.tsx +13 -8
  68. package/src/components/TicketConversation/components/ClientHistory.tsx +1 -0
  69. package/src/components/TicketConversation/components/CodeBlock.tsx +1 -0
  70. package/src/components/TicketConversation/components/CodeBlockInserter.tsx +1 -1
  71. package/src/components/TicketConversation/components/QuickActions.tsx +10 -8
  72. package/src/components/TicketConversation/components/TicketHeader.tsx +2 -0
  73. package/src/components/TicketConversation/components/TimeTrackingPanel.tsx +17 -9
  74. package/src/components/TicketConversation/index.tsx +41 -7
  75. package/src/endpoints/delete-account.ts +191 -47
  76. package/src/endpoints/export-data.ts +104 -4
  77. package/src/endpoints/purge-logs.ts +6 -15
  78. package/src/index.ts +2 -0
  79. package/src/plugin.ts +51 -1
  80. package/src/portal/LiveChat.tsx +1 -1
  81. package/src/portal/auth/profile/page.tsx +3 -2
  82. package/src/portal/icons.tsx +91 -0
  83. package/src/styles/BillingView.module.scss +11 -11
  84. package/src/styles/ChatView.module.scss +7 -7
  85. package/src/styles/CommandPalette.module.scss +1 -1
  86. package/src/styles/CrmView.module.scss +9 -9
  87. package/src/styles/EmailTracking.module.scss +6 -6
  88. package/src/styles/ImportConversation.module.scss +5 -5
  89. package/src/styles/Logs.module.scss +5 -5
  90. package/src/styles/NewTicket.module.scss +2 -2
  91. package/src/styles/PendingEmails.module.scss +13 -13
  92. package/src/styles/SupportDashboard.module.scss +24 -8
  93. package/src/styles/TicketDetail.module.scss +20 -20
  94. package/src/styles/TicketInbox.module.scss +6 -6
  95. package/src/styles/TicketingSettings.module.scss +10 -10
  96. package/src/styles/TimeDashboard.module.scss +6 -6
  97. package/src/styles/_tokens.scss +3 -1
  98. package/src/types.ts +20 -0
  99. package/src/utils/retention.ts +111 -0
  100. package/src/utils/slugs.ts +15 -0
  101. package/src/views/BillingView/client.tsx +17 -8
  102. package/src/views/ChatView/client.tsx +2 -0
  103. package/src/views/CrmView/client.tsx +2 -0
  104. package/src/views/EmailTrackingView/client.tsx +18 -7
  105. package/src/views/ImportConversationView/client.tsx +1 -0
  106. package/src/views/LogsView/client.tsx +1 -1
  107. package/src/views/NewTicketView/client.tsx +22 -13
  108. package/src/views/PendingEmailsView/client.tsx +9 -3
  109. package/src/views/SupportDashboardView/client.tsx +11 -5
  110. package/src/views/TicketDetailView/client.tsx +25 -11
  111. package/src/views/TicketInboxView/client.tsx +47 -7
  112. package/src/views/TicketingSettingsView/TicketingSettings.module.scss +7 -7
  113. package/src/views/TicketingSettingsView/client.tsx +201 -144
  114. package/src/views/TimeDashboardView/client.tsx +10 -7
  115. package/src/views/shared/ErrorBoundary.tsx +95 -31
  116. package/src/views/shared/adminTokens.ts +28 -11
  117. package/src/views/shared/icons.tsx +146 -0
  118. package/src/views/shared/locales/en.json +15 -1
  119. package/src/views/shared/locales/fr.json +15 -1
  120. package/src/types/lucide-react.d.ts +0 -42
package/dist/index.d.ts CHANGED
@@ -65,6 +65,94 @@ declare class RateLimiter {
65
65
  reset(key: string, context?: unknown): Promise<void>;
66
66
  }
67
67
 
68
+ /**
69
+ * Default collection slugs used by the support plugin.
70
+ * All slugs can be overridden via SupportPluginConfig.collectionSlugs.
71
+ */
72
+ interface CollectionSlugs {
73
+ tickets: string;
74
+ ticketMessages: string;
75
+ supportClients: string;
76
+ timeEntries: string;
77
+ cannedResponses: string;
78
+ ticketActivityLog: string;
79
+ satisfactionSurveys: string;
80
+ knowledgeBase: string;
81
+ chatMessages: string;
82
+ pendingEmails: string;
83
+ emailLogs: string;
84
+ authLogs: string;
85
+ webhookEndpoints: string;
86
+ slaPolicies: string;
87
+ macros: string;
88
+ ticketStatuses: string;
89
+ ticketFeedback: string;
90
+ notificationQueue: string;
91
+ automationRules: string;
92
+ supportTeams: string;
93
+ pushSubscriptions: string;
94
+ rateLimits: string;
95
+ counters: string;
96
+ users: string;
97
+ media: string;
98
+ }
99
+ declare const DEFAULT_SLUGS: CollectionSlugs;
100
+ /**
101
+ * Resolve collection slugs merging user overrides with defaults.
102
+ */
103
+ declare function resolveSlugs(overrides?: Partial<CollectionSlugs>): CollectionSlugs;
104
+
105
+ /**
106
+ * How long the two journals are kept, in days.
107
+ *
108
+ * Defaults follow the CNIL guidance the README documents: six months would be
109
+ * the floor for connection logs, a year is the ceiling usually accepted, and
110
+ * these two values sit inside that range while staying long enough to
111
+ * investigate an intrusion. Set a value to 0 to disable the automatic purge of
112
+ * that journal — never to mean "delete everything", which is what `days=0`
113
+ * means on the manual endpoint.
114
+ */
115
+ interface RetentionConfig {
116
+ /** Retention of `auth-logs` in days. `0` disables the scheduled purge. */
117
+ authLogsDays?: number;
118
+ /** Retention of `email-logs` in days. `0` disables the scheduled purge. */
119
+ emailLogsDays?: number;
120
+ /**
121
+ * Cron for the scheduled purge. Payload's cron takes an optional leading
122
+ * seconds field. Default: every day at 03:30.
123
+ */
124
+ cron?: string;
125
+ /** Queue the scheduled job is pushed to. Default: `default`. */
126
+ queue?: string;
127
+ }
128
+ declare const DEFAULT_RETENTION: {
129
+ readonly authLogsDays: 180;
130
+ readonly emailLogsDays: 365;
131
+ readonly cron: "0 30 3 * * *";
132
+ readonly queue: "default";
133
+ };
134
+ declare const PURGE_LOGS_TASK_SLUG = "support-purge-logs";
135
+ /** Journals the purge is allowed to touch, keyed by their public name. */
136
+ declare function purgeableCollections(slugs: CollectionSlugs): Record<string, string>;
137
+ /**
138
+ * Delete rows of `slug` created more than `days` days ago.
139
+ *
140
+ * `days <= 0` means "everything" and is only reachable from the manual
141
+ * admin-only endpoint. The scheduled task refuses it: a misconfigured cron
142
+ * that wipes the whole security journal every night is a far worse outcome
143
+ * than a journal that grows.
144
+ */
145
+ declare function purgeOlderThan(payload: Payload, slug: string, days: number, req?: PayloadRequest): Promise<number>;
146
+ interface ScheduledPurgeResult {
147
+ purged: Record<string, number>;
148
+ skipped: string[];
149
+ }
150
+ /**
151
+ * The scheduled half of the purge. Never accepts `days <= 0`, and silently
152
+ * skips a journal whose collection is not registered (its feature flag is off).
153
+ */
154
+ declare function runScheduledPurge(payload: Payload, slugs: CollectionSlugs, retention: RetentionConfig | false | undefined, req?: PayloadRequest): Promise<ScheduledPurgeResult>;
155
+
68
156
  interface InboundEmailLimits {
69
157
  maxRequestBytes: number;
70
158
  maxAttachmentBytes: number;
@@ -228,6 +316,24 @@ interface SupportPluginConfig {
228
316
  email?: EmailConfig;
229
317
  /** Shared rate-limit storage. Defaults to process-local memory. */
230
318
  rateLimitStore?: RateLimitStore | 'payload';
319
+ /**
320
+ * Automatic retention of the two journals the plugin writes.
321
+ *
322
+ * The plugin registers a daily Payload Jobs task that deletes rows older
323
+ * than the configured number of days. Set a value to 0 to keep that journal
324
+ * forever (the manual `DELETE /api/support/purge-logs` endpoint stays
325
+ * available either way).
326
+ *
327
+ * Defaults: auth-logs 180 days, email-logs 365 days, cron `0 30 3 * * *`.
328
+ *
329
+ * `false` registers no task at all. That is the ONLY value that changes the
330
+ * schema footprint: registering a task turns Payload's job queue on, which
331
+ * adds the `payload-jobs` collection and the `payload-jobs-stats` global to
332
+ * an app that had no jobs — two tables you would have to migrate. Pass
333
+ * `false` if you would rather purge from your own cron, calling
334
+ * `DELETE /api/support/purge-logs`.
335
+ */
336
+ retention?: RetentionConfig | false;
231
337
  /** Sequential ticket number formatting. */
232
338
  ticketNumber?: {
233
339
  prefix?: string;
@@ -376,43 +482,6 @@ declare const DEFAULT_FEATURES: Required<SupportFeatures>;
376
482
  */
377
483
  declare function supportPlugin(config?: SupportPluginConfig): Plugin;
378
484
 
379
- /**
380
- * Default collection slugs used by the support plugin.
381
- * All slugs can be overridden via SupportPluginConfig.collectionSlugs.
382
- */
383
- interface CollectionSlugs {
384
- tickets: string;
385
- ticketMessages: string;
386
- supportClients: string;
387
- timeEntries: string;
388
- cannedResponses: string;
389
- ticketActivityLog: string;
390
- satisfactionSurveys: string;
391
- knowledgeBase: string;
392
- chatMessages: string;
393
- pendingEmails: string;
394
- emailLogs: string;
395
- authLogs: string;
396
- webhookEndpoints: string;
397
- slaPolicies: string;
398
- macros: string;
399
- ticketStatuses: string;
400
- ticketFeedback: string;
401
- notificationQueue: string;
402
- automationRules: string;
403
- supportTeams: string;
404
- pushSubscriptions: string;
405
- rateLimits: string;
406
- counters: string;
407
- users: string;
408
- media: string;
409
- }
410
- declare const DEFAULT_SLUGS: CollectionSlugs;
411
- /**
412
- * Resolve collection slugs merging user overrides with defaults.
413
- */
414
- declare function resolveSlugs(overrides?: Partial<CollectionSlugs>): CollectionSlugs;
415
-
416
485
  /**
417
486
  * Ticketing feature flags — single source of truth (client + server).
418
487
  *
@@ -710,4 +779,4 @@ declare function createTicketStatusesCollection(slugs: CollectionSlugs): Collect
710
779
  */
711
780
  declare function createTicketCollaboratorsCollection(slugs: CollectionSlugs): CollectionConfig;
712
781
 
713
- export { type AIProviderConfig, type ActivityEntryData, type CannedResponseData, type ClientData, type CollectionSlugs, DEFAULT_FEATURES, DEFAULT_INBOUND_EMAIL_LIMITS, DEFAULT_SETTINGS, DEFAULT_SLUGS, DEFAULT_TICKETING_FEATURES, DEFAULT_USER_PREFS, type EmailConfig, type InboundEmailInput, type InboundEmailLimits, type InboundEmailValidationError, MemoryRateLimitStore, type MessageData, PayloadRateLimitStore, type RateLimitEntry, type RateLimitStore, RateLimiter, type SatisfactionSurveyData, type SupportFeatures, type SupportPluginConfig, type SupportSettings, type SupportSettingsState, type TicketData, type TicketSynthesisResult, type TicketingFeatures, type TimeEntryData, type UserPrefs, calculateBusinessHoursDeadline, createAdminNotification, createAssignSlaDeadlines, createAuthLogsCollection, createCannedResponsesCollection, createChatMessagesCollection, createCheckSlaOnReply, createCheckSlaOnResolve, createEmailLogsCollection, createKnowledgeBaseCollection, createLoginEndpoint, createMacrosCollection, createOAuthGoogleEndpoint, createPendingEmailsCollection, createSatisfactionSurveysCollection, createSlaPoliciesCollection, createSupportClientsCollection, createTicketActivityLogCollection, createTicketCollaboratorsCollection, createTicketMessagesCollection, createTicketStatusEmail, createTicketStatusesCollection, createTicketsCollection, createTimeEntriesCollection, createTrackOpenEndpoint, createWebhookEndpointsCollection, dispatchWebhook, generateTicketSynthesis, normalizeFeatures, projectAutoClose, readSupportSettings, readSupportSettingsState, readUserPrefs, resolveSlugs, stripProjectedFeatures, supportPlugin, validateInboundEmailPayload, verifySecret };
782
+ export { type AIProviderConfig, type ActivityEntryData, type CannedResponseData, type ClientData, type CollectionSlugs, DEFAULT_FEATURES, DEFAULT_INBOUND_EMAIL_LIMITS, DEFAULT_RETENTION, DEFAULT_SETTINGS, DEFAULT_SLUGS, DEFAULT_TICKETING_FEATURES, DEFAULT_USER_PREFS, type EmailConfig, type InboundEmailInput, type InboundEmailLimits, type InboundEmailValidationError, MemoryRateLimitStore, type MessageData, PURGE_LOGS_TASK_SLUG, PayloadRateLimitStore, type RateLimitEntry, type RateLimitStore, RateLimiter, type RetentionConfig, type SatisfactionSurveyData, type ScheduledPurgeResult, type SupportFeatures, type SupportPluginConfig, type SupportSettings, type SupportSettingsState, type TicketData, type TicketSynthesisResult, type TicketingFeatures, type TimeEntryData, type UserPrefs, calculateBusinessHoursDeadline, createAdminNotification, createAssignSlaDeadlines, createAuthLogsCollection, createCannedResponsesCollection, createChatMessagesCollection, createCheckSlaOnReply, createCheckSlaOnResolve, createEmailLogsCollection, createKnowledgeBaseCollection, createLoginEndpoint, createMacrosCollection, createOAuthGoogleEndpoint, createPendingEmailsCollection, createSatisfactionSurveysCollection, createSlaPoliciesCollection, createSupportClientsCollection, createTicketActivityLogCollection, createTicketCollaboratorsCollection, createTicketMessagesCollection, createTicketStatusEmail, createTicketStatusesCollection, createTicketsCollection, createTimeEntriesCollection, createTrackOpenEndpoint, createWebhookEndpointsCollection, dispatchWebhook, generateTicketSynthesis, normalizeFeatures, projectAutoClose, purgeOlderThan, purgeableCollections, readSupportSettings, readSupportSettingsState, readUserPrefs, resolveSlugs, runScheduledPurge, stripProjectedFeatures, supportPlugin, validateInboundEmailPayload, verifySecret };
package/dist/index.js CHANGED
@@ -72,6 +72,11 @@ var DEFAULT_SLUGS = {
72
72
  users: "users",
73
73
  media: "media"
74
74
  };
75
+ var FIXED_SLUGS = {
76
+ clientSummaries: "client-summaries",
77
+ ticketCollaborators: "ticket-collaborators",
78
+ ticketFeedback: "ticket-feedback"
79
+ };
75
80
  function resolveSlugs(overrides) {
76
81
  return { ...DEFAULT_SLUGS, ...overrides };
77
82
  }
@@ -2988,6 +2993,51 @@ function createApplyMacroEndpoint(slugs) {
2988
2993
  };
2989
2994
  }
2990
2995
 
2996
+ // src/utils/retention.ts
2997
+ var DEFAULT_RETENTION = {
2998
+ authLogsDays: 180,
2999
+ emailLogsDays: 365,
3000
+ cron: "0 30 3 * * *",
3001
+ queue: "default"
3002
+ };
3003
+ var PURGE_LOGS_TASK_SLUG = "support-purge-logs";
3004
+ function purgeableCollections(slugs) {
3005
+ return {
3006
+ "email-logs": slugs.emailLogs,
3007
+ "auth-logs": slugs.authLogs
3008
+ };
3009
+ }
3010
+ async function purgeOlderThan(payload, slug, days, req) {
3011
+ const cutoff = days > 0 ? new Date(Date.now() - days * 864e5).toISOString() : null;
3012
+ const result = await dbDelete(payload, slug, {
3013
+ where: cutoff ? { createdAt: { less_than: cutoff } } : { id: { exists: true } },
3014
+ overrideAccess: true,
3015
+ ...req ? { req } : {}
3016
+ });
3017
+ return Array.isArray(result?.docs) ? result.docs.length : 0;
3018
+ }
3019
+ async function runScheduledPurge(payload, slugs, retention, req) {
3020
+ if (retention === false) return { purged: {}, skipped: ["auth-logs", "email-logs"] };
3021
+ const plan = [
3022
+ ["auth-logs", slugs.authLogs, retention?.authLogsDays ?? DEFAULT_RETENTION.authLogsDays],
3023
+ ["email-logs", slugs.emailLogs, retention?.emailLogsDays ?? DEFAULT_RETENTION.emailLogsDays]
3024
+ ];
3025
+ const purged = {};
3026
+ const skipped = [];
3027
+ for (const [name, slug, days] of plan) {
3028
+ if (!Number.isFinite(days) || days <= 0) {
3029
+ skipped.push(name);
3030
+ continue;
3031
+ }
3032
+ if (!payload.collections?.[slug]) {
3033
+ skipped.push(name);
3034
+ continue;
3035
+ }
3036
+ purged[name] = await purgeOlderThan(payload, slug, days, req);
3037
+ }
3038
+ return { purged, skipped };
3039
+ }
3040
+
2991
3041
  // src/endpoints/purge-logs.ts
2992
3042
  function createPurgeLogsEndpoint(slugs) {
2993
3043
  return {
@@ -3000,19 +3050,11 @@ function createPurgeLogsEndpoint(slugs) {
3000
3050
  const url = new URL(req.url);
3001
3051
  const collection = url.searchParams.get("collection");
3002
3052
  const days = Number(url.searchParams.get("days") || "0");
3003
- const allowedCollections = {
3004
- "email-logs": slugs.emailLogs,
3005
- "auth-logs": slugs.authLogs
3006
- };
3053
+ const allowedCollections = purgeableCollections(slugs);
3007
3054
  if (!collection || !allowedCollections[collection]) {
3008
3055
  return Response.json({ error: "Invalid collection. Use email-logs or auth-logs." }, { status: 400 });
3009
3056
  }
3010
- const cutoff = days > 0 ? new Date(Date.now() - days * 864e5).toISOString() : null;
3011
- const result = await dbDelete(payload, allowedCollections[collection], {
3012
- where: cutoff ? { createdAt: { less_than: cutoff } } : { id: { exists: true } },
3013
- overrideAccess: true
3014
- });
3015
- const count = Array.isArray(result.docs) ? result.docs.length : 0;
3057
+ const count = await purgeOlderThan(payload, allowedCollections[collection], days, req);
3016
3058
  return Response.json({
3017
3059
  purged: count,
3018
3060
  collection,
@@ -4735,6 +4777,7 @@ function createExportCsvEndpoint(slugs) {
4735
4777
  }
4736
4778
 
4737
4779
  // src/endpoints/export-data.ts
4780
+ var EMPTY = { docs: [] };
4738
4781
  function createExportDataEndpoint(slugs) {
4739
4782
  return {
4740
4783
  path: "/support/export-data",
@@ -4743,7 +4786,16 @@ function createExportDataEndpoint(slugs) {
4743
4786
  try {
4744
4787
  const payload = req.payload;
4745
4788
  requireClient(req, slugs);
4746
- const [clientData, ticketsResult, messagesResult, surveysResult] = await Promise.all([
4789
+ const isRegistered = (slug) => Boolean(payload.collections?.[slug]);
4790
+ const [
4791
+ clientData,
4792
+ ticketsResult,
4793
+ messagesResult,
4794
+ surveysResult,
4795
+ chatResult,
4796
+ feedbackResult,
4797
+ summariesResult
4798
+ ] = await Promise.all([
4747
4799
  dbFindByID(payload, slugs.supportClients, {
4748
4800
  id: req.user.id,
4749
4801
  depth: 0,
@@ -4769,12 +4821,35 @@ function createExportDataEndpoint(slugs) {
4769
4821
  limit: 500,
4770
4822
  depth: 0,
4771
4823
  overrideAccess: true
4772
- })
4824
+ }),
4825
+ isRegistered(slugs.chatMessages) ? dbFind(payload, slugs.chatMessages, {
4826
+ where: { client: { equals: req.user.id } },
4827
+ limit: 5e3,
4828
+ depth: 0,
4829
+ overrideAccess: true
4830
+ }) : EMPTY,
4831
+ isRegistered(FIXED_SLUGS.ticketFeedback) ? dbFind(payload, FIXED_SLUGS.ticketFeedback, {
4832
+ where: { client: { equals: req.user.id } },
4833
+ limit: 500,
4834
+ depth: 0,
4835
+ overrideAccess: true
4836
+ }) : EMPTY,
4837
+ isRegistered(FIXED_SLUGS.clientSummaries) ? dbFind(payload, FIXED_SLUGS.clientSummaries, {
4838
+ where: { client: { equals: req.user.id } },
4839
+ limit: 100,
4840
+ depth: 0,
4841
+ overrideAccess: true
4842
+ }) : EMPTY
4773
4843
  ]);
4844
+ const ticketIds = ticketsResult.docs.map((t) => t.id);
4845
+ const timeResult = ticketIds.length > 0 && isRegistered(slugs.timeEntries) ? await dbFind(payload, slugs.timeEntries, {
4846
+ where: { ticket: { in: ticketIds } },
4847
+ limit: 5e3,
4848
+ depth: 0,
4849
+ overrideAccess: true
4850
+ }) : EMPTY;
4774
4851
  const c = clientData;
4775
- const exportData = {
4776
- exportDate: (/* @__PURE__ */ new Date()).toISOString(),
4777
- exportType: "RGPD - Export des donn\xE9es personnelles",
4852
+ const providedByYou = {
4778
4853
  profile: {
4779
4854
  email: c.email,
4780
4855
  firstName: c.firstName,
@@ -4803,8 +4878,50 @@ function createExportDataEndpoint(slugs) {
4803
4878
  rating: s.rating,
4804
4879
  comment: s.comment,
4805
4880
  createdAt: s.createdAt
4881
+ })),
4882
+ feedback: feedbackResult.docs.map((f) => ({
4883
+ ticketId: f.ticket,
4884
+ rating: f.rating,
4885
+ comment: f.comment,
4886
+ submittedFrom: f.submittedFrom,
4887
+ createdAt: f.createdAt
4888
+ })),
4889
+ chatMessages: chatResult.docs.map((m) => ({
4890
+ session: m.session,
4891
+ senderType: m.senderType,
4892
+ message: m.message,
4893
+ createdAt: m.createdAt
4894
+ })),
4895
+ timeEntries: timeResult.docs.map((e) => ({
4896
+ ticketId: e.ticket,
4897
+ duration: e.duration,
4898
+ date: e.date,
4899
+ description: e.description,
4900
+ billable: e.billable
4806
4901
  }))
4807
4902
  };
4903
+ const exportData = {
4904
+ exportDate: (/* @__PURE__ */ new Date()).toISOString(),
4905
+ exportType: "RGPD - Export des donn\xE9es personnelles",
4906
+ providedByYou: {
4907
+ legalBasis: "Art. 20 RGPD - droit \xE0 la portabilit\xE9",
4908
+ ...providedByYou
4909
+ },
4910
+ derivedData: {
4911
+ legalBasis: "Art. 15 RGPD - droit d'acc\xE8s (donn\xE9es d\xE9riv\xE9es, hors portabilit\xE9)",
4912
+ clientSummaries: summariesResult.docs.map((s) => ({
4913
+ summary: s.summary,
4914
+ recurringTopics: s.recurringTopics,
4915
+ patterns: s.patterns,
4916
+ keyFacts: s.keyFacts,
4917
+ ticketCount: s.ticketCount,
4918
+ messageCount: s.messageCount,
4919
+ averageSatisfaction: s.averageSatisfaction,
4920
+ generatedAt: s.generatedAt,
4921
+ aiModel: s.aiModel
4922
+ }))
4923
+ }
4924
+ };
4808
4925
  const json = JSON.stringify(exportData, null, 2);
4809
4926
  return new Response(json, {
4810
4927
  status: 200,
@@ -5718,8 +5835,15 @@ function createOAuthGoogleEndpoint(slugs, options) {
5718
5835
  }
5719
5836
  };
5720
5837
  }
5721
-
5722
- // src/endpoints/delete-account.ts
5838
+ function collectAttachmentIds(docs, into) {
5839
+ for (const doc of docs) {
5840
+ for (const attachment of doc.attachments ?? []) {
5841
+ const file = attachment?.file;
5842
+ const id = file != null && typeof file === "object" ? file.id : file;
5843
+ if (id != null) into.add(id);
5844
+ }
5845
+ }
5846
+ }
5723
5847
  function createDeleteAccountEndpoint(slugs) {
5724
5848
  return {
5725
5849
  path: "/support/delete-account",
@@ -5748,44 +5872,135 @@ function createDeleteAccountEndpoint(slugs) {
5748
5872
  );
5749
5873
  }
5750
5874
  const clientId = req.user.id;
5751
- const tickets = await dbFind(payload, slugs.tickets, {
5752
- where: { client: { equals: clientId } },
5753
- limit: 1e4,
5754
- depth: 0,
5755
- overrideAccess: true,
5756
- select: { id: true }
5757
- });
5758
- const ticketIds = tickets.docs.map((t) => t.id);
5759
- if (ticketIds.length > 0) {
5760
- await dbDelete(payload, slugs.ticketMessages, {
5761
- where: { ticket: { in: ticketIds } },
5762
- overrideAccess: true
5763
- });
5764
- await dbDelete(payload, slugs.ticketActivityLog, {
5765
- where: { ticket: { in: ticketIds } },
5766
- overrideAccess: true
5767
- });
5768
- await dbDelete(payload, slugs.timeEntries, {
5769
- where: { ticket: { in: ticketIds } },
5770
- overrideAccess: true
5771
- });
5772
- await dbDelete(payload, slugs.tickets, {
5875
+ const clientEmail = req.user.email;
5876
+ const isRegistered = (slug) => Boolean(payload.collections?.[slug]);
5877
+ const startedTransaction = await initTransaction(req);
5878
+ try {
5879
+ const tickets = await dbFind(payload, slugs.tickets, {
5773
5880
  where: { client: { equals: clientId } },
5774
- overrideAccess: true
5881
+ limit: 1e4,
5882
+ depth: 0,
5883
+ overrideAccess: true,
5884
+ select: { id: true },
5885
+ req
5775
5886
  });
5887
+ const ticketIds = tickets.docs.map((t) => t.id);
5888
+ const attachmentIds = /* @__PURE__ */ new Set();
5889
+ if (ticketIds.length > 0) {
5890
+ const messages = await dbFind(
5891
+ payload,
5892
+ slugs.ticketMessages,
5893
+ {
5894
+ where: { ticket: { in: ticketIds } },
5895
+ limit: 1e4,
5896
+ depth: 0,
5897
+ overrideAccess: true,
5898
+ req
5899
+ }
5900
+ );
5901
+ collectAttachmentIds(messages.docs, attachmentIds);
5902
+ }
5903
+ if (isRegistered(slugs.pendingEmails)) {
5904
+ const pendingEmails = await dbFind(
5905
+ payload,
5906
+ slugs.pendingEmails,
5907
+ {
5908
+ where: { client: { equals: clientId } },
5909
+ limit: 1e4,
5910
+ depth: 0,
5911
+ overrideAccess: true,
5912
+ req
5913
+ }
5914
+ );
5915
+ collectAttachmentIds(pendingEmails.docs, attachmentIds);
5916
+ }
5917
+ if (ticketIds.length > 0) {
5918
+ for (const slug of [FIXED_SLUGS.ticketFeedback, FIXED_SLUGS.ticketCollaborators]) {
5919
+ if (!isRegistered(slug)) continue;
5920
+ await dbDelete(payload, slug, {
5921
+ where: {
5922
+ or: [
5923
+ { ticket: { in: ticketIds } },
5924
+ { client: { equals: clientId } }
5925
+ ]
5926
+ },
5927
+ overrideAccess: true,
5928
+ req
5929
+ });
5930
+ }
5931
+ }
5932
+ if (ticketIds.length > 0) {
5933
+ await dbDelete(payload, slugs.tickets, {
5934
+ where: { client: { equals: clientId } },
5935
+ overrideAccess: true,
5936
+ req
5937
+ });
5938
+ }
5939
+ for (const slug of [
5940
+ slugs.satisfactionSurveys,
5941
+ slugs.chatMessages,
5942
+ slugs.notificationQueue,
5943
+ slugs.pendingEmails,
5944
+ FIXED_SLUGS.clientSummaries,
5945
+ FIXED_SLUGS.ticketFeedback
5946
+ ]) {
5947
+ if (!isRegistered(slug)) continue;
5948
+ await dbDelete(payload, slug, {
5949
+ where: { client: { equals: clientId } },
5950
+ overrideAccess: true,
5951
+ req
5952
+ });
5953
+ }
5954
+ if (isRegistered(FIXED_SLUGS.ticketCollaborators)) {
5955
+ await dbDelete(payload, FIXED_SLUGS.ticketCollaborators, {
5956
+ where: {
5957
+ or: [
5958
+ { client: { equals: clientId } },
5959
+ ...clientEmail ? [{ email: { equals: clientEmail } }] : []
5960
+ ]
5961
+ },
5962
+ overrideAccess: true,
5963
+ req
5964
+ });
5965
+ }
5966
+ if (clientEmail) {
5967
+ if (isRegistered(slugs.authLogs)) {
5968
+ await dbDelete(payload, slugs.authLogs, {
5969
+ where: { email: { equals: clientEmail } },
5970
+ overrideAccess: true,
5971
+ req
5972
+ });
5973
+ }
5974
+ if (isRegistered(slugs.emailLogs)) {
5975
+ await dbDelete(payload, slugs.emailLogs, {
5976
+ where: {
5977
+ or: [
5978
+ { senderEmail: { equals: clientEmail } },
5979
+ { recipientEmail: { equals: clientEmail } }
5980
+ ]
5981
+ },
5982
+ overrideAccess: true,
5983
+ req
5984
+ });
5985
+ }
5986
+ }
5987
+ for (const id of isRegistered(slugs.media) ? attachmentIds : []) {
5988
+ await dbDelete(payload, slugs.media, {
5989
+ id,
5990
+ overrideAccess: true,
5991
+ req
5992
+ });
5993
+ }
5994
+ await dbDelete(payload, slugs.supportClients, {
5995
+ id: clientId,
5996
+ overrideAccess: true,
5997
+ req
5998
+ });
5999
+ if (startedTransaction) await commitTransaction(req);
6000
+ } catch (err) {
6001
+ if (startedTransaction) await killTransaction(req);
6002
+ throw err;
5776
6003
  }
5777
- await dbDelete(payload, slugs.satisfactionSurveys, {
5778
- where: { client: { equals: clientId } },
5779
- overrideAccess: true
5780
- });
5781
- await dbDelete(payload, slugs.chatMessages, {
5782
- where: { client: { equals: clientId } },
5783
- overrideAccess: true
5784
- });
5785
- await dbDelete(payload, slugs.supportClients, {
5786
- id: clientId,
5787
- overrideAccess: true
5788
- });
5789
6004
  const headers = new Headers({ "Content-Type": "application/json" });
5790
6005
  const secure = process.env.NODE_ENV === "production";
5791
6006
  headers.append(
@@ -8428,9 +8643,11 @@ function createCascadeDelete(slugs) {
8428
8643
  return async ({ id, req }) => {
8429
8644
  const collections = [slugs.ticketMessages, slugs.ticketActivityLog, slugs.timeEntries, slugs.satisfactionSurveys];
8430
8645
  for (const slug of collections) {
8646
+ if (!req.payload.collections?.[slug]) continue;
8431
8647
  await dbDelete(req.payload, slug, {
8432
8648
  where: { ticket: { equals: id } },
8433
- overrideAccess: true
8649
+ overrideAccess: true,
8650
+ req
8434
8651
  });
8435
8652
  }
8436
8653
  };
@@ -9918,7 +10135,7 @@ function createSupportClientsCollection(slugs, options) {
9918
10135
  }
9919
10136
 
9920
10137
  // src/collections/TimeEntries.ts
9921
- async function recalculateTicketTime(payload, slugs, ticketId) {
10138
+ async function recalculateTicketTime(payload, slugs, ticketId, req) {
9922
10139
  let totalMinutes = 0;
9923
10140
  let page = 1;
9924
10141
  let hasMore = true;
@@ -9930,7 +10147,8 @@ async function recalculateTicketTime(payload, slugs, ticketId) {
9930
10147
  page,
9931
10148
  depth: 0,
9932
10149
  overrideAccess: true,
9933
- select: { duration: true }
10150
+ select: { duration: true },
10151
+ req
9934
10152
  });
9935
10153
  for (const entry of entries.docs) {
9936
10154
  totalMinutes += entry.duration || 0;
@@ -9942,14 +10160,15 @@ async function recalculateTicketTime(payload, slugs, ticketId) {
9942
10160
  collection: slugs.tickets,
9943
10161
  id: ticketId,
9944
10162
  data: { totalTimeMinutes: totalMinutes },
9945
- overrideAccess: true
10163
+ overrideAccess: true,
10164
+ req
9946
10165
  });
9947
10166
  }
9948
10167
  function createRecalculateTicketTime(slugs) {
9949
10168
  return async ({ doc, req }) => {
9950
10169
  if (!doc.ticket) return;
9951
10170
  const ticketId = typeof doc.ticket === "object" ? doc.ticket.id : doc.ticket;
9952
- await recalculateTicketTime(req.payload, slugs, ticketId);
10171
+ await recalculateTicketTime(req.payload, slugs, ticketId, req);
9953
10172
  };
9954
10173
  }
9955
10174
  function createRecalculateTicketTimeOnDelete(slugs) {
@@ -9957,7 +10176,7 @@ function createRecalculateTicketTimeOnDelete(slugs) {
9957
10176
  if (!doc?.ticket) return;
9958
10177
  const ticketId = typeof doc.ticket === "object" ? doc.ticket.id : doc.ticket;
9959
10178
  try {
9960
- await recalculateTicketTime(req.payload, slugs, ticketId);
10179
+ await recalculateTicketTime(req.payload, slugs, ticketId, req);
9961
10180
  } catch (err) {
9962
10181
  console.error("[support] Failed to recalculate ticket time after delete:", err);
9963
10182
  }
@@ -11828,8 +12047,30 @@ function supportPlugin(config) {
11828
12047
  rateLimitStore,
11829
12048
  capabilities: config?.capabilities
11830
12049
  });
12050
+ const retention = config?.retention === false ? void 0 : config?.retention;
12051
+ const retentionEnabled = Boolean(retention);
12052
+ const purgeTask = {
12053
+ slug: PURGE_LOGS_TASK_SLUG,
12054
+ label: "Support \u2014 purge des journaux",
12055
+ schedule: [
12056
+ {
12057
+ cron: retention?.cron ?? DEFAULT_RETENTION.cron,
12058
+ queue: retention?.queue ?? DEFAULT_RETENTION.queue
12059
+ }
12060
+ ],
12061
+ handler: async ({ req }) => {
12062
+ const result = await runScheduledPurge(req.payload, slugs, retention, req);
12063
+ return { output: result };
12064
+ }
12065
+ };
12066
+ const existingJobs = incomingConfig.jobs;
12067
+ const jobs = retentionEnabled ? {
12068
+ ...existingJobs,
12069
+ tasks: [...existingJobs?.tasks ?? [], purgeTask]
12070
+ } : existingJobs;
11831
12071
  return {
11832
12072
  ...incomingConfig,
12073
+ jobs,
11833
12074
  // Publish the resolved staff collection so the server-side readers share
11834
12075
  // ONE source of truth with the writers. `requireAdmin` compares against
11835
12076
  // `slugs.users`; the `payload-preferences` reads used to scope themselves
@@ -11854,4 +12095,4 @@ function supportPlugin(config) {
11854
12095
  };
11855
12096
  }
11856
12097
 
11857
- export { DEFAULT_FEATURES, DEFAULT_INBOUND_EMAIL_LIMITS, DEFAULT_SETTINGS, DEFAULT_SLUGS, DEFAULT_TICKETING_FEATURES, DEFAULT_USER_PREFS, MemoryRateLimitStore, PayloadRateLimitStore, RateLimiter, calculateBusinessHoursDeadline, createAdminNotification, createAssignSlaDeadlines, createAuthLogsCollection, createCannedResponsesCollection, createChatMessagesCollection, createCheckSlaOnReply, createCheckSlaOnResolve, createEmailLogsCollection, createKnowledgeBaseCollection, createLoginEndpoint, createMacrosCollection, createOAuthGoogleEndpoint, createPendingEmailsCollection, createSatisfactionSurveysCollection, createSlaPoliciesCollection, createSupportClientsCollection, createTicketActivityLogCollection, createTicketCollaboratorsCollection, createTicketMessagesCollection, createTicketStatusEmail, createTicketStatusesCollection, createTicketsCollection, createTimeEntriesCollection, createTrackOpenEndpoint, createWebhookEndpointsCollection, dispatchWebhook, generateTicketSynthesis, normalizeFeatures, projectAutoClose, readSupportSettings, readSupportSettingsState, readUserPrefs, resolveSlugs, stripProjectedFeatures, supportPlugin, validateInboundEmailPayload, verifySecret };
12098
+ export { DEFAULT_FEATURES, DEFAULT_INBOUND_EMAIL_LIMITS, DEFAULT_RETENTION, DEFAULT_SETTINGS, DEFAULT_SLUGS, DEFAULT_TICKETING_FEATURES, DEFAULT_USER_PREFS, MemoryRateLimitStore, PURGE_LOGS_TASK_SLUG, PayloadRateLimitStore, RateLimiter, calculateBusinessHoursDeadline, createAdminNotification, createAssignSlaDeadlines, createAuthLogsCollection, createCannedResponsesCollection, createChatMessagesCollection, createCheckSlaOnReply, createCheckSlaOnResolve, createEmailLogsCollection, createKnowledgeBaseCollection, createLoginEndpoint, createMacrosCollection, createOAuthGoogleEndpoint, createPendingEmailsCollection, createSatisfactionSurveysCollection, createSlaPoliciesCollection, createSupportClientsCollection, createTicketActivityLogCollection, createTicketCollaboratorsCollection, createTicketMessagesCollection, createTicketStatusEmail, createTicketStatusesCollection, createTicketsCollection, createTimeEntriesCollection, createTrackOpenEndpoint, createWebhookEndpointsCollection, dispatchWebhook, generateTicketSynthesis, normalizeFeatures, projectAutoClose, purgeOlderThan, purgeableCollections, readSupportSettings, readSupportSettingsState, readUserPrefs, resolveSlugs, runScheduledPurge, stripProjectedFeatures, supportPlugin, validateInboundEmailPayload, verifySecret };