@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.cjs CHANGED
@@ -81,6 +81,11 @@ var DEFAULT_SLUGS = {
81
81
  users: "users",
82
82
  media: "media"
83
83
  };
84
+ var FIXED_SLUGS = {
85
+ clientSummaries: "client-summaries",
86
+ ticketCollaborators: "ticket-collaborators",
87
+ ticketFeedback: "ticket-feedback"
88
+ };
84
89
  function resolveSlugs(overrides) {
85
90
  return { ...DEFAULT_SLUGS, ...overrides };
86
91
  }
@@ -2997,6 +3002,51 @@ function createApplyMacroEndpoint(slugs) {
2997
3002
  };
2998
3003
  }
2999
3004
 
3005
+ // src/utils/retention.ts
3006
+ var DEFAULT_RETENTION = {
3007
+ authLogsDays: 180,
3008
+ emailLogsDays: 365,
3009
+ cron: "0 30 3 * * *",
3010
+ queue: "default"
3011
+ };
3012
+ var PURGE_LOGS_TASK_SLUG = "support-purge-logs";
3013
+ function purgeableCollections(slugs) {
3014
+ return {
3015
+ "email-logs": slugs.emailLogs,
3016
+ "auth-logs": slugs.authLogs
3017
+ };
3018
+ }
3019
+ async function purgeOlderThan(payload, slug, days, req) {
3020
+ const cutoff = days > 0 ? new Date(Date.now() - days * 864e5).toISOString() : null;
3021
+ const result = await dbDelete(payload, slug, {
3022
+ where: cutoff ? { createdAt: { less_than: cutoff } } : { id: { exists: true } },
3023
+ overrideAccess: true,
3024
+ ...req ? { req } : {}
3025
+ });
3026
+ return Array.isArray(result?.docs) ? result.docs.length : 0;
3027
+ }
3028
+ async function runScheduledPurge(payload, slugs, retention, req) {
3029
+ if (retention === false) return { purged: {}, skipped: ["auth-logs", "email-logs"] };
3030
+ const plan = [
3031
+ ["auth-logs", slugs.authLogs, retention?.authLogsDays ?? DEFAULT_RETENTION.authLogsDays],
3032
+ ["email-logs", slugs.emailLogs, retention?.emailLogsDays ?? DEFAULT_RETENTION.emailLogsDays]
3033
+ ];
3034
+ const purged = {};
3035
+ const skipped = [];
3036
+ for (const [name, slug, days] of plan) {
3037
+ if (!Number.isFinite(days) || days <= 0) {
3038
+ skipped.push(name);
3039
+ continue;
3040
+ }
3041
+ if (!payload.collections?.[slug]) {
3042
+ skipped.push(name);
3043
+ continue;
3044
+ }
3045
+ purged[name] = await purgeOlderThan(payload, slug, days, req);
3046
+ }
3047
+ return { purged, skipped };
3048
+ }
3049
+
3000
3050
  // src/endpoints/purge-logs.ts
3001
3051
  function createPurgeLogsEndpoint(slugs) {
3002
3052
  return {
@@ -3009,19 +3059,11 @@ function createPurgeLogsEndpoint(slugs) {
3009
3059
  const url = new URL(req.url);
3010
3060
  const collection = url.searchParams.get("collection");
3011
3061
  const days = Number(url.searchParams.get("days") || "0");
3012
- const allowedCollections = {
3013
- "email-logs": slugs.emailLogs,
3014
- "auth-logs": slugs.authLogs
3015
- };
3062
+ const allowedCollections = purgeableCollections(slugs);
3016
3063
  if (!collection || !allowedCollections[collection]) {
3017
3064
  return Response.json({ error: "Invalid collection. Use email-logs or auth-logs." }, { status: 400 });
3018
3065
  }
3019
- const cutoff = days > 0 ? new Date(Date.now() - days * 864e5).toISOString() : null;
3020
- const result = await dbDelete(payload, allowedCollections[collection], {
3021
- where: cutoff ? { createdAt: { less_than: cutoff } } : { id: { exists: true } },
3022
- overrideAccess: true
3023
- });
3024
- const count = Array.isArray(result.docs) ? result.docs.length : 0;
3066
+ const count = await purgeOlderThan(payload, allowedCollections[collection], days, req);
3025
3067
  return Response.json({
3026
3068
  purged: count,
3027
3069
  collection,
@@ -4744,6 +4786,7 @@ function createExportCsvEndpoint(slugs) {
4744
4786
  }
4745
4787
 
4746
4788
  // src/endpoints/export-data.ts
4789
+ var EMPTY = { docs: [] };
4747
4790
  function createExportDataEndpoint(slugs) {
4748
4791
  return {
4749
4792
  path: "/support/export-data",
@@ -4752,7 +4795,16 @@ function createExportDataEndpoint(slugs) {
4752
4795
  try {
4753
4796
  const payload = req.payload;
4754
4797
  requireClient(req, slugs);
4755
- const [clientData, ticketsResult, messagesResult, surveysResult] = await Promise.all([
4798
+ const isRegistered = (slug) => Boolean(payload.collections?.[slug]);
4799
+ const [
4800
+ clientData,
4801
+ ticketsResult,
4802
+ messagesResult,
4803
+ surveysResult,
4804
+ chatResult,
4805
+ feedbackResult,
4806
+ summariesResult
4807
+ ] = await Promise.all([
4756
4808
  dbFindByID(payload, slugs.supportClients, {
4757
4809
  id: req.user.id,
4758
4810
  depth: 0,
@@ -4778,12 +4830,35 @@ function createExportDataEndpoint(slugs) {
4778
4830
  limit: 500,
4779
4831
  depth: 0,
4780
4832
  overrideAccess: true
4781
- })
4833
+ }),
4834
+ isRegistered(slugs.chatMessages) ? dbFind(payload, slugs.chatMessages, {
4835
+ where: { client: { equals: req.user.id } },
4836
+ limit: 5e3,
4837
+ depth: 0,
4838
+ overrideAccess: true
4839
+ }) : EMPTY,
4840
+ isRegistered(FIXED_SLUGS.ticketFeedback) ? dbFind(payload, FIXED_SLUGS.ticketFeedback, {
4841
+ where: { client: { equals: req.user.id } },
4842
+ limit: 500,
4843
+ depth: 0,
4844
+ overrideAccess: true
4845
+ }) : EMPTY,
4846
+ isRegistered(FIXED_SLUGS.clientSummaries) ? dbFind(payload, FIXED_SLUGS.clientSummaries, {
4847
+ where: { client: { equals: req.user.id } },
4848
+ limit: 100,
4849
+ depth: 0,
4850
+ overrideAccess: true
4851
+ }) : EMPTY
4782
4852
  ]);
4853
+ const ticketIds = ticketsResult.docs.map((t) => t.id);
4854
+ const timeResult = ticketIds.length > 0 && isRegistered(slugs.timeEntries) ? await dbFind(payload, slugs.timeEntries, {
4855
+ where: { ticket: { in: ticketIds } },
4856
+ limit: 5e3,
4857
+ depth: 0,
4858
+ overrideAccess: true
4859
+ }) : EMPTY;
4783
4860
  const c = clientData;
4784
- const exportData = {
4785
- exportDate: (/* @__PURE__ */ new Date()).toISOString(),
4786
- exportType: "RGPD - Export des donn\xE9es personnelles",
4861
+ const providedByYou = {
4787
4862
  profile: {
4788
4863
  email: c.email,
4789
4864
  firstName: c.firstName,
@@ -4812,8 +4887,50 @@ function createExportDataEndpoint(slugs) {
4812
4887
  rating: s.rating,
4813
4888
  comment: s.comment,
4814
4889
  createdAt: s.createdAt
4890
+ })),
4891
+ feedback: feedbackResult.docs.map((f) => ({
4892
+ ticketId: f.ticket,
4893
+ rating: f.rating,
4894
+ comment: f.comment,
4895
+ submittedFrom: f.submittedFrom,
4896
+ createdAt: f.createdAt
4897
+ })),
4898
+ chatMessages: chatResult.docs.map((m) => ({
4899
+ session: m.session,
4900
+ senderType: m.senderType,
4901
+ message: m.message,
4902
+ createdAt: m.createdAt
4903
+ })),
4904
+ timeEntries: timeResult.docs.map((e) => ({
4905
+ ticketId: e.ticket,
4906
+ duration: e.duration,
4907
+ date: e.date,
4908
+ description: e.description,
4909
+ billable: e.billable
4815
4910
  }))
4816
4911
  };
4912
+ const exportData = {
4913
+ exportDate: (/* @__PURE__ */ new Date()).toISOString(),
4914
+ exportType: "RGPD - Export des donn\xE9es personnelles",
4915
+ providedByYou: {
4916
+ legalBasis: "Art. 20 RGPD - droit \xE0 la portabilit\xE9",
4917
+ ...providedByYou
4918
+ },
4919
+ derivedData: {
4920
+ legalBasis: "Art. 15 RGPD - droit d'acc\xE8s (donn\xE9es d\xE9riv\xE9es, hors portabilit\xE9)",
4921
+ clientSummaries: summariesResult.docs.map((s) => ({
4922
+ summary: s.summary,
4923
+ recurringTopics: s.recurringTopics,
4924
+ patterns: s.patterns,
4925
+ keyFacts: s.keyFacts,
4926
+ ticketCount: s.ticketCount,
4927
+ messageCount: s.messageCount,
4928
+ averageSatisfaction: s.averageSatisfaction,
4929
+ generatedAt: s.generatedAt,
4930
+ aiModel: s.aiModel
4931
+ }))
4932
+ }
4933
+ };
4817
4934
  const json = JSON.stringify(exportData, null, 2);
4818
4935
  return new Response(json, {
4819
4936
  status: 200,
@@ -5727,15 +5844,22 @@ function createOAuthGoogleEndpoint(slugs, options) {
5727
5844
  }
5728
5845
  };
5729
5846
  }
5730
-
5731
- // src/endpoints/delete-account.ts
5847
+ function collectAttachmentIds(docs, into) {
5848
+ for (const doc of docs) {
5849
+ for (const attachment of doc.attachments ?? []) {
5850
+ const file = attachment?.file;
5851
+ const id = file != null && typeof file === "object" ? file.id : file;
5852
+ if (id != null) into.add(id);
5853
+ }
5854
+ }
5855
+ }
5732
5856
  function createDeleteAccountEndpoint(slugs) {
5733
5857
  return {
5734
5858
  path: "/support/delete-account",
5735
5859
  method: "post",
5736
5860
  handler: async (req) => {
5737
5861
  try {
5738
- const payload = req.payload;
5862
+ const payload$1 = req.payload;
5739
5863
  requireClient(req, slugs);
5740
5864
  const body = await req.json();
5741
5865
  const { confirmPassword } = body;
@@ -5746,7 +5870,7 @@ function createDeleteAccountEndpoint(slugs) {
5746
5870
  );
5747
5871
  }
5748
5872
  try {
5749
- await payload.login({
5873
+ await payload$1.login({
5750
5874
  collection: slugs.supportClients,
5751
5875
  data: { email: req.user.email, password: confirmPassword }
5752
5876
  });
@@ -5757,44 +5881,135 @@ function createDeleteAccountEndpoint(slugs) {
5757
5881
  );
5758
5882
  }
5759
5883
  const clientId = req.user.id;
5760
- const tickets = await dbFind(payload, slugs.tickets, {
5761
- where: { client: { equals: clientId } },
5762
- limit: 1e4,
5763
- depth: 0,
5764
- overrideAccess: true,
5765
- select: { id: true }
5766
- });
5767
- const ticketIds = tickets.docs.map((t) => t.id);
5768
- if (ticketIds.length > 0) {
5769
- await dbDelete(payload, slugs.ticketMessages, {
5770
- where: { ticket: { in: ticketIds } },
5771
- overrideAccess: true
5772
- });
5773
- await dbDelete(payload, slugs.ticketActivityLog, {
5774
- where: { ticket: { in: ticketIds } },
5775
- overrideAccess: true
5776
- });
5777
- await dbDelete(payload, slugs.timeEntries, {
5778
- where: { ticket: { in: ticketIds } },
5779
- overrideAccess: true
5780
- });
5781
- await dbDelete(payload, slugs.tickets, {
5884
+ const clientEmail = req.user.email;
5885
+ const isRegistered = (slug) => Boolean(payload$1.collections?.[slug]);
5886
+ const startedTransaction = await payload.initTransaction(req);
5887
+ try {
5888
+ const tickets = await dbFind(payload$1, slugs.tickets, {
5782
5889
  where: { client: { equals: clientId } },
5783
- overrideAccess: true
5890
+ limit: 1e4,
5891
+ depth: 0,
5892
+ overrideAccess: true,
5893
+ select: { id: true },
5894
+ req
5895
+ });
5896
+ const ticketIds = tickets.docs.map((t) => t.id);
5897
+ const attachmentIds = /* @__PURE__ */ new Set();
5898
+ if (ticketIds.length > 0) {
5899
+ const messages = await dbFind(
5900
+ payload$1,
5901
+ slugs.ticketMessages,
5902
+ {
5903
+ where: { ticket: { in: ticketIds } },
5904
+ limit: 1e4,
5905
+ depth: 0,
5906
+ overrideAccess: true,
5907
+ req
5908
+ }
5909
+ );
5910
+ collectAttachmentIds(messages.docs, attachmentIds);
5911
+ }
5912
+ if (isRegistered(slugs.pendingEmails)) {
5913
+ const pendingEmails = await dbFind(
5914
+ payload$1,
5915
+ slugs.pendingEmails,
5916
+ {
5917
+ where: { client: { equals: clientId } },
5918
+ limit: 1e4,
5919
+ depth: 0,
5920
+ overrideAccess: true,
5921
+ req
5922
+ }
5923
+ );
5924
+ collectAttachmentIds(pendingEmails.docs, attachmentIds);
5925
+ }
5926
+ if (ticketIds.length > 0) {
5927
+ for (const slug of [FIXED_SLUGS.ticketFeedback, FIXED_SLUGS.ticketCollaborators]) {
5928
+ if (!isRegistered(slug)) continue;
5929
+ await dbDelete(payload$1, slug, {
5930
+ where: {
5931
+ or: [
5932
+ { ticket: { in: ticketIds } },
5933
+ { client: { equals: clientId } }
5934
+ ]
5935
+ },
5936
+ overrideAccess: true,
5937
+ req
5938
+ });
5939
+ }
5940
+ }
5941
+ if (ticketIds.length > 0) {
5942
+ await dbDelete(payload$1, slugs.tickets, {
5943
+ where: { client: { equals: clientId } },
5944
+ overrideAccess: true,
5945
+ req
5946
+ });
5947
+ }
5948
+ for (const slug of [
5949
+ slugs.satisfactionSurveys,
5950
+ slugs.chatMessages,
5951
+ slugs.notificationQueue,
5952
+ slugs.pendingEmails,
5953
+ FIXED_SLUGS.clientSummaries,
5954
+ FIXED_SLUGS.ticketFeedback
5955
+ ]) {
5956
+ if (!isRegistered(slug)) continue;
5957
+ await dbDelete(payload$1, slug, {
5958
+ where: { client: { equals: clientId } },
5959
+ overrideAccess: true,
5960
+ req
5961
+ });
5962
+ }
5963
+ if (isRegistered(FIXED_SLUGS.ticketCollaborators)) {
5964
+ await dbDelete(payload$1, FIXED_SLUGS.ticketCollaborators, {
5965
+ where: {
5966
+ or: [
5967
+ { client: { equals: clientId } },
5968
+ ...clientEmail ? [{ email: { equals: clientEmail } }] : []
5969
+ ]
5970
+ },
5971
+ overrideAccess: true,
5972
+ req
5973
+ });
5974
+ }
5975
+ if (clientEmail) {
5976
+ if (isRegistered(slugs.authLogs)) {
5977
+ await dbDelete(payload$1, slugs.authLogs, {
5978
+ where: { email: { equals: clientEmail } },
5979
+ overrideAccess: true,
5980
+ req
5981
+ });
5982
+ }
5983
+ if (isRegistered(slugs.emailLogs)) {
5984
+ await dbDelete(payload$1, slugs.emailLogs, {
5985
+ where: {
5986
+ or: [
5987
+ { senderEmail: { equals: clientEmail } },
5988
+ { recipientEmail: { equals: clientEmail } }
5989
+ ]
5990
+ },
5991
+ overrideAccess: true,
5992
+ req
5993
+ });
5994
+ }
5995
+ }
5996
+ for (const id of isRegistered(slugs.media) ? attachmentIds : []) {
5997
+ await dbDelete(payload$1, slugs.media, {
5998
+ id,
5999
+ overrideAccess: true,
6000
+ req
6001
+ });
6002
+ }
6003
+ await dbDelete(payload$1, slugs.supportClients, {
6004
+ id: clientId,
6005
+ overrideAccess: true,
6006
+ req
5784
6007
  });
6008
+ if (startedTransaction) await payload.commitTransaction(req);
6009
+ } catch (err) {
6010
+ if (startedTransaction) await payload.killTransaction(req);
6011
+ throw err;
5785
6012
  }
5786
- await dbDelete(payload, slugs.satisfactionSurveys, {
5787
- where: { client: { equals: clientId } },
5788
- overrideAccess: true
5789
- });
5790
- await dbDelete(payload, slugs.chatMessages, {
5791
- where: { client: { equals: clientId } },
5792
- overrideAccess: true
5793
- });
5794
- await dbDelete(payload, slugs.supportClients, {
5795
- id: clientId,
5796
- overrideAccess: true
5797
- });
5798
6013
  const headers = new Headers({ "Content-Type": "application/json" });
5799
6014
  const secure = process.env.NODE_ENV === "production";
5800
6015
  headers.append(
@@ -8437,9 +8652,11 @@ function createCascadeDelete(slugs) {
8437
8652
  return async ({ id, req }) => {
8438
8653
  const collections = [slugs.ticketMessages, slugs.ticketActivityLog, slugs.timeEntries, slugs.satisfactionSurveys];
8439
8654
  for (const slug of collections) {
8655
+ if (!req.payload.collections?.[slug]) continue;
8440
8656
  await dbDelete(req.payload, slug, {
8441
8657
  where: { ticket: { equals: id } },
8442
- overrideAccess: true
8658
+ overrideAccess: true,
8659
+ req
8443
8660
  });
8444
8661
  }
8445
8662
  };
@@ -9927,7 +10144,7 @@ function createSupportClientsCollection(slugs, options) {
9927
10144
  }
9928
10145
 
9929
10146
  // src/collections/TimeEntries.ts
9930
- async function recalculateTicketTime(payload, slugs, ticketId) {
10147
+ async function recalculateTicketTime(payload, slugs, ticketId, req) {
9931
10148
  let totalMinutes = 0;
9932
10149
  let page = 1;
9933
10150
  let hasMore = true;
@@ -9939,7 +10156,8 @@ async function recalculateTicketTime(payload, slugs, ticketId) {
9939
10156
  page,
9940
10157
  depth: 0,
9941
10158
  overrideAccess: true,
9942
- select: { duration: true }
10159
+ select: { duration: true },
10160
+ req
9943
10161
  });
9944
10162
  for (const entry of entries.docs) {
9945
10163
  totalMinutes += entry.duration || 0;
@@ -9951,14 +10169,15 @@ async function recalculateTicketTime(payload, slugs, ticketId) {
9951
10169
  collection: slugs.tickets,
9952
10170
  id: ticketId,
9953
10171
  data: { totalTimeMinutes: totalMinutes },
9954
- overrideAccess: true
10172
+ overrideAccess: true,
10173
+ req
9955
10174
  });
9956
10175
  }
9957
10176
  function createRecalculateTicketTime(slugs) {
9958
10177
  return async ({ doc, req }) => {
9959
10178
  if (!doc.ticket) return;
9960
10179
  const ticketId = typeof doc.ticket === "object" ? doc.ticket.id : doc.ticket;
9961
- await recalculateTicketTime(req.payload, slugs, ticketId);
10180
+ await recalculateTicketTime(req.payload, slugs, ticketId, req);
9962
10181
  };
9963
10182
  }
9964
10183
  function createRecalculateTicketTimeOnDelete(slugs) {
@@ -9966,7 +10185,7 @@ function createRecalculateTicketTimeOnDelete(slugs) {
9966
10185
  if (!doc?.ticket) return;
9967
10186
  const ticketId = typeof doc.ticket === "object" ? doc.ticket.id : doc.ticket;
9968
10187
  try {
9969
- await recalculateTicketTime(req.payload, slugs, ticketId);
10188
+ await recalculateTicketTime(req.payload, slugs, ticketId, req);
9970
10189
  } catch (err) {
9971
10190
  console.error("[support] Failed to recalculate ticket time after delete:", err);
9972
10191
  }
@@ -11837,8 +12056,30 @@ function supportPlugin(config) {
11837
12056
  rateLimitStore,
11838
12057
  capabilities: config?.capabilities
11839
12058
  });
12059
+ const retention = config?.retention === false ? void 0 : config?.retention;
12060
+ const retentionEnabled = Boolean(retention);
12061
+ const purgeTask = {
12062
+ slug: PURGE_LOGS_TASK_SLUG,
12063
+ label: "Support \u2014 purge des journaux",
12064
+ schedule: [
12065
+ {
12066
+ cron: retention?.cron ?? DEFAULT_RETENTION.cron,
12067
+ queue: retention?.queue ?? DEFAULT_RETENTION.queue
12068
+ }
12069
+ ],
12070
+ handler: async ({ req }) => {
12071
+ const result = await runScheduledPurge(req.payload, slugs, retention, req);
12072
+ return { output: result };
12073
+ }
12074
+ };
12075
+ const existingJobs = incomingConfig.jobs;
12076
+ const jobs = retentionEnabled ? {
12077
+ ...existingJobs,
12078
+ tasks: [...existingJobs?.tasks ?? [], purgeTask]
12079
+ } : existingJobs;
11840
12080
  return {
11841
12081
  ...incomingConfig,
12082
+ jobs,
11842
12083
  // Publish the resolved staff collection so the server-side readers share
11843
12084
  // ONE source of truth with the writers. `requireAdmin` compares against
11844
12085
  // `slugs.users`; the `payload-preferences` reads used to scope themselves
@@ -11865,11 +12106,13 @@ function supportPlugin(config) {
11865
12106
 
11866
12107
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
11867
12108
  exports.DEFAULT_INBOUND_EMAIL_LIMITS = DEFAULT_INBOUND_EMAIL_LIMITS;
12109
+ exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
11868
12110
  exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
11869
12111
  exports.DEFAULT_SLUGS = DEFAULT_SLUGS;
11870
12112
  exports.DEFAULT_TICKETING_FEATURES = DEFAULT_TICKETING_FEATURES;
11871
12113
  exports.DEFAULT_USER_PREFS = DEFAULT_USER_PREFS;
11872
12114
  exports.MemoryRateLimitStore = MemoryRateLimitStore;
12115
+ exports.PURGE_LOGS_TASK_SLUG = PURGE_LOGS_TASK_SLUG;
11873
12116
  exports.PayloadRateLimitStore = PayloadRateLimitStore;
11874
12117
  exports.RateLimiter = RateLimiter;
11875
12118
  exports.calculateBusinessHoursDeadline = calculateBusinessHoursDeadline;
@@ -11902,10 +12145,13 @@ exports.dispatchWebhook = dispatchWebhook;
11902
12145
  exports.generateTicketSynthesis = generateTicketSynthesis;
11903
12146
  exports.normalizeFeatures = normalizeFeatures;
11904
12147
  exports.projectAutoClose = projectAutoClose;
12148
+ exports.purgeOlderThan = purgeOlderThan;
12149
+ exports.purgeableCollections = purgeableCollections;
11905
12150
  exports.readSupportSettings = readSupportSettings;
11906
12151
  exports.readSupportSettingsState = readSupportSettingsState;
11907
12152
  exports.readUserPrefs = readUserPrefs;
11908
12153
  exports.resolveSlugs = resolveSlugs;
12154
+ exports.runScheduledPurge = runScheduledPurge;
11909
12155
  exports.stripProjectedFeatures = stripProjectedFeatures;
11910
12156
  exports.supportPlugin = supportPlugin;
11911
12157
  exports.validateInboundEmailPayload = validateInboundEmailPayload;
package/dist/index.d.cts 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 };