@trieb.work/payload-plugin-backup-mongodb 0.1.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 (165) hide show
  1. package/README.md +319 -0
  2. package/dist/backup-dashboard.css +1132 -0
  3. package/dist/client.d.ts +3 -0
  4. package/dist/client.js +3 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/collections/BackupSettings.d.ts +6 -0
  7. package/dist/collections/BackupSettings.js +91 -0
  8. package/dist/collections/BackupSettings.js.map +1 -0
  9. package/dist/collections/BackupTasks.d.ts +2 -0
  10. package/dist/collections/BackupTasks.js +56 -0
  11. package/dist/collections/BackupTasks.js.map +1 -0
  12. package/dist/components/BackupDashboard/BackupItemActions.client.d.ts +7 -0
  13. package/dist/components/BackupDashboard/BackupItemActions.client.js +90 -0
  14. package/dist/components/BackupDashboard/BackupItemActions.client.js.map +1 -0
  15. package/dist/components/BackupDashboard/BackupListCollapsible.client.d.ts +16 -0
  16. package/dist/components/BackupDashboard/BackupListCollapsible.client.js +597 -0
  17. package/dist/components/BackupDashboard/BackupListCollapsible.client.js.map +1 -0
  18. package/dist/components/BackupDashboard/BackupSettingsModal.client.d.ts +2 -0
  19. package/dist/components/BackupDashboard/BackupSettingsModal.client.js +939 -0
  20. package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -0
  21. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.d.ts +18 -0
  22. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js +267 -0
  23. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js.map +1 -0
  24. package/dist/components/BackupDashboard/ManualBackupDialog.client.d.ts +1 -0
  25. package/dist/components/BackupDashboard/ManualBackupDialog.client.js +276 -0
  26. package/dist/components/BackupDashboard/ManualBackupDialog.client.js.map +1 -0
  27. package/dist/components/BackupDashboard/RestoreBackupDialog.client.d.ts +6 -0
  28. package/dist/components/BackupDashboard/RestoreBackupDialog.client.js +551 -0
  29. package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -0
  30. package/dist/components/BackupDashboard/TaskActionButton.client.d.ts +29 -0
  31. package/dist/components/BackupDashboard/TaskActionButton.client.js +240 -0
  32. package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -0
  33. package/dist/components/BackupDashboard/backupDashboardInlineCss.d.ts +1 -0
  34. package/dist/components/BackupDashboard/backupDashboardInlineCss.js +3 -0
  35. package/dist/components/BackupDashboard/backupDashboardInlineCss.js.map +1 -0
  36. package/dist/components/BackupDashboard/index.client.d.ts +8 -0
  37. package/dist/components/BackupDashboard/index.client.js +9 -0
  38. package/dist/components/BackupDashboard/index.client.js.map +1 -0
  39. package/dist/components/BackupDashboard/index.d.ts +8 -0
  40. package/dist/components/BackupDashboard/index.js +196 -0
  41. package/dist/components/BackupDashboard/index.js.map +1 -0
  42. package/dist/core/archive.d.ts +8 -0
  43. package/dist/core/archive.js +47 -0
  44. package/dist/core/archive.js.map +1 -0
  45. package/dist/core/backup.d.ts +41 -0
  46. package/dist/core/backup.js +188 -0
  47. package/dist/core/backup.js.map +1 -0
  48. package/dist/core/backupBlobIO.d.ts +54 -0
  49. package/dist/core/backupBlobIO.js +179 -0
  50. package/dist/core/backupBlobIO.js.map +1 -0
  51. package/dist/core/backupBlobTransfer.d.ts +29 -0
  52. package/dist/core/backupBlobTransfer.js +85 -0
  53. package/dist/core/backupBlobTransfer.js.map +1 -0
  54. package/dist/core/backupSettings.d.ts +35 -0
  55. package/dist/core/backupSettings.js +95 -0
  56. package/dist/core/backupSettings.js.map +1 -0
  57. package/dist/core/backupSourcePreview.d.ts +10 -0
  58. package/dist/core/backupSourcePreview.js +33 -0
  59. package/dist/core/backupSourcePreview.js.map +1 -0
  60. package/dist/core/blobTokenValidate.d.ts +14 -0
  61. package/dist/core/blobTokenValidate.js +75 -0
  62. package/dist/core/blobTokenValidate.js.map +1 -0
  63. package/dist/core/db.d.ts +24 -0
  64. package/dist/core/db.js +12 -0
  65. package/dist/core/db.js.map +1 -0
  66. package/dist/core/restore.d.ts +27 -0
  67. package/dist/core/restore.js +199 -0
  68. package/dist/core/restore.js.map +1 -0
  69. package/dist/core/restorePreview.d.ts +64 -0
  70. package/dist/core/restorePreview.js +266 -0
  71. package/dist/core/restorePreview.js.map +1 -0
  72. package/dist/core/taskProgress.d.ts +26 -0
  73. package/dist/core/taskProgress.js +73 -0
  74. package/dist/core/taskProgress.js.map +1 -0
  75. package/dist/core/vercelBackupCron.d.ts +8 -0
  76. package/dist/core/vercelBackupCron.js +38 -0
  77. package/dist/core/vercelBackupCron.js.map +1 -0
  78. package/dist/endpoints/createBackupEndpoints.d.ts +1 -0
  79. package/dist/endpoints/createBackupEndpoints.js +3 -0
  80. package/dist/endpoints/createBackupEndpoints.js.map +1 -0
  81. package/dist/endpoints/index.d.ts +3 -0
  82. package/dist/endpoints/index.js +34 -0
  83. package/dist/endpoints/index.js.map +1 -0
  84. package/dist/endpoints/paths/admin-backup-download.d.ts +8 -0
  85. package/dist/endpoints/paths/admin-backup-download.js +72 -0
  86. package/dist/endpoints/paths/admin-backup-download.js.map +1 -0
  87. package/dist/endpoints/paths/admin-delete.d.ts +3 -0
  88. package/dist/endpoints/paths/admin-delete.js +55 -0
  89. package/dist/endpoints/paths/admin-delete.js.map +1 -0
  90. package/dist/endpoints/paths/admin-manual.d.ts +3 -0
  91. package/dist/endpoints/paths/admin-manual.js +61 -0
  92. package/dist/endpoints/paths/admin-manual.js.map +1 -0
  93. package/dist/endpoints/paths/admin-preview.d.ts +6 -0
  94. package/dist/endpoints/paths/admin-preview.js +102 -0
  95. package/dist/endpoints/paths/admin-preview.js.map +1 -0
  96. package/dist/endpoints/paths/admin-restore.d.ts +3 -0
  97. package/dist/endpoints/paths/admin-restore.js +74 -0
  98. package/dist/endpoints/paths/admin-restore.js.map +1 -0
  99. package/dist/endpoints/paths/admin-seed.d.ts +3 -0
  100. package/dist/endpoints/paths/admin-seed.js +47 -0
  101. package/dist/endpoints/paths/admin-seed.js.map +1 -0
  102. package/dist/endpoints/paths/admin-settings.d.ts +3 -0
  103. package/dist/endpoints/paths/admin-settings.js +230 -0
  104. package/dist/endpoints/paths/admin-settings.js.map +1 -0
  105. package/dist/endpoints/paths/admin-task.d.ts +3 -0
  106. package/dist/endpoints/paths/admin-task.js +34 -0
  107. package/dist/endpoints/paths/admin-task.js.map +1 -0
  108. package/dist/endpoints/paths/admin-validate-blob-token.d.ts +8 -0
  109. package/dist/endpoints/paths/admin-validate-blob-token.js +26 -0
  110. package/dist/endpoints/paths/admin-validate-blob-token.js.map +1 -0
  111. package/dist/endpoints/paths/cron-list.d.ts +2 -0
  112. package/dist/endpoints/paths/cron-list.js +33 -0
  113. package/dist/endpoints/paths/cron-list.js.map +1 -0
  114. package/dist/endpoints/paths/cron-restore.d.ts +2 -0
  115. package/dist/endpoints/paths/cron-restore.js +61 -0
  116. package/dist/endpoints/paths/cron-restore.js.map +1 -0
  117. package/dist/endpoints/paths/cron-run.d.ts +3 -0
  118. package/dist/endpoints/paths/cron-run.js +45 -0
  119. package/dist/endpoints/paths/cron-run.js.map +1 -0
  120. package/dist/endpoints/shared.d.ts +13 -0
  121. package/dist/endpoints/shared.js +58 -0
  122. package/dist/endpoints/shared.js.map +1 -0
  123. package/dist/exports/client.d.ts +1 -0
  124. package/dist/exports/client.js +3 -0
  125. package/dist/exports/client.js.map +1 -0
  126. package/dist/exports/rsc.d.ts +1 -0
  127. package/dist/exports/rsc.js +3 -0
  128. package/dist/exports/rsc.js.map +1 -0
  129. package/dist/index.d.ts +18 -0
  130. package/dist/index.js +14 -0
  131. package/dist/index.js.map +1 -0
  132. package/dist/plugin.d.ts +3 -0
  133. package/dist/plugin.js +82 -0
  134. package/dist/plugin.js.map +1 -0
  135. package/dist/publicApiPaths.d.ts +19 -0
  136. package/dist/publicApiPaths.js +20 -0
  137. package/dist/publicApiPaths.js.map +1 -0
  138. package/dist/types.d.ts +21 -0
  139. package/dist/types.js +3 -0
  140. package/dist/types.js.map +1 -0
  141. package/dist/utils/backupSelection.d.ts +6 -0
  142. package/dist/utils/backupSelection.js +16 -0
  143. package/dist/utils/backupSelection.js.map +1 -0
  144. package/dist/utils/blobName.d.ts +32 -0
  145. package/dist/utils/blobName.js +99 -0
  146. package/dist/utils/blobName.js.map +1 -0
  147. package/dist/utils/dashboardRoleAccess.d.ts +23 -0
  148. package/dist/utils/dashboardRoleAccess.js +64 -0
  149. package/dist/utils/dashboardRoleAccess.js.map +1 -0
  150. package/dist/utils/dialogBackdrop.d.ts +9 -0
  151. package/dist/utils/dialogBackdrop.js +17 -0
  152. package/dist/utils/dialogBackdrop.js.map +1 -0
  153. package/dist/utils/formatBytes.d.ts +2 -0
  154. package/dist/utils/formatBytes.js +20 -0
  155. package/dist/utils/formatBytes.js.map +1 -0
  156. package/dist/utils/hostname.d.ts +2 -0
  157. package/dist/utils/hostname.js +17 -0
  158. package/dist/utils/hostname.js.map +1 -0
  159. package/dist/utils/index.d.ts +5 -0
  160. package/dist/utils/index.js +7 -0
  161. package/dist/utils/index.js.map +1 -0
  162. package/dist/utils/maskBlobToken.d.ts +11 -0
  163. package/dist/utils/maskBlobToken.js +42 -0
  164. package/dist/utils/maskBlobToken.js.map +1 -0
  165. package/package.json +140 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/endpoints/paths/admin-validate-blob-token.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { validateBackupBlobToken } from '../../core/blobTokenValidate'\nimport { readRequestJson, requireBackupAdmin } from '../shared'\n\n/**\n * Admin-only validation endpoint for a (candidate) Vercel Blob read/write token. Probes the store\n * by uploading and deleting a tiny marker file and reports back whether the token is usable and\n * which access level (public/private) the store accepts.\n */\nexport function createAdminValidateBlobTokenEndpoint(options: BackupPluginOptions): Endpoint {\n return {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const body = (await readRequestJson(req)) as { token?: unknown }\n const token = typeof body?.token === 'string' ? body.token : ''\n\n const result = await validateBackupBlobToken(token)\n return Response.json(result, { status: result.ok ? 200 : 422 })\n },\n method: 'post',\n path: '/backup-mongodb/admin/validate-blob-token',\n }\n}\n"],"names":["validateBackupBlobToken","readRequestJson","requireBackupAdmin","createAdminValidateBlobTokenEndpoint","options","handler","req","auth","Response","body","token","result","json","status","ok","method","path"],"mappings":"AAIA,SAASA,uBAAuB,QAAQ,+BAA8B;AACtE,SAASC,eAAe,EAAEC,kBAAkB,QAAQ,YAAW;AAE/D;;;;CAIC,GACD,OAAO,SAASC,qCAAqCC,OAA4B;IAC/E,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,OAAO,MAAML,mBAAmBI,KAAKF;YAC3C,IAAIG,gBAAgBC,UAAU;gBAC5B,OAAOD;YACT;YAEA,MAAME,OAAQ,MAAMR,gBAAgBK;YACpC,MAAMI,QAAQ,OAAOD,MAAMC,UAAU,WAAWD,KAAKC,KAAK,GAAG;YAE7D,MAAMC,SAAS,MAAMX,wBAAwBU;YAC7C,OAAOF,SAASI,IAAI,CAACD,QAAQ;gBAAEE,QAAQF,OAAOG,EAAE,GAAG,MAAM;YAAI;QAC/D;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -0,0 +1,2 @@
1
+ import type { Endpoint } from 'payload';
2
+ export declare function createCronListEndpoint(): Endpoint;
@@ -0,0 +1,33 @@
1
+ import { listBackups, resolveBackupListToken } from '../../core/backup';
2
+ import { requireCronBearer } from '../shared';
3
+ export function createCronListEndpoint() {
4
+ return {
5
+ handler: async (req)=>{
6
+ const cronErr = requireCronBearer(req);
7
+ if (cronErr) {
8
+ return cronErr;
9
+ }
10
+ const { payload } = req;
11
+ const backupBlobToken = await resolveBackupListToken(payload);
12
+ if (!backupBlobToken) {
13
+ return new Response('Service unavailable', {
14
+ status: 503
15
+ });
16
+ }
17
+ payload.logger.info('[backup-endpoint] Listing backups');
18
+ const blobs = await listBackups(payload, {
19
+ blobToken: backupBlobToken
20
+ });
21
+ payload.logger.info({
22
+ count: blobs.length
23
+ }, '[backup-endpoint] Backup list loaded');
24
+ return Response.json(blobs, {
25
+ status: 200
26
+ });
27
+ },
28
+ method: 'get',
29
+ path: '/backup-mongodb/cron/list'
30
+ };
31
+ }
32
+
33
+ //# sourceMappingURL=cron-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/endpoints/paths/cron-list.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { listBackups, resolveBackupListToken } from '../../core/backup'\nimport { requireCronBearer } from '../shared'\n\nexport function createCronListEndpoint(): Endpoint {\n return {\n handler: async (req) => {\n const cronErr = requireCronBearer(req)\n if (cronErr) {\n return cronErr\n }\n\n const { payload } = req\n const backupBlobToken = await resolveBackupListToken(payload)\n if (!backupBlobToken) {\n return new Response('Service unavailable', { status: 503 })\n }\n payload.logger.info('[backup-endpoint] Listing backups')\n const blobs = await listBackups(payload, { blobToken: backupBlobToken })\n payload.logger.info({ count: blobs.length }, '[backup-endpoint] Backup list loaded')\n return Response.json(blobs, { status: 200 })\n },\n method: 'get',\n path: '/backup-mongodb/cron/list',\n }\n}\n"],"names":["listBackups","resolveBackupListToken","requireCronBearer","createCronListEndpoint","handler","req","cronErr","payload","backupBlobToken","Response","status","logger","info","blobs","blobToken","count","length","json","method","path"],"mappings":"AAEA,SAASA,WAAW,EAAEC,sBAAsB,QAAQ,oBAAmB;AACvE,SAASC,iBAAiB,QAAQ,YAAW;AAE7C,OAAO,SAASC;IACd,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,UAAUJ,kBAAkBG;YAClC,IAAIC,SAAS;gBACX,OAAOA;YACT;YAEA,MAAM,EAAEC,OAAO,EAAE,GAAGF;YACpB,MAAMG,kBAAkB,MAAMP,uBAAuBM;YACrD,IAAI,CAACC,iBAAiB;gBACpB,OAAO,IAAIC,SAAS,uBAAuB;oBAAEC,QAAQ;gBAAI;YAC3D;YACAH,QAAQI,MAAM,CAACC,IAAI,CAAC;YACpB,MAAMC,QAAQ,MAAMb,YAAYO,SAAS;gBAAEO,WAAWN;YAAgB;YACtED,QAAQI,MAAM,CAACC,IAAI,CAAC;gBAAEG,OAAOF,MAAMG,MAAM;YAAC,GAAG;YAC7C,OAAOP,SAASQ,IAAI,CAACJ,OAAO;gBAAEH,QAAQ;YAAI;QAC5C;QACAQ,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -0,0 +1,2 @@
1
+ import type { Endpoint } from 'payload';
2
+ export declare function createCronRestoreEndpoint(): Endpoint;
@@ -0,0 +1,61 @@
1
+ import { getResolvedCronBackupSettings, resolveBackupArchiveRead, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
2
+ import { restoreBackup } from '../../core/restore';
3
+ import { readRequestJson, requireCronBearer } from '../shared';
4
+ export function createCronRestoreEndpoint() {
5
+ return {
6
+ handler: async (req)=>{
7
+ const cronErr = requireCronBearer(req);
8
+ if (cronErr) {
9
+ return cronErr;
10
+ }
11
+ const { payload } = req;
12
+ const settings = await getResolvedCronBackupSettings(payload);
13
+ const blobToken = resolveBackupBlobToken(settings);
14
+ const blobAccess = resolveBackupBlobAccess(settings);
15
+ if (!blobToken) {
16
+ return new Response('Service unavailable', {
17
+ status: 503
18
+ });
19
+ }
20
+ const { pathname, url } = await readRequestJson(req);
21
+ if (!url || typeof url !== 'string') {
22
+ return new Response('Missing url', {
23
+ status: 400
24
+ });
25
+ }
26
+ try {
27
+ new URL(url);
28
+ } catch {
29
+ return new Response('Invalid url', {
30
+ status: 400
31
+ });
32
+ }
33
+ const backupRead = resolveBackupArchiveRead(settings, pathname);
34
+ if (blobAccess === 'private' && !backupRead) {
35
+ return new Response('Missing pathname (required for dedicated backup blob store)', {
36
+ status: 400
37
+ });
38
+ }
39
+ payload.logger.info({
40
+ url
41
+ }, '[backup-endpoint] Restore request accepted');
42
+ await restoreBackup(payload, url, [], false, undefined, {
43
+ backupRead: backupRead ?? undefined,
44
+ blobAccess,
45
+ blobToken
46
+ });
47
+ payload.logger.info({
48
+ url
49
+ }, '[backup-endpoint] Restore request finished');
50
+ return Response.json({
51
+ message: 'Backup restore finished'
52
+ }, {
53
+ status: 202
54
+ });
55
+ },
56
+ method: 'post',
57
+ path: '/backup-mongodb/cron/restore'
58
+ };
59
+ }
60
+
61
+ //# sourceMappingURL=cron-restore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/endpoints/paths/cron-restore.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport {\n getResolvedCronBackupSettings,\n resolveBackupArchiveRead,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { restoreBackup } from '../../core/restore'\nimport { readRequestJson, requireCronBearer } from '../shared'\n\nexport function createCronRestoreEndpoint(): Endpoint {\n return {\n handler: async (req) => {\n const cronErr = requireCronBearer(req)\n if (cronErr) {\n return cronErr\n }\n const { payload } = req\n const settings = await getResolvedCronBackupSettings(payload)\n const blobToken = resolveBackupBlobToken(settings)\n const blobAccess = resolveBackupBlobAccess(settings)\n if (!blobToken) {\n return new Response('Service unavailable', { status: 503 })\n }\n\n const { pathname, url } = (await readRequestJson(req)) as { pathname?: string; url?: string }\n if (!url || typeof url !== 'string') {\n return new Response('Missing url', { status: 400 })\n }\n try {\n new URL(url)\n } catch {\n return new Response('Invalid url', { status: 400 })\n }\n\n const backupRead = resolveBackupArchiveRead(settings, pathname)\n if (blobAccess === 'private' && !backupRead) {\n return new Response('Missing pathname (required for dedicated backup blob store)', {\n status: 400,\n })\n }\n\n payload.logger.info({ url }, '[backup-endpoint] Restore request accepted')\n await restoreBackup(payload, url, [], false, undefined, {\n backupRead: backupRead ?? undefined,\n blobAccess,\n blobToken,\n })\n payload.logger.info({ url }, '[backup-endpoint] Restore request finished')\n return Response.json({ message: 'Backup restore finished' }, { status: 202 })\n },\n method: 'post',\n path: '/backup-mongodb/cron/restore',\n }\n}\n"],"names":["getResolvedCronBackupSettings","resolveBackupArchiveRead","resolveBackupBlobAccess","resolveBackupBlobToken","restoreBackup","readRequestJson","requireCronBearer","createCronRestoreEndpoint","handler","req","cronErr","payload","settings","blobToken","blobAccess","Response","status","pathname","url","URL","backupRead","logger","info","undefined","json","message","method","path"],"mappings":"AAEA,SACEA,6BAA6B,EAC7BC,wBAAwB,EACxBC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,eAAe,EAAEC,iBAAiB,QAAQ,YAAW;AAE9D,OAAO,SAASC;IACd,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,UAAUJ,kBAAkBG;YAClC,IAAIC,SAAS;gBACX,OAAOA;YACT;YACA,MAAM,EAAEC,OAAO,EAAE,GAAGF;YACpB,MAAMG,WAAW,MAAMZ,8BAA8BW;YACrD,MAAME,YAAYV,uBAAuBS;YACzC,MAAME,aAAaZ,wBAAwBU;YAC3C,IAAI,CAACC,WAAW;gBACd,OAAO,IAAIE,SAAS,uBAAuB;oBAAEC,QAAQ;gBAAI;YAC3D;YAEA,MAAM,EAAEC,QAAQ,EAAEC,GAAG,EAAE,GAAI,MAAMb,gBAAgBI;YACjD,IAAI,CAACS,OAAO,OAAOA,QAAQ,UAAU;gBACnC,OAAO,IAAIH,SAAS,eAAe;oBAAEC,QAAQ;gBAAI;YACnD;YACA,IAAI;gBACF,IAAIG,IAAID;YACV,EAAE,OAAM;gBACN,OAAO,IAAIH,SAAS,eAAe;oBAAEC,QAAQ;gBAAI;YACnD;YAEA,MAAMI,aAAanB,yBAAyBW,UAAUK;YACtD,IAAIH,eAAe,aAAa,CAACM,YAAY;gBAC3C,OAAO,IAAIL,SAAS,+DAA+D;oBACjFC,QAAQ;gBACV;YACF;YAEAL,QAAQU,MAAM,CAACC,IAAI,CAAC;gBAAEJ;YAAI,GAAG;YAC7B,MAAMd,cAAcO,SAASO,KAAK,EAAE,EAAE,OAAOK,WAAW;gBACtDH,YAAYA,cAAcG;gBAC1BT;gBACAD;YACF;YACAF,QAAQU,MAAM,CAACC,IAAI,CAAC;gBAAEJ;YAAI,GAAG;YAC7B,OAAOH,SAASS,IAAI,CAAC;gBAAEC,SAAS;YAA0B,GAAG;gBAAET,QAAQ;YAAI;QAC7E;QACAU,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -0,0 +1,3 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { BackupPluginOptions } from '../../types';
3
+ export declare function createCronRunEndpoint(options: BackupPluginOptions): Endpoint;
@@ -0,0 +1,45 @@
1
+ import { after } from 'next/server';
2
+ import { createBackup } from '../../core/backup';
3
+ import { getResolvedCronBackupSettings, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
4
+ import { requireCronBearer } from '../shared';
5
+ export function createCronRunEndpoint(options) {
6
+ return {
7
+ handler: async (req)=>{
8
+ const cronErr = requireCronBearer(req);
9
+ if (cronErr) {
10
+ return cronErr;
11
+ }
12
+ const { payload } = req;
13
+ const settings = await getResolvedCronBackupSettings(payload);
14
+ const blobToken = resolveBackupBlobToken(settings);
15
+ const blobAccess = resolveBackupBlobAccess(settings);
16
+ if (!blobToken) {
17
+ return new Response('Service unavailable', {
18
+ status: 503
19
+ });
20
+ }
21
+ payload.logger.info('[backup-endpoint] Cron backup request accepted');
22
+ after(createBackup(payload, {
23
+ backupsToKeep: options.backupsToKeep ?? settings.backupsToKeep,
24
+ blobAccess,
25
+ blobToken,
26
+ cron: true,
27
+ includeMedia: settings.includeMediaForCron,
28
+ skipCollections: settings.skipMongoCollections
29
+ }).catch((error)=>{
30
+ payload.logger.error({
31
+ err: error
32
+ }, '[backup-endpoint] Cron backup failed');
33
+ throw error;
34
+ }));
35
+ payload.logger.info('[backup-endpoint] Cron backup queued');
36
+ return new Response('Backup creation started', {
37
+ status: 202
38
+ });
39
+ },
40
+ method: 'get',
41
+ path: '/backup-mongodb/cron/run'
42
+ };
43
+ }
44
+
45
+ //# sourceMappingURL=cron-run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/endpoints/paths/cron-run.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { createBackup } from '../../core/backup'\nimport {\n getResolvedCronBackupSettings,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { requireCronBearer } from '../shared'\n\nexport function createCronRunEndpoint(options: BackupPluginOptions): Endpoint {\n return {\n handler: async (req) => {\n const cronErr = requireCronBearer(req)\n if (cronErr) {\n return cronErr\n }\n\n const { payload } = req\n const settings = await getResolvedCronBackupSettings(payload)\n const blobToken = resolveBackupBlobToken(settings)\n const blobAccess = resolveBackupBlobAccess(settings)\n if (!blobToken) {\n return new Response('Service unavailable', { status: 503 })\n }\n payload.logger.info('[backup-endpoint] Cron backup request accepted')\n after(\n createBackup(payload, {\n backupsToKeep: options.backupsToKeep ?? settings.backupsToKeep,\n blobAccess,\n blobToken,\n cron: true,\n includeMedia: settings.includeMediaForCron,\n skipCollections: settings.skipMongoCollections,\n }).catch((error) => {\n payload.logger.error({ err: error }, '[backup-endpoint] Cron backup failed')\n throw error\n }),\n )\n payload.logger.info('[backup-endpoint] Cron backup queued')\n return new Response('Backup creation started', { status: 202 })\n },\n method: 'get',\n path: '/backup-mongodb/cron/run',\n }\n}\n"],"names":["after","createBackup","getResolvedCronBackupSettings","resolveBackupBlobAccess","resolveBackupBlobToken","requireCronBearer","createCronRunEndpoint","options","handler","req","cronErr","payload","settings","blobToken","blobAccess","Response","status","logger","info","backupsToKeep","cron","includeMedia","includeMediaForCron","skipCollections","skipMongoCollections","catch","error","err","method","path"],"mappings":"AAEA,SAASA,KAAK,QAAQ,cAAa;AAInC,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SACEC,6BAA6B,EAC7BC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,iBAAiB,QAAQ,YAAW;AAE7C,OAAO,SAASC,sBAAsBC,OAA4B;IAChE,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,UAAUL,kBAAkBI;YAClC,IAAIC,SAAS;gBACX,OAAOA;YACT;YAEA,MAAM,EAAEC,OAAO,EAAE,GAAGF;YACpB,MAAMG,WAAW,MAAMV,8BAA8BS;YACrD,MAAME,YAAYT,uBAAuBQ;YACzC,MAAME,aAAaX,wBAAwBS;YAC3C,IAAI,CAACC,WAAW;gBACd,OAAO,IAAIE,SAAS,uBAAuB;oBAAEC,QAAQ;gBAAI;YAC3D;YACAL,QAAQM,MAAM,CAACC,IAAI,CAAC;YACpBlB,MACEC,aAAaU,SAAS;gBACpBQ,eAAeZ,QAAQY,aAAa,IAAIP,SAASO,aAAa;gBAC9DL;gBACAD;gBACAO,MAAM;gBACNC,cAAcT,SAASU,mBAAmB;gBAC1CC,iBAAiBX,SAASY,oBAAoB;YAChD,GAAGC,KAAK,CAAC,CAACC;gBACRf,QAAQM,MAAM,CAACS,KAAK,CAAC;oBAAEC,KAAKD;gBAAM,GAAG;gBACrC,MAAMA;YACR;YAEFf,QAAQM,MAAM,CAACC,IAAI,CAAC;YACpB,OAAO,IAAIH,SAAS,2BAA2B;gBAAEC,QAAQ;YAAI;QAC/D;QACAY,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -0,0 +1,13 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { BackupPluginOptions } from '../types';
3
+ export declare function readRequestJson(req: PayloadRequest): Promise<unknown>;
4
+ /**
5
+ * Canonical JSON error response for admin-facing endpoints. The dashboard clients
6
+ * always parse responses as JSON, so plain-text bodies would crash `await res.json()`
7
+ * with `Unexpected token ... is not valid JSON`.
8
+ */
9
+ export declare function jsonError(message: string, status: number): Response;
10
+ export declare function requireBlobEnv(): null | Response;
11
+ export declare function requireCronBearer(req: PayloadRequest): null | Response;
12
+ export declare function getAuthorizedBackupAdmin(req: PayloadRequest, options: BackupPluginOptions): Promise<null | Record<string, unknown>>;
13
+ export declare function requireBackupAdmin(req: PayloadRequest, options: BackupPluginOptions): Promise<Record<string, unknown> | Response>;
@@ -0,0 +1,58 @@
1
+ import { isUserAllowedByEnvRoles } from '../utils/dashboardRoleAccess';
2
+ export async function readRequestJson(req) {
3
+ return req.json();
4
+ }
5
+ /**
6
+ * Canonical JSON error response for admin-facing endpoints. The dashboard clients
7
+ * always parse responses as JSON, so plain-text bodies would crash `await res.json()`
8
+ * with `Unexpected token ... is not valid JSON`.
9
+ */ export function jsonError(message, status) {
10
+ return Response.json({
11
+ error: message
12
+ }, {
13
+ status
14
+ });
15
+ }
16
+ export function requireBlobEnv() {
17
+ if (!process.env.BLOB_READ_WRITE_TOKEN) {
18
+ return jsonError('Service unavailable', 503);
19
+ }
20
+ return null;
21
+ }
22
+ export function requireCronBearer(req) {
23
+ // Security gate for cron/external backup routes: only a matching CRON_SECRET bearer may pass.
24
+ const authHeader = req.headers.get('authorization');
25
+ if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
26
+ return new Response('Unauthorized', {
27
+ status: 401
28
+ });
29
+ }
30
+ return null;
31
+ }
32
+ export async function getAuthorizedBackupAdmin(req, options) {
33
+ const fromRequest = req.user;
34
+ const authUser = fromRequest ? fromRequest : (await req.payload.auth({
35
+ headers: req.headers
36
+ }))?.user;
37
+ const user = authUser;
38
+ if (!user) {
39
+ return null;
40
+ }
41
+ if (options.access) {
42
+ return options.access(user) ? user : null;
43
+ }
44
+ // Default access check stays in sync with the dashboard visibility rule:
45
+ // driven by `PAYLOAD_BACKUP_ALLOWED_ROLES` with backwards-compatible defaults
46
+ // (admin role required when roles exist, or allow when the project has no roles field).
47
+ return isUserAllowedByEnvRoles(user) ? user : null;
48
+ }
49
+ export async function requireBackupAdmin(req, options) {
50
+ // Security gate for admin routes: request must resolve to an authorized backup admin user.
51
+ const user = await getAuthorizedBackupAdmin(req, options);
52
+ if (!user) {
53
+ return jsonError('Unauthorized', 401);
54
+ }
55
+ return user;
56
+ }
57
+
58
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/shared.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport type { BackupPluginOptions } from '../types'\n\nimport { isUserAllowedByEnvRoles } from '../utils/dashboardRoleAccess'\n\nexport async function readRequestJson(req: PayloadRequest): Promise<unknown> {\n return (req as unknown as Request).json() as Promise<unknown>\n}\n\n/**\n * Canonical JSON error response for admin-facing endpoints. The dashboard clients\n * always parse responses as JSON, so plain-text bodies would crash `await res.json()`\n * with `Unexpected token ... is not valid JSON`.\n */\nexport function jsonError(message: string, status: number): Response {\n return Response.json({ error: message }, { status })\n}\n\nexport function requireBlobEnv(): null | Response {\n if (!process.env.BLOB_READ_WRITE_TOKEN) {\n return jsonError('Service unavailable', 503)\n }\n return null\n}\n\nexport function requireCronBearer(req: PayloadRequest): null | Response {\n // Security gate for cron/external backup routes: only a matching CRON_SECRET bearer may pass.\n const authHeader = req.headers.get('authorization')\n if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {\n return new Response('Unauthorized', { status: 401 })\n }\n return null\n}\n\nexport async function getAuthorizedBackupAdmin(\n req: PayloadRequest,\n options: BackupPluginOptions,\n): Promise<null | Record<string, unknown>> {\n const fromRequest = (req as { user?: null | Record<string, unknown> } & PayloadRequest).user\n const authUser = fromRequest\n ? fromRequest\n : ((await req.payload.auth({ headers: req.headers }))?.user as\n | null\n | Record<string, unknown>\n | undefined)\n const user = authUser\n if (!user) {\n return null\n }\n if (options.access) {\n return options.access(user) ? user : null\n }\n // Default access check stays in sync with the dashboard visibility rule:\n // driven by `PAYLOAD_BACKUP_ALLOWED_ROLES` with backwards-compatible defaults\n // (admin role required when roles exist, or allow when the project has no roles field).\n return isUserAllowedByEnvRoles(user) ? user : null\n}\n\nexport async function requireBackupAdmin(\n req: PayloadRequest,\n options: BackupPluginOptions,\n): Promise<Record<string, unknown> | Response> {\n // Security gate for admin routes: request must resolve to an authorized backup admin user.\n const user = await getAuthorizedBackupAdmin(req, options)\n if (!user) {\n return jsonError('Unauthorized', 401)\n }\n return user\n}\n"],"names":["isUserAllowedByEnvRoles","readRequestJson","req","json","jsonError","message","status","Response","error","requireBlobEnv","process","env","BLOB_READ_WRITE_TOKEN","requireCronBearer","authHeader","headers","get","CRON_SECRET","getAuthorizedBackupAdmin","options","fromRequest","user","authUser","payload","auth","access","requireBackupAdmin"],"mappings":"AAIA,SAASA,uBAAuB,QAAQ,+BAA8B;AAEtE,OAAO,eAAeC,gBAAgBC,GAAmB;IACvD,OAAO,AAACA,IAA2BC,IAAI;AACzC;AAEA;;;;CAIC,GACD,OAAO,SAASC,UAAUC,OAAe,EAAEC,MAAc;IACvD,OAAOC,SAASJ,IAAI,CAAC;QAAEK,OAAOH;IAAQ,GAAG;QAAEC;IAAO;AACpD;AAEA,OAAO,SAASG;IACd,IAAI,CAACC,QAAQC,GAAG,CAACC,qBAAqB,EAAE;QACtC,OAAOR,UAAU,uBAAuB;IAC1C;IACA,OAAO;AACT;AAEA,OAAO,SAASS,kBAAkBX,GAAmB;IACnD,8FAA8F;IAC9F,MAAMY,aAAaZ,IAAIa,OAAO,CAACC,GAAG,CAAC;IACnC,IAAIF,eAAe,CAAC,OAAO,EAAEJ,QAAQC,GAAG,CAACM,WAAW,EAAE,EAAE;QACtD,OAAO,IAAIV,SAAS,gBAAgB;YAAED,QAAQ;QAAI;IACpD;IACA,OAAO;AACT;AAEA,OAAO,eAAeY,yBACpBhB,GAAmB,EACnBiB,OAA4B;IAE5B,MAAMC,cAAc,AAAClB,IAAmEmB,IAAI;IAC5F,MAAMC,WAAWF,cACbA,cACE,CAAA,MAAMlB,IAAIqB,OAAO,CAACC,IAAI,CAAC;QAAET,SAASb,IAAIa,OAAO;IAAC,EAAC,GAAIM;IAIzD,MAAMA,OAAOC;IACb,IAAI,CAACD,MAAM;QACT,OAAO;IACT;IACA,IAAIF,QAAQM,MAAM,EAAE;QAClB,OAAON,QAAQM,MAAM,CAACJ,QAAQA,OAAO;IACvC;IACA,yEAAyE;IACzE,8EAA8E;IAC9E,wFAAwF;IACxF,OAAOrB,wBAAwBqB,QAAQA,OAAO;AAChD;AAEA,OAAO,eAAeK,mBACpBxB,GAAmB,EACnBiB,OAA4B;IAE5B,2FAA2F;IAC3F,MAAME,OAAO,MAAMH,yBAAyBhB,KAAKiB;IACjD,IAAI,CAACE,MAAM;QACT,OAAOjB,UAAU,gBAAgB;IACnC;IACA,OAAOiB;AACT"}
@@ -0,0 +1 @@
1
+ export * from '../client';
@@ -0,0 +1,3 @@
1
+ export * from '../client';
2
+
3
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export * from '../client'\n"],"names":[],"mappings":"AAAA,cAAc,YAAW"}
@@ -0,0 +1 @@
1
+ export { BackupDashboard } from '../components/BackupDashboard/index';
@@ -0,0 +1,3 @@
1
+ export { BackupDashboard } from '../components/BackupDashboard/index';
2
+
3
+ //# sourceMappingURL=rsc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["export { BackupDashboard } from '../components/BackupDashboard/index'\n"],"names":["BackupDashboard"],"mappings":"AAAA,SAASA,eAAe,QAAQ,sCAAqC"}
@@ -0,0 +1,18 @@
1
+ export { createTarGzip, resolveTarGzip } from './core/archive';
2
+ export { createBackup, createMediaBackupFile, listBackups, resolveBackupListToken, } from './core/backup';
3
+ export { getBackupSourcePreviewForManual } from './core/backupSourcePreview';
4
+ export type { BackupSourcePreviewResponse } from './core/backupSourcePreview';
5
+ export { getDb } from './core/db';
6
+ export type { MongoDb } from './core/db';
7
+ export { restoreBackup, restoreSeedMedia } from './core/restore';
8
+ export type { RestoreBackupOptions } from './core/restore';
9
+ export { buildCollectionPreviewGroups, buildRestorePreviewGroups, getRestorePreviewForAdminRestore, loadRestoreBackupIndex, navVisibleCollectionOrderIndex, } from './core/restorePreview';
10
+ export type { RestorePreviewAdminHiddenReason, RestorePreviewFileKind, RestorePreviewGroup, RestorePreviewResponse, } from './core/restorePreview';
11
+ export { completeBackupTask, createBackupTask, failBackupTask, getBackupTask, pollSecretsMatch, stripPollSecretForClient, updateBackupTask, } from './core/taskProgress';
12
+ export type { BackupTaskKind, BackupTaskProgress, BackupTaskStatus, BackupTaskWithPollSecret, } from './core/taskProgress';
13
+ export { backupMongodbPlugin } from './plugin';
14
+ export { backupPluginPublicApiPaths } from './publicApiPaths';
15
+ export type { BackupPluginOptions } from './types';
16
+ export { createBlobName, getBackupSortTimeMs, transformBlobName } from './utils/blobName';
17
+ export { formatBytes } from './utils/formatBytes';
18
+ export { getCurrentDbName, getCurrentHostname } from './utils/hostname';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export { createTarGzip, resolveTarGzip } from './core/archive';
2
+ export { createBackup, createMediaBackupFile, listBackups, resolveBackupListToken } from './core/backup';
3
+ export { getBackupSourcePreviewForManual } from './core/backupSourcePreview';
4
+ export { getDb } from './core/db';
5
+ export { restoreBackup, restoreSeedMedia } from './core/restore';
6
+ export { buildCollectionPreviewGroups, buildRestorePreviewGroups, getRestorePreviewForAdminRestore, loadRestoreBackupIndex, navVisibleCollectionOrderIndex } from './core/restorePreview';
7
+ export { completeBackupTask, createBackupTask, failBackupTask, getBackupTask, pollSecretsMatch, stripPollSecretForClient, updateBackupTask } from './core/taskProgress';
8
+ export { backupMongodbPlugin } from './plugin';
9
+ export { backupPluginPublicApiPaths } from './publicApiPaths';
10
+ export { createBlobName, getBackupSortTimeMs, transformBlobName } from './utils/blobName';
11
+ export { formatBytes } from './utils/formatBytes';
12
+ export { getCurrentDbName, getCurrentHostname } from './utils/hostname';
13
+
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createTarGzip, resolveTarGzip } from './core/archive'\nexport {\n createBackup,\n createMediaBackupFile,\n listBackups,\n resolveBackupListToken,\n} from './core/backup'\nexport { getBackupSourcePreviewForManual } from './core/backupSourcePreview'\n\nexport type { BackupSourcePreviewResponse } from './core/backupSourcePreview'\nexport { getDb } from './core/db'\nexport type { MongoDb } from './core/db'\nexport { restoreBackup, restoreSeedMedia } from './core/restore'\nexport type { RestoreBackupOptions } from './core/restore'\nexport {\n buildCollectionPreviewGroups,\n buildRestorePreviewGroups,\n getRestorePreviewForAdminRestore,\n loadRestoreBackupIndex,\n navVisibleCollectionOrderIndex,\n} from './core/restorePreview'\nexport type {\n RestorePreviewAdminHiddenReason,\n RestorePreviewFileKind,\n RestorePreviewGroup,\n RestorePreviewResponse,\n} from './core/restorePreview'\nexport {\n completeBackupTask,\n createBackupTask,\n failBackupTask,\n getBackupTask,\n pollSecretsMatch,\n stripPollSecretForClient,\n updateBackupTask,\n} from './core/taskProgress'\nexport type {\n BackupTaskKind,\n BackupTaskProgress,\n BackupTaskStatus,\n BackupTaskWithPollSecret,\n} from './core/taskProgress'\nexport { backupMongodbPlugin } from './plugin'\nexport { backupPluginPublicApiPaths } from './publicApiPaths'\nexport type { BackupPluginOptions } from './types'\n\nexport { createBlobName, getBackupSortTimeMs, transformBlobName } from './utils/blobName'\nexport { formatBytes } from './utils/formatBytes'\nexport { getCurrentDbName, getCurrentHostname } from './utils/hostname'\n"],"names":["createTarGzip","resolveTarGzip","createBackup","createMediaBackupFile","listBackups","resolveBackupListToken","getBackupSourcePreviewForManual","getDb","restoreBackup","restoreSeedMedia","buildCollectionPreviewGroups","buildRestorePreviewGroups","getRestorePreviewForAdminRestore","loadRestoreBackupIndex","navVisibleCollectionOrderIndex","completeBackupTask","createBackupTask","failBackupTask","getBackupTask","pollSecretsMatch","stripPollSecretForClient","updateBackupTask","backupMongodbPlugin","backupPluginPublicApiPaths","createBlobName","getBackupSortTimeMs","transformBlobName","formatBytes","getCurrentDbName","getCurrentHostname"],"mappings":"AAAA,SAASA,aAAa,EAAEC,cAAc,QAAQ,iBAAgB;AAC9D,SACEC,YAAY,EACZC,qBAAqB,EACrBC,WAAW,EACXC,sBAAsB,QACjB,gBAAe;AACtB,SAASC,+BAA+B,QAAQ,6BAA4B;AAG5E,SAASC,KAAK,QAAQ,YAAW;AAEjC,SAASC,aAAa,EAAEC,gBAAgB,QAAQ,iBAAgB;AAEhE,SACEC,4BAA4B,EAC5BC,yBAAyB,EACzBC,gCAAgC,EAChCC,sBAAsB,EACtBC,8BAA8B,QACzB,wBAAuB;AAO9B,SACEC,kBAAkB,EAClBC,gBAAgB,EAChBC,cAAc,EACdC,aAAa,EACbC,gBAAgB,EAChBC,wBAAwB,EACxBC,gBAAgB,QACX,sBAAqB;AAO5B,SAASC,mBAAmB,QAAQ,WAAU;AAC9C,SAASC,0BAA0B,QAAQ,mBAAkB;AAG7D,SAASC,cAAc,EAAEC,mBAAmB,EAAEC,iBAAiB,QAAQ,mBAAkB;AACzF,SAASC,WAAW,QAAQ,sBAAqB;AACjD,SAASC,gBAAgB,EAAEC,kBAAkB,QAAQ,mBAAkB"}
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'payload';
2
+ import type { BackupPluginOptions } from './types';
3
+ export declare const backupMongodbPlugin: (options?: BackupPluginOptions) => Plugin;
package/dist/plugin.js ADDED
@@ -0,0 +1,82 @@
1
+ import { BackupSettingsCollection } from './collections/BackupSettings';
2
+ import { BackupTasksCollection } from './collections/BackupTasks';
3
+ import { BACKUP_SETTINGS_SLUG } from './core/backupSettings';
4
+ import { createBackupMongodbEndpoints } from './endpoints/index';
5
+ export const backupMongodbPlugin = (options = {})=>{
6
+ return (incomingConfig)=>{
7
+ if (options.enabled === false) {
8
+ return incomingConfig;
9
+ }
10
+ const existingAfterDashboard = incomingConfig.admin?.components?.afterDashboard || [];
11
+ const existingCollections = incomingConfig.collections || [];
12
+ const existingEndpoints = incomingConfig.endpoints ?? [];
13
+ const backupEndpoints = createBackupMongodbEndpoints(options);
14
+ return {
15
+ ...incomingConfig,
16
+ admin: {
17
+ ...incomingConfig.admin,
18
+ components: {
19
+ ...incomingConfig.admin?.components,
20
+ afterDashboard: [
21
+ ...existingAfterDashboard,
22
+ '@trieb.work/payload-plugin-backup-mongodb/rsc#BackupDashboard'
23
+ ]
24
+ }
25
+ },
26
+ collections: [
27
+ ...existingCollections,
28
+ BackupTasksCollection,
29
+ BackupSettingsCollection
30
+ ],
31
+ endpoints: [
32
+ ...existingEndpoints,
33
+ ...backupEndpoints
34
+ ],
35
+ onInit: async (payload)=>{
36
+ await incomingConfig.onInit?.(payload);
37
+ try {
38
+ const existing = await payload.count({
39
+ collection: BACKUP_SETTINGS_SLUG,
40
+ overrideAccess: true
41
+ });
42
+ if (existing.totalDocs === 0) {
43
+ await payload.create({
44
+ collection: BACKUP_SETTINGS_SLUG,
45
+ data: {
46
+ backupBlobAccess: null,
47
+ backupBlobReadWriteToken: '',
48
+ backupsToKeep: Number(process.env.BACKUPS_TO_KEEP) || 10,
49
+ includeMediaForCron: true,
50
+ skipMongoCollections: []
51
+ },
52
+ overrideAccess: true
53
+ });
54
+ payload.logger.info('[backup-plugin] Created default backup-settings document');
55
+ }
56
+ } catch (err) {
57
+ payload.logger.warn({
58
+ err
59
+ }, '[backup-plugin] Could not ensure backup-settings document');
60
+ }
61
+ try {
62
+ if (payload.db.name === 'mongoose') {
63
+ const mongooseDb = payload.db;
64
+ const db = mongooseDb.connection?.db;
65
+ if (db) {
66
+ await db.collection('backup-tasks').createIndex({
67
+ updatedAt: 1
68
+ }, {
69
+ background: true,
70
+ expireAfterSeconds: 1800
71
+ });
72
+ }
73
+ }
74
+ } catch {
75
+ payload.logger.warn('[backup-plugin] Could not create TTL index on backup-tasks');
76
+ }
77
+ }
78
+ };
79
+ };
80
+ };
81
+
82
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { BackupPluginOptions } from './types'\n\nimport { BackupSettingsCollection } from './collections/BackupSettings'\nimport { BackupTasksCollection } from './collections/BackupTasks'\nimport { BACKUP_SETTINGS_SLUG } from './core/backupSettings'\nimport { createBackupMongodbEndpoints } from './endpoints/index'\n\nexport const backupMongodbPlugin = (options: BackupPluginOptions = {}): Plugin => {\n return (incomingConfig: Config): Config => {\n if (options.enabled === false) {\n return incomingConfig\n }\n\n const existingAfterDashboard = incomingConfig.admin?.components?.afterDashboard || []\n const existingCollections = incomingConfig.collections || []\n const existingEndpoints = incomingConfig.endpoints ?? []\n const backupEndpoints = createBackupMongodbEndpoints(options)\n\n return {\n ...incomingConfig,\n admin: {\n ...incomingConfig.admin,\n components: {\n ...incomingConfig.admin?.components,\n afterDashboard: [\n ...existingAfterDashboard,\n '@trieb.work/payload-plugin-backup-mongodb/rsc#BackupDashboard',\n ],\n },\n },\n collections: [...existingCollections, BackupTasksCollection, BackupSettingsCollection],\n endpoints: [...existingEndpoints, ...backupEndpoints],\n onInit: async (payload) => {\n await incomingConfig.onInit?.(payload)\n try {\n const existing = await payload.count({\n collection: BACKUP_SETTINGS_SLUG,\n overrideAccess: true,\n })\n if (existing.totalDocs === 0) {\n await payload.create({\n collection: BACKUP_SETTINGS_SLUG,\n data: {\n backupBlobAccess: null,\n backupBlobReadWriteToken: '',\n backupsToKeep: Number(process.env.BACKUPS_TO_KEEP) || 10,\n includeMediaForCron: true,\n skipMongoCollections: [],\n },\n overrideAccess: true,\n })\n payload.logger.info('[backup-plugin] Created default backup-settings document')\n }\n } catch (err) {\n payload.logger.warn({ err }, '[backup-plugin] Could not ensure backup-settings document')\n }\n try {\n if (payload.db.name === 'mongoose') {\n const mongooseDb = payload.db as {\n connection?: {\n db?: {\n collection: (name: string) => {\n createIndex: (\n keys: Record<string, number>,\n options?: { background?: boolean; expireAfterSeconds?: number },\n ) => Promise<string>\n }\n }\n }\n }\n const db = mongooseDb.connection?.db\n if (db) {\n await db.collection('backup-tasks').createIndex(\n { updatedAt: 1 },\n { background: true, expireAfterSeconds: 1800 },\n )\n }\n }\n } catch {\n payload.logger.warn('[backup-plugin] Could not create TTL index on backup-tasks')\n }\n },\n }\n }\n}\n"],"names":["BackupSettingsCollection","BackupTasksCollection","BACKUP_SETTINGS_SLUG","createBackupMongodbEndpoints","backupMongodbPlugin","options","incomingConfig","enabled","existingAfterDashboard","admin","components","afterDashboard","existingCollections","collections","existingEndpoints","endpoints","backupEndpoints","onInit","payload","existing","count","collection","overrideAccess","totalDocs","create","data","backupBlobAccess","backupBlobReadWriteToken","backupsToKeep","Number","process","env","BACKUPS_TO_KEEP","includeMediaForCron","skipMongoCollections","logger","info","err","warn","db","name","mongooseDb","connection","createIndex","updatedAt","background","expireAfterSeconds"],"mappings":"AAIA,SAASA,wBAAwB,QAAQ,+BAA8B;AACvE,SAASC,qBAAqB,QAAQ,4BAA2B;AACjE,SAASC,oBAAoB,QAAQ,wBAAuB;AAC5D,SAASC,4BAA4B,QAAQ,oBAAmB;AAEhE,OAAO,MAAMC,sBAAsB,CAACC,UAA+B,CAAC,CAAC;IACnE,OAAO,CAACC;QACN,IAAID,QAAQE,OAAO,KAAK,OAAO;YAC7B,OAAOD;QACT;QAEA,MAAME,yBAAyBF,eAAeG,KAAK,EAAEC,YAAYC,kBAAkB,EAAE;QACrF,MAAMC,sBAAsBN,eAAeO,WAAW,IAAI,EAAE;QAC5D,MAAMC,oBAAoBR,eAAeS,SAAS,IAAI,EAAE;QACxD,MAAMC,kBAAkBb,6BAA6BE;QAErD,OAAO;YACL,GAAGC,cAAc;YACjBG,OAAO;gBACL,GAAGH,eAAeG,KAAK;gBACvBC,YAAY;oBACV,GAAGJ,eAAeG,KAAK,EAAEC,UAAU;oBACnCC,gBAAgB;2BACXH;wBACH;qBACD;gBACH;YACF;YACAK,aAAa;mBAAID;gBAAqBX;gBAAuBD;aAAyB;YACtFe,WAAW;mBAAID;mBAAsBE;aAAgB;YACrDC,QAAQ,OAAOC;gBACb,MAAMZ,eAAeW,MAAM,GAAGC;gBAC9B,IAAI;oBACF,MAAMC,WAAW,MAAMD,QAAQE,KAAK,CAAC;wBACnCC,YAAYnB;wBACZoB,gBAAgB;oBAClB;oBACA,IAAIH,SAASI,SAAS,KAAK,GAAG;wBAC5B,MAAML,QAAQM,MAAM,CAAC;4BACnBH,YAAYnB;4BACZuB,MAAM;gCACJC,kBAAkB;gCAClBC,0BAA0B;gCAC1BC,eAAeC,OAAOC,QAAQC,GAAG,CAACC,eAAe,KAAK;gCACtDC,qBAAqB;gCACrBC,sBAAsB,EAAE;4BAC1B;4BACAZ,gBAAgB;wBAClB;wBACAJ,QAAQiB,MAAM,CAACC,IAAI,CAAC;oBACtB;gBACF,EAAE,OAAOC,KAAK;oBACZnB,QAAQiB,MAAM,CAACG,IAAI,CAAC;wBAAED;oBAAI,GAAG;gBAC/B;gBACA,IAAI;oBACF,IAAInB,QAAQqB,EAAE,CAACC,IAAI,KAAK,YAAY;wBAClC,MAAMC,aAAavB,QAAQqB,EAAE;wBAY7B,MAAMA,KAAKE,WAAWC,UAAU,EAAEH;wBAClC,IAAIA,IAAI;4BACN,MAAMA,GAAGlB,UAAU,CAAC,gBAAgBsB,WAAW,CAC7C;gCAAEC,WAAW;4BAAE,GACf;gCAAEC,YAAY;gCAAMC,oBAAoB;4BAAK;wBAEjD;oBACF;gBACF,EAAE,OAAM;oBACN5B,QAAQiB,MAAM,CAACG,IAAI,CAAC;gBACtB;YACF;QACF;IACF;AACF,EAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Browser-facing paths for the backup plugin (Payload custom REST under default {@code /api}).
3
+ * Must stay in sync with {@link createBackupMongodbEndpoints} in `endpoints/index.ts`.
4
+ */
5
+ export declare const backupPluginPublicApiPaths: {
6
+ readonly adminBackupDownload: "/api/backup-mongodb/admin/backup-download";
7
+ readonly adminBackupPreview: "/api/backup-mongodb/admin/backup-preview";
8
+ readonly adminDelete: "/api/backup-mongodb/admin/delete";
9
+ readonly adminManual: "/api/backup-mongodb/admin/manual";
10
+ readonly adminRestore: "/api/backup-mongodb/admin/restore";
11
+ readonly adminRestorePreview: "/api/backup-mongodb/admin/restore-preview";
12
+ readonly adminSeed: "/api/backup-mongodb/admin/seed";
13
+ readonly adminSettings: "/api/backup-mongodb/admin/settings";
14
+ readonly adminTask: (id: string) => string;
15
+ readonly adminValidateBlobToken: "/api/backup-mongodb/admin/validate-blob-token";
16
+ readonly cron: "/api/backup-mongodb/cron/run";
17
+ readonly cronRestore: "/api/backup-mongodb/cron/restore";
18
+ readonly list: "/api/backup-mongodb/cron/list";
19
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Browser-facing paths for the backup plugin (Payload custom REST under default {@code /api}).
3
+ * Must stay in sync with {@link createBackupMongodbEndpoints} in `endpoints/index.ts`.
4
+ */ export const backupPluginPublicApiPaths = {
5
+ adminBackupDownload: '/api/backup-mongodb/admin/backup-download',
6
+ adminBackupPreview: '/api/backup-mongodb/admin/backup-preview',
7
+ adminDelete: '/api/backup-mongodb/admin/delete',
8
+ adminManual: '/api/backup-mongodb/admin/manual',
9
+ adminRestore: '/api/backup-mongodb/admin/restore',
10
+ adminRestorePreview: '/api/backup-mongodb/admin/restore-preview',
11
+ adminSeed: '/api/backup-mongodb/admin/seed',
12
+ adminSettings: '/api/backup-mongodb/admin/settings',
13
+ adminTask: (id)=>`/api/backup-mongodb/admin/task/${encodeURIComponent(id)}`,
14
+ adminValidateBlobToken: '/api/backup-mongodb/admin/validate-blob-token',
15
+ cron: '/api/backup-mongodb/cron/run',
16
+ cronRestore: '/api/backup-mongodb/cron/restore',
17
+ list: '/api/backup-mongodb/cron/list'
18
+ };
19
+
20
+ //# sourceMappingURL=publicApiPaths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/publicApiPaths.ts"],"sourcesContent":["/**\n * Browser-facing paths for the backup plugin (Payload custom REST under default {@code /api}).\n * Must stay in sync with {@link createBackupMongodbEndpoints} in `endpoints/index.ts`.\n */\nexport const backupPluginPublicApiPaths = {\n adminBackupDownload: '/api/backup-mongodb/admin/backup-download',\n adminBackupPreview: '/api/backup-mongodb/admin/backup-preview',\n adminDelete: '/api/backup-mongodb/admin/delete',\n adminManual: '/api/backup-mongodb/admin/manual',\n adminRestore: '/api/backup-mongodb/admin/restore',\n adminRestorePreview: '/api/backup-mongodb/admin/restore-preview',\n adminSeed: '/api/backup-mongodb/admin/seed',\n adminSettings: '/api/backup-mongodb/admin/settings',\n adminTask: (id: string) => `/api/backup-mongodb/admin/task/${encodeURIComponent(id)}`,\n adminValidateBlobToken: '/api/backup-mongodb/admin/validate-blob-token',\n cron: '/api/backup-mongodb/cron/run',\n cronRestore: '/api/backup-mongodb/cron/restore',\n list: '/api/backup-mongodb/cron/list',\n} as const\n"],"names":["backupPluginPublicApiPaths","adminBackupDownload","adminBackupPreview","adminDelete","adminManual","adminRestore","adminRestorePreview","adminSeed","adminSettings","adminTask","id","encodeURIComponent","adminValidateBlobToken","cron","cronRestore","list"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,6BAA6B;IACxCC,qBAAqB;IACrBC,oBAAoB;IACpBC,aAAa;IACbC,aAAa;IACbC,cAAc;IACdC,qBAAqB;IACrBC,WAAW;IACXC,eAAe;IACfC,WAAW,CAACC,KAAe,CAAC,+BAA+B,EAAEC,mBAAmBD,KAAK;IACrFE,wBAAwB;IACxBC,MAAM;IACNC,aAAa;IACbC,MAAM;AACR,EAAU"}
@@ -0,0 +1,21 @@
1
+ export type BackupPluginOptions = {
2
+ /**
3
+ * Custom access control function. Receives the user object from Payload and returns true
4
+ * if the user is allowed to see/use the backup dashboard.
5
+ *
6
+ * When omitted, visibility falls back to the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var
7
+ * (comma-separated role slugs, or `*` for any authenticated user). If that env var is
8
+ * also unset, the historical default applies: user has a role with slug `admin`, or —
9
+ * for users collections that have no `roles` field — any authenticated user.
10
+ */
11
+ access?: (user: null | Record<string, unknown>) => boolean;
12
+ /** Number of automatic (cron) backups to keep. Default: process.env.BACKUPS_TO_KEEP or 10 */
13
+ backupsToKeep?: number;
14
+ /** Enable/disable the plugin entirely. Default: true */
15
+ enabled?: boolean;
16
+ /**
17
+ * When set, registers POST {@code /api/backup-mongodb/admin/seed} (demo DB + media seed).
18
+ * Omit to disable the seed endpoint entirely.
19
+ */
20
+ seedDemoDumpUrl?: string;
21
+ };
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ export { };
2
+
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["export type BackupPluginOptions = {\n /**\n * Custom access control function. Receives the user object from Payload and returns true\n * if the user is allowed to see/use the backup dashboard.\n *\n * When omitted, visibility falls back to the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var\n * (comma-separated role slugs, or `*` for any authenticated user). If that env var is\n * also unset, the historical default applies: user has a role with slug `admin`, or —\n * for users collections that have no `roles` field — any authenticated user.\n */\n access?: (user: null | Record<string, unknown>) => boolean\n /** Number of automatic (cron) backups to keep. Default: process.env.BACKUPS_TO_KEEP or 10 */\n backupsToKeep?: number\n /** Enable/disable the plugin entirely. Default: true */\n enabled?: boolean\n /**\n * When set, registers POST {@code /api/backup-mongodb/admin/seed} (demo DB + media seed).\n * Omit to disable the seed endpoint entirely.\n */\n seedDemoDumpUrl?: string\n}\n"],"names":[],"mappings":"AAAA,WAoBC"}
@@ -0,0 +1,6 @@
1
+ import type { BackupSourcePreviewResponse } from '../core/backupSourcePreview';
2
+ export declare function skipMongoNamesFromPreview(preview: BackupSourcePreviewResponse, selected: Record<string, boolean>): string[];
3
+ /**
4
+ * Derives checkbox state from stored Mongo collection names to skip (cron / settings).
5
+ */
6
+ export declare function selectedFromSkipMongoNames(preview: BackupSourcePreviewResponse, skipMongoNames: string[]): Record<string, boolean>;
@@ -0,0 +1,16 @@
1
+ export function skipMongoNamesFromPreview(preview, selected) {
2
+ return preview.groups.filter((g)=>selected[g.groupId] === false).flatMap((g)=>g.mongoNames);
3
+ }
4
+ /**
5
+ * Derives checkbox state from stored Mongo collection names to skip (cron / settings).
6
+ */ export function selectedFromSkipMongoNames(preview, skipMongoNames) {
7
+ const skip = new Set(skipMongoNames);
8
+ const selected = {};
9
+ for (const g of preview.groups){
10
+ const allSkipped = g.mongoNames.length > 0 && g.mongoNames.every((m)=>skip.has(m));
11
+ selected[g.groupId] = !allSkipped;
12
+ }
13
+ return selected;
14
+ }
15
+
16
+ //# sourceMappingURL=backupSelection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/backupSelection.ts"],"sourcesContent":["import type { BackupSourcePreviewResponse } from '../core/backupSourcePreview'\n\nexport function skipMongoNamesFromPreview(\n preview: BackupSourcePreviewResponse,\n selected: Record<string, boolean>,\n): string[] {\n return preview.groups.filter((g) => selected[g.groupId] === false).flatMap((g) => g.mongoNames)\n}\n\n/**\n * Derives checkbox state from stored Mongo collection names to skip (cron / settings).\n */\nexport function selectedFromSkipMongoNames(\n preview: BackupSourcePreviewResponse,\n skipMongoNames: string[],\n): Record<string, boolean> {\n const skip = new Set(skipMongoNames)\n const selected: Record<string, boolean> = {}\n for (const g of preview.groups) {\n const allSkipped = g.mongoNames.length > 0 && g.mongoNames.every((m) => skip.has(m))\n selected[g.groupId] = !allSkipped\n }\n return selected\n}\n"],"names":["skipMongoNamesFromPreview","preview","selected","groups","filter","g","groupId","flatMap","mongoNames","selectedFromSkipMongoNames","skipMongoNames","skip","Set","allSkipped","length","every","m","has"],"mappings":"AAEA,OAAO,SAASA,0BACdC,OAAoC,EACpCC,QAAiC;IAEjC,OAAOD,QAAQE,MAAM,CAACC,MAAM,CAAC,CAACC,IAAMH,QAAQ,CAACG,EAAEC,OAAO,CAAC,KAAK,OAAOC,OAAO,CAAC,CAACF,IAAMA,EAAEG,UAAU;AAChG;AAEA;;CAEC,GACD,OAAO,SAASC,2BACdR,OAAoC,EACpCS,cAAwB;IAExB,MAAMC,OAAO,IAAIC,IAAIF;IACrB,MAAMR,WAAoC,CAAC;IAC3C,KAAK,MAAMG,KAAKJ,QAAQE,MAAM,CAAE;QAC9B,MAAMU,aAAaR,EAAEG,UAAU,CAACM,MAAM,GAAG,KAAKT,EAAEG,UAAU,CAACO,KAAK,CAAC,CAACC,IAAML,KAAKM,GAAG,CAACD;QACjFd,QAAQ,CAACG,EAAEC,OAAO,CAAC,GAAG,CAACO;IACzB;IACA,OAAOX;AACT"}
@@ -0,0 +1,32 @@
1
+ /** Max length for a user-supplied manual backup label (after sanitization). */
2
+ export declare const BACKUP_LABEL_MAX_LENGTH = 64;
3
+ /**
4
+ * Normalize a user-supplied manual backup label so it is safe to embed as a
5
+ * blob-name segment. Trims, collapses whitespace, removes characters that would
6
+ * confuse the separator, and enforces {@link BACKUP_LABEL_MAX_LENGTH}.
7
+ * Returns an empty string when nothing usable remains.
8
+ */
9
+ export declare function sanitizeBackupLabel(raw: unknown): string;
10
+ export interface TransformBlobNameResult {
11
+ collectionCount?: number;
12
+ date: string;
13
+ dbName: string;
14
+ fileType: 'json' | 'na' | 'tar.gz';
15
+ hostname: string;
16
+ label?: string;
17
+ type: string;
18
+ }
19
+ export declare function transformBlobName(blobName: string): TransformBlobNameResult;
20
+ /**
21
+ * Backup blob pathname segment (without `backups/` prefix or extension).
22
+ * New format: `{collectionCount}-{timestampMs}` encodes included collection count + backup time.
23
+ * When a non-empty `label` is supplied it is appended as a separate URL-encoded segment
24
+ * (`---{encodedLabel}`) so older parsers that only read the first 4 segments still work.
25
+ */
26
+ export declare function createBlobName(type: string, dbName: string, hostname: string, collectionCount: number, timestampMs: number, fileType: 'json' | 'tar.gz', label?: string): string;
27
+ /**
28
+ * Prefer timestamp encoded in filename (stable across blob store migration); else blob upload time.
29
+ * Legacy tails are digits-only epoch values; if {@link TransformBlobNameResult.collectionCount}
30
+ * is absent, values below 1e12 are treated as **Unix seconds** (common older convention), else ms.
31
+ */
32
+ export declare function getBackupSortTimeMs(parsed: TransformBlobNameResult, uploadedAt: Date): number;