@trieb.work/payload-plugin-backup-mongodb 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +260 -90
  2. package/dist/backup-dashboard.css +18 -1
  3. package/dist/collections/BackupTasks.js +8 -1
  4. package/dist/collections/BackupTasks.js.map +1 -1
  5. package/dist/components/BackupDashboard/BackupListCollapsible.client.js.map +1 -1
  6. package/dist/components/BackupDashboard/BackupSettingsModal.client.js +203 -115
  7. package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -1
  8. package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js.map +1 -1
  9. package/dist/components/BackupDashboard/RestoreBackupDialog.client.js +1 -1
  10. package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -1
  11. package/dist/components/BackupDashboard/TaskActionButton.client.d.ts +1 -1
  12. package/dist/components/BackupDashboard/TaskActionButton.client.js +59 -26
  13. package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -1
  14. package/dist/components/BackupDashboard/backupDashboardInlineCss.js +1 -1
  15. package/dist/components/BackupDashboard/backupDashboardInlineCss.js.map +1 -1
  16. package/dist/components/BackupDashboard/index.js +27 -3
  17. package/dist/components/BackupDashboard/index.js.map +1 -1
  18. package/dist/core/archive.js.map +1 -1
  19. package/dist/core/backup.d.ts +4 -3
  20. package/dist/core/backup.js +50 -43
  21. package/dist/core/backup.js.map +1 -1
  22. package/dist/core/backupArchiveRead.d.ts +15 -0
  23. package/dist/core/backupArchiveRead.js +30 -0
  24. package/dist/core/backupArchiveRead.js.map +1 -0
  25. package/dist/core/backupBlobIO.js.map +1 -1
  26. package/dist/core/backupSettings.js +5 -0
  27. package/dist/core/backupSettings.js.map +1 -1
  28. package/dist/core/blobTokenValidate.js.map +1 -1
  29. package/dist/core/restore.d.ts +5 -2
  30. package/dist/core/restore.js +133 -89
  31. package/dist/core/restore.js.map +1 -1
  32. package/dist/core/restorePreview.d.ts +12 -3
  33. package/dist/core/restorePreview.js +17 -13
  34. package/dist/core/restorePreview.js.map +1 -1
  35. package/dist/core/restoreResult.d.ts +34 -0
  36. package/dist/core/restoreResult.js +45 -0
  37. package/dist/core/restoreResult.js.map +1 -0
  38. package/dist/core/storage/config.d.ts +31 -0
  39. package/dist/core/storage/config.js +60 -0
  40. package/dist/core/storage/config.js.map +1 -0
  41. package/dist/core/storage/index.d.ts +23 -0
  42. package/dist/core/storage/index.js +25 -0
  43. package/dist/core/storage/index.js.map +1 -0
  44. package/dist/core/storage/s3.d.ts +35 -0
  45. package/dist/core/storage/s3.js +200 -0
  46. package/dist/core/storage/s3.js.map +1 -0
  47. package/dist/core/storage/types.d.ts +60 -0
  48. package/dist/core/storage/types.js +6 -0
  49. package/dist/core/storage/types.js.map +1 -0
  50. package/dist/core/storage/vercelBlob.d.ts +25 -0
  51. package/dist/core/storage/vercelBlob.js +53 -0
  52. package/dist/core/storage/vercelBlob.js.map +1 -0
  53. package/dist/core/taskProgress.d.ts +10 -4
  54. package/dist/core/taskProgress.js +11 -2
  55. package/dist/core/taskProgress.js.map +1 -1
  56. package/dist/core/vercelBackupCron.js.map +1 -1
  57. package/dist/endpoints/index.js +0 -5
  58. package/dist/endpoints/index.js.map +1 -1
  59. package/dist/endpoints/paths/admin-backup-download.js +8 -7
  60. package/dist/endpoints/paths/admin-backup-download.js.map +1 -1
  61. package/dist/endpoints/paths/admin-delete.js +11 -5
  62. package/dist/endpoints/paths/admin-delete.js.map +1 -1
  63. package/dist/endpoints/paths/admin-manual.js +2 -1
  64. package/dist/endpoints/paths/admin-manual.js.map +1 -1
  65. package/dist/endpoints/paths/admin-preview.js +9 -2
  66. package/dist/endpoints/paths/admin-preview.js.map +1 -1
  67. package/dist/endpoints/paths/admin-restore.js +7 -4
  68. package/dist/endpoints/paths/admin-restore.js.map +1 -1
  69. package/dist/endpoints/paths/admin-settings.js +35 -2
  70. package/dist/endpoints/paths/admin-settings.js.map +1 -1
  71. package/dist/endpoints/paths/cron-list.js +2 -1
  72. package/dist/endpoints/paths/cron-list.js.map +1 -1
  73. package/dist/endpoints/paths/cron-restore.js +14 -4
  74. package/dist/endpoints/paths/cron-restore.js.map +1 -1
  75. package/dist/endpoints/paths/cron-run.js +2 -1
  76. package/dist/endpoints/paths/cron-run.js.map +1 -1
  77. package/dist/endpoints/shared.d.ts +0 -1
  78. package/dist/endpoints/shared.js +0 -6
  79. package/dist/endpoints/shared.js.map +1 -1
  80. package/dist/index.d.ts +5 -3
  81. package/dist/index.js +3 -2
  82. package/dist/index.js.map +1 -1
  83. package/dist/plugin.js.map +1 -1
  84. package/dist/publicApiPaths.d.ts +0 -1
  85. package/dist/publicApiPaths.js +0 -1
  86. package/dist/publicApiPaths.js.map +1 -1
  87. package/dist/types.d.ts +0 -5
  88. package/dist/types.js.map +1 -1
  89. package/dist/utils/blobName.js.map +1 -1
  90. package/dist/utils/dashboardRoleAccess.js.map +1 -1
  91. package/dist/utils/dialogBackdrop.js.map +1 -1
  92. package/dist/utils/hostname.js.map +1 -1
  93. package/dist/utils/maskBlobToken.js.map +1 -1
  94. package/package.json +25 -4
  95. package/dist/endpoints/paths/admin-seed.d.ts +0 -3
  96. package/dist/endpoints/paths/admin-seed.js +0 -47
  97. package/dist/endpoints/paths/admin-seed.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/endpoints/paths/admin-settings.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { list } from '@vercel/blob'\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { transferBackupBlobsToToken } from '../../core/backupBlobTransfer'\nimport {\n BACKUP_SETTINGS_SLUG,\n getResolvedCronBackupSettings,\n normalizeSkipMongoCollections,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n toPayloadSkipRows,\n} from '../../core/backupSettings'\nimport { validateBackupBlobToken } from '../../core/blobTokenValidate'\nimport {\n completeBackupTask,\n createBackupTask,\n failBackupTask,\n updateBackupTask,\n} from '../../core/taskProgress'\nimport { describeCronSchedule, readVercelBackupCronFromRepo } from '../../core/vercelBackupCron'\nimport {\n maskBlobReadWriteToken,\n shouldPreserveBackupBlobTokenField,\n} from '../../utils/maskBlobToken'\nimport { readRequestJson, requireBackupAdmin } from '../shared'\n\nfunction clampBackupsToKeep(n: unknown): number {\n if (typeof n !== 'number' || !Number.isFinite(n)) {\n return 10\n }\n return Math.min(365, Math.max(1, Math.floor(n)))\n}\n\nfunction buildSettingsJson(\n stored: Awaited<ReturnType<typeof getResolvedCronBackupSettings>>,\n options: BackupPluginOptions,\n vercelCron: ReturnType<typeof readVercelBackupCronFromRepo>,\n humanDescription: null | string,\n transfer:\n | {\n deferred: boolean\n failed: number\n performed: boolean\n pollSecret?: string\n skipped: number\n taskId?: string\n total: number\n transferred: number\n }\n | undefined,\n) {\n const t = transfer ?? {\n deferred: false,\n failed: 0,\n performed: false,\n skipped: 0,\n total: 0,\n transferred: 0,\n }\n return {\n id: stored.id,\n backupBlobAccess: stored.backupBlobAccess,\n backupBlobAccessEffective: resolveBackupBlobAccess(stored),\n backupBlobTokenMasked: maskBlobReadWriteToken(stored.backupBlobReadWriteToken),\n backupsToKeep: stored.backupsToKeep,\n cron: vercelCron\n ? {\n configFileRelative: vercelCron.configFileRelative,\n humanDescription,\n path: vercelCron.path,\n schedule: vercelCron.schedule,\n }\n : null,\n effectiveBackupsToKeep:\n typeof options.backupsToKeep === 'number' ? options.backupsToKeep : stored.backupsToKeep,\n hasBackupBlobReadWriteToken: stored.backupBlobReadWriteToken.trim().length > 0,\n includeMediaForCron: stored.includeMediaForCron,\n pluginBackupsToKeepOverride: typeof options.backupsToKeep === 'number',\n skipMongoCollections: stored.skipMongoCollections,\n transfer: t,\n }\n}\n\nexport function createAdminSettingsEndpoints(options: BackupPluginOptions): Endpoint[] {\n return [\n {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const stored = await getResolvedCronBackupSettings(payload)\n const vercelCron = readVercelBackupCronFromRepo()\n const humanDescription =\n vercelCron?.schedule != null ? describeCronSchedule(vercelCron.schedule) : null\n\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, undefined),\n )\n },\n method: 'get',\n path: '/backup-mongodb/admin/settings',\n },\n {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const body = (await readRequestJson(req)) as Record<string, unknown>\n const backupsToKeep = clampBackupsToKeep(body?.backupsToKeep)\n const skipMongoCollections = normalizeSkipMongoCollections(body?.skipMongoCollections)\n const includeMediaForCron = body?.includeMediaForCron === true\n const transferBackupBlobs =\n typeof body?.transferBackupBlobs === 'boolean' ? body.transferBackupBlobs : false\n const deleteBackupBlobsFromSource =\n typeof body?.deleteBackupBlobsFromSource === 'boolean'\n ? body.deleteBackupBlobsFromSource\n : false\n const requestedRaw =\n typeof body?.backupBlobReadWriteToken === 'string' ? body.backupBlobReadWriteToken : ''\n const requestedTrimmed = requestedRaw.trim()\n\n let stored = await getResolvedCronBackupSettings(payload)\n const previousAccess = resolveBackupBlobAccess(stored)\n const previousBackupBlobToken = stored.backupBlobReadWriteToken.trim()\n const hadStoredToken = previousBackupBlobToken.length > 0\n const preserveTokenField = shouldPreserveBackupBlobTokenField(\n requestedTrimmed,\n hadStoredToken,\n )\n const tokenForDb = preserveTokenField ? previousBackupBlobToken : requestedTrimmed\n\n // Access detection: re-probe when the token actually changes; preserve existing otherwise;\n // clear when the override is removed (falls back to heuristic on the default env token).\n let backupBlobAccessForDb: 'private' | 'public' | null = stored.backupBlobAccess\n if (!preserveTokenField) {\n if (tokenForDb.length === 0) {\n backupBlobAccessForDb = null\n } else {\n const validation = await validateBackupBlobToken(tokenForDb)\n if (!validation.ok) {\n return Response.json(\n { error: `Blob token rejected: ${validation.error ?? 'unknown error'}` },\n { status: 422 },\n )\n }\n backupBlobAccessForDb = validation.access ?? null\n }\n }\n\n if (!stored.id) {\n await payload.create({\n collection: BACKUP_SETTINGS_SLUG,\n data: {\n backupBlobAccess: backupBlobAccessForDb,\n backupBlobReadWriteToken: tokenForDb,\n backupsToKeep,\n includeMediaForCron,\n skipMongoCollections: toPayloadSkipRows(skipMongoCollections),\n },\n overrideAccess: true,\n })\n } else {\n await payload.update({\n id: stored.id,\n collection: BACKUP_SETTINGS_SLUG,\n data: {\n backupBlobAccess: backupBlobAccessForDb,\n backupBlobReadWriteToken: tokenForDb,\n backupsToKeep,\n includeMediaForCron,\n skipMongoCollections: toPayloadSkipRows(skipMongoCollections),\n },\n overrideAccess: true,\n })\n }\n\n stored = await getResolvedCronBackupSettings(payload)\n if (!stored.id) {\n return Response.json({ error: 'Could not persist backup settings' }, { status: 500 })\n }\n\n const vercelCron = readVercelBackupCronFromRepo()\n const humanDescription =\n vercelCron?.schedule != null ? describeCronSchedule(vercelCron.schedule) : null\n\n const newBlobToken = stored.backupBlobReadWriteToken.trim()\n // Read blobs from the *previous* store when rotating tokens; otherwise first-time setup\n // reads from BLOB_READ_WRITE_TOKEN (default Vercel store).\n const sourceTokenForTransfer =\n previousBackupBlobToken.length > 0\n ? previousBackupBlobToken\n : (process.env.BLOB_READ_WRITE_TOKEN || '').trim()\n\n const shouldTransferToNewBlobToken =\n newBlobToken.length > 0 &&\n sourceTokenForTransfer.length > 0 &&\n newBlobToken !== sourceTokenForTransfer\n\n if (!transferBackupBlobs || !shouldTransferToNewBlobToken) {\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: false,\n failed: 0,\n performed: false,\n skipped: 0,\n total: 0,\n transferred: 0,\n }),\n )\n }\n\n const targetToken = resolveBackupBlobToken(stored)\n const targetAccess = resolveBackupBlobAccess(stored)\n const { blobs } = await list({\n limit: 1000,\n prefix: 'backups/',\n token: sourceTokenForTransfer,\n })\n const total = blobs.length\n\n if (total === 0) {\n const summary = await transferBackupBlobsToToken(\n payload,\n sourceTokenForTransfer,\n targetToken,\n {\n deleteFromSource: deleteBackupBlobsFromSource,\n sourceAccessHint: previousAccess,\n targetAccess,\n },\n )\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: false,\n failed: summary.failed,\n performed: true,\n skipped: summary.skipped,\n total: summary.total,\n transferred: summary.transferred,\n }),\n )\n }\n\n const { pollSecret, taskId } = await createBackupTask(\n payload,\n 'blobTransfer',\n JSON.stringify({ failed: 0, pathname: '', total, transferred: 0 }),\n )\n\n after(\n (async () => {\n try {\n await updateBackupTask(payload, taskId, {\n message: JSON.stringify({ failed: 0, pathname: '', total, transferred: 0 }),\n status: 'running',\n })\n const summary = await transferBackupBlobsToToken(\n payload,\n sourceTokenForTransfer,\n targetToken,\n {\n deleteFromSource: deleteBackupBlobsFromSource,\n sourceAccessHint: previousAccess,\n targetAccess,\n taskId,\n },\n )\n await completeBackupTask(\n payload,\n taskId,\n JSON.stringify({\n failed: summary.failed,\n pathname: '',\n phase: 'done',\n total: summary.total,\n transferred: summary.transferred,\n }),\n )\n } catch (e) {\n await failBackupTask(payload, taskId, e)\n payload.logger.error(\n { err: e, taskId },\n '[backup-settings] Blob transfer task failed',\n )\n }\n })(),\n )\n\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: true,\n failed: 0,\n performed: true,\n pollSecret,\n skipped: 0,\n taskId,\n total,\n transferred: 0,\n }),\n )\n },\n method: 'patch',\n path: '/backup-mongodb/admin/settings',\n },\n ]\n}\n"],"names":["list","after","transferBackupBlobsToToken","BACKUP_SETTINGS_SLUG","getResolvedCronBackupSettings","normalizeSkipMongoCollections","resolveBackupBlobAccess","resolveBackupBlobToken","toPayloadSkipRows","validateBackupBlobToken","completeBackupTask","createBackupTask","failBackupTask","updateBackupTask","describeCronSchedule","readVercelBackupCronFromRepo","maskBlobReadWriteToken","shouldPreserveBackupBlobTokenField","readRequestJson","requireBackupAdmin","clampBackupsToKeep","n","Number","isFinite","Math","min","max","floor","buildSettingsJson","stored","options","vercelCron","humanDescription","transfer","t","deferred","failed","performed","skipped","total","transferred","id","backupBlobAccess","backupBlobAccessEffective","backupBlobTokenMasked","backupBlobReadWriteToken","backupsToKeep","cron","configFileRelative","path","schedule","effectiveBackupsToKeep","hasBackupBlobReadWriteToken","trim","length","includeMediaForCron","pluginBackupsToKeepOverride","skipMongoCollections","createAdminSettingsEndpoints","handler","req","auth","Response","payload","json","undefined","method","body","transferBackupBlobs","deleteBackupBlobsFromSource","requestedRaw","requestedTrimmed","previousAccess","previousBackupBlobToken","hadStoredToken","preserveTokenField","tokenForDb","backupBlobAccessForDb","validation","ok","error","status","access","create","collection","data","overrideAccess","update","newBlobToken","sourceTokenForTransfer","process","env","BLOB_READ_WRITE_TOKEN","shouldTransferToNewBlobToken","targetToken","targetAccess","blobs","limit","prefix","token","summary","deleteFromSource","sourceAccessHint","pollSecret","taskId","JSON","stringify","pathname","message","phase","e","logger","err"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/endpoints/paths/admin-settings.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { list } from '@vercel/blob'\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { transferBackupBlobsToToken } from '../../core/backupBlobTransfer'\nimport {\n BACKUP_SETTINGS_SLUG,\n getResolvedCronBackupSettings,\n normalizeSkipMongoCollections,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n toPayloadSkipRows,\n} from '../../core/backupSettings'\nimport { validateBackupBlobToken } from '../../core/blobTokenValidate'\nimport { getBackupStorageKind, isS3Configured, loadS3Config } from '../../core/storage'\nimport {\n completeBackupTask,\n createBackupTask,\n failBackupTask,\n updateBackupTask,\n} from '../../core/taskProgress'\nimport { describeCronSchedule, readVercelBackupCronFromRepo } from '../../core/vercelBackupCron'\nimport {\n maskBlobReadWriteToken,\n shouldPreserveBackupBlobTokenField,\n} from '../../utils/maskBlobToken'\nimport { readRequestJson, requireBackupAdmin } from '../shared'\n\nfunction clampBackupsToKeep(n: unknown): number {\n if (typeof n !== 'number' || !Number.isFinite(n)) {\n return 10\n }\n return Math.min(365, Math.max(1, Math.floor(n)))\n}\n\n/** Active backup target + a non-secret S3 summary for the admin UI. */\nfunction backupStorageSummary() {\n const kind = getBackupStorageKind()\n if (kind !== 's3' || !isS3Configured()) {\n return { kind }\n }\n const cfg = loadS3Config()\n return {\n kind,\n s3: {\n bucket: cfg.bucket,\n endpoint: cfg.endpoint,\n prefix: cfg.prefix || undefined,\n region: cfg.region,\n },\n }\n}\n\nfunction buildSettingsJson(\n stored: Awaited<ReturnType<typeof getResolvedCronBackupSettings>>,\n options: BackupPluginOptions,\n vercelCron: ReturnType<typeof readVercelBackupCronFromRepo>,\n humanDescription: null | string,\n transfer:\n | {\n deferred: boolean\n failed: number\n performed: boolean\n pollSecret?: string\n skipped: number\n taskId?: string\n total: number\n transferred: number\n }\n | undefined,\n) {\n const t = transfer ?? {\n deferred: false,\n failed: 0,\n performed: false,\n skipped: 0,\n total: 0,\n transferred: 0,\n }\n return {\n id: stored.id,\n backupBlobAccess: stored.backupBlobAccess,\n backupBlobAccessEffective: resolveBackupBlobAccess(stored),\n backupBlobTokenMasked: maskBlobReadWriteToken(stored.backupBlobReadWriteToken),\n backupsToKeep: stored.backupsToKeep,\n cron:\n vercelCron ?\n {\n configFileRelative: vercelCron.configFileRelative,\n humanDescription,\n path: vercelCron.path,\n schedule: vercelCron.schedule,\n }\n : null,\n effectiveBackupsToKeep:\n typeof options.backupsToKeep === 'number' ? options.backupsToKeep : stored.backupsToKeep,\n hasBackupBlobReadWriteToken: stored.backupBlobReadWriteToken.trim().length > 0,\n includeMediaForCron: stored.includeMediaForCron,\n pluginBackupsToKeepOverride: typeof options.backupsToKeep === 'number',\n skipMongoCollections: stored.skipMongoCollections,\n storage: backupStorageSummary(),\n transfer: t,\n }\n}\n\nexport function createAdminSettingsEndpoints(options: BackupPluginOptions): Endpoint[] {\n return [\n {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const stored = await getResolvedCronBackupSettings(payload)\n const vercelCron = readVercelBackupCronFromRepo()\n const humanDescription =\n vercelCron?.schedule != null ? describeCronSchedule(vercelCron.schedule) : null\n\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, undefined),\n )\n },\n method: 'get',\n path: '/backup-mongodb/admin/settings',\n },\n {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const body = (await readRequestJson(req)) as Record<string, unknown>\n const backupsToKeep = clampBackupsToKeep(body?.backupsToKeep)\n const skipMongoCollections = normalizeSkipMongoCollections(body?.skipMongoCollections)\n const includeMediaForCron = body?.includeMediaForCron === true\n const transferBackupBlobs =\n typeof body?.transferBackupBlobs === 'boolean' ? body.transferBackupBlobs : false\n const deleteBackupBlobsFromSource =\n typeof body?.deleteBackupBlobsFromSource === 'boolean' ?\n body.deleteBackupBlobsFromSource\n : false\n const requestedRaw =\n typeof body?.backupBlobReadWriteToken === 'string' ? body.backupBlobReadWriteToken : ''\n const requestedTrimmed = requestedRaw.trim()\n\n let stored = await getResolvedCronBackupSettings(payload)\n const previousAccess = resolveBackupBlobAccess(stored)\n const previousBackupBlobToken = stored.backupBlobReadWriteToken.trim()\n const hadStoredToken = previousBackupBlobToken.length > 0\n const preserveTokenField = shouldPreserveBackupBlobTokenField(\n requestedTrimmed,\n hadStoredToken,\n )\n const tokenForDb = preserveTokenField ? previousBackupBlobToken : requestedTrimmed\n\n // Access detection: re-probe when the token actually changes; preserve existing otherwise;\n // clear when the override is removed (falls back to heuristic on the default env token).\n // Skip Vercel validation when the active target is S3.\n let backupBlobAccessForDb: 'private' | 'public' | null = stored.backupBlobAccess\n if (!preserveTokenField && getBackupStorageKind() !== 's3') {\n if (tokenForDb.length === 0) {\n backupBlobAccessForDb = null\n } else {\n const validation = await validateBackupBlobToken(tokenForDb)\n if (!validation.ok) {\n return Response.json(\n { error: `Blob token rejected: ${validation.error ?? 'unknown error'}` },\n { status: 422 },\n )\n }\n backupBlobAccessForDb = validation.access ?? null\n }\n }\n\n if (!stored.id) {\n await payload.create({\n collection: BACKUP_SETTINGS_SLUG,\n data: {\n backupBlobAccess: backupBlobAccessForDb,\n backupBlobReadWriteToken: tokenForDb,\n backupsToKeep,\n includeMediaForCron,\n skipMongoCollections: toPayloadSkipRows(skipMongoCollections),\n },\n overrideAccess: true,\n })\n } else {\n await payload.update({\n id: stored.id,\n collection: BACKUP_SETTINGS_SLUG,\n data: {\n backupBlobAccess: backupBlobAccessForDb,\n backupBlobReadWriteToken: tokenForDb,\n backupsToKeep,\n includeMediaForCron,\n skipMongoCollections: toPayloadSkipRows(skipMongoCollections),\n },\n overrideAccess: true,\n })\n }\n\n stored = await getResolvedCronBackupSettings(payload)\n if (!stored.id) {\n return Response.json({ error: 'Could not persist backup settings' }, { status: 500 })\n }\n\n const vercelCron = readVercelBackupCronFromRepo()\n const humanDescription =\n vercelCron?.schedule != null ? describeCronSchedule(vercelCron.schedule) : null\n\n // Blob transfer is a Vercel-specific operation (moving blobs between tokens).\n // Skip entirely when the active target is S3.\n if (getBackupStorageKind() === 's3' || !transferBackupBlobs) {\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: false,\n failed: 0,\n performed: false,\n skipped: 0,\n total: 0,\n transferred: 0,\n }),\n )\n }\n\n const newBlobToken = stored.backupBlobReadWriteToken.trim()\n // Read blobs from the *previous* store when rotating tokens; otherwise first-time setup\n // reads from BLOB_READ_WRITE_TOKEN (default Vercel store).\n const sourceTokenForTransfer =\n previousBackupBlobToken.length > 0 ?\n previousBackupBlobToken\n : (process.env.BLOB_READ_WRITE_TOKEN || '').trim()\n\n const shouldTransferToNewBlobToken =\n newBlobToken.length > 0 &&\n sourceTokenForTransfer.length > 0 &&\n newBlobToken !== sourceTokenForTransfer\n\n if (!shouldTransferToNewBlobToken) {\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: false,\n failed: 0,\n performed: false,\n skipped: 0,\n total: 0,\n transferred: 0,\n }),\n )\n }\n\n const targetToken = resolveBackupBlobToken(stored)\n const targetAccess = resolveBackupBlobAccess(stored)\n const { blobs } = await list({\n limit: 1000,\n prefix: 'backups/',\n token: sourceTokenForTransfer,\n })\n const total = blobs.length\n\n if (total === 0) {\n const summary = await transferBackupBlobsToToken(\n payload,\n sourceTokenForTransfer,\n targetToken,\n {\n deleteFromSource: deleteBackupBlobsFromSource,\n sourceAccessHint: previousAccess,\n targetAccess,\n },\n )\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: false,\n failed: summary.failed,\n performed: true,\n skipped: summary.skipped,\n total: summary.total,\n transferred: summary.transferred,\n }),\n )\n }\n\n const { pollSecret, taskId } = await createBackupTask(\n payload,\n 'blobTransfer',\n JSON.stringify({ failed: 0, pathname: '', total, transferred: 0 }),\n )\n\n after(\n (async () => {\n try {\n await updateBackupTask(payload, taskId, {\n message: JSON.stringify({ failed: 0, pathname: '', total, transferred: 0 }),\n status: 'running',\n })\n const summary = await transferBackupBlobsToToken(\n payload,\n sourceTokenForTransfer,\n targetToken,\n {\n deleteFromSource: deleteBackupBlobsFromSource,\n sourceAccessHint: previousAccess,\n targetAccess,\n taskId,\n },\n )\n await completeBackupTask(\n payload,\n taskId,\n JSON.stringify({\n failed: summary.failed,\n pathname: '',\n phase: 'done',\n total: summary.total,\n transferred: summary.transferred,\n }),\n )\n } catch (e) {\n await failBackupTask(payload, taskId, e)\n payload.logger.error(\n { err: e, taskId },\n '[backup-settings] Blob transfer task failed',\n )\n }\n })(),\n )\n\n return Response.json(\n buildSettingsJson(stored, options, vercelCron, humanDescription, {\n deferred: true,\n failed: 0,\n performed: true,\n pollSecret,\n skipped: 0,\n taskId,\n total,\n transferred: 0,\n }),\n )\n },\n method: 'patch',\n path: '/backup-mongodb/admin/settings',\n },\n ]\n}\n"],"names":["list","after","transferBackupBlobsToToken","BACKUP_SETTINGS_SLUG","getResolvedCronBackupSettings","normalizeSkipMongoCollections","resolveBackupBlobAccess","resolveBackupBlobToken","toPayloadSkipRows","validateBackupBlobToken","getBackupStorageKind","isS3Configured","loadS3Config","completeBackupTask","createBackupTask","failBackupTask","updateBackupTask","describeCronSchedule","readVercelBackupCronFromRepo","maskBlobReadWriteToken","shouldPreserveBackupBlobTokenField","readRequestJson","requireBackupAdmin","clampBackupsToKeep","n","Number","isFinite","Math","min","max","floor","backupStorageSummary","kind","cfg","s3","bucket","endpoint","prefix","undefined","region","buildSettingsJson","stored","options","vercelCron","humanDescription","transfer","t","deferred","failed","performed","skipped","total","transferred","id","backupBlobAccess","backupBlobAccessEffective","backupBlobTokenMasked","backupBlobReadWriteToken","backupsToKeep","cron","configFileRelative","path","schedule","effectiveBackupsToKeep","hasBackupBlobReadWriteToken","trim","length","includeMediaForCron","pluginBackupsToKeepOverride","skipMongoCollections","storage","createAdminSettingsEndpoints","handler","req","auth","Response","payload","json","method","body","transferBackupBlobs","deleteBackupBlobsFromSource","requestedRaw","requestedTrimmed","previousAccess","previousBackupBlobToken","hadStoredToken","preserveTokenField","tokenForDb","backupBlobAccessForDb","validation","ok","error","status","access","create","collection","data","overrideAccess","update","newBlobToken","sourceTokenForTransfer","process","env","BLOB_READ_WRITE_TOKEN","shouldTransferToNewBlobToken","targetToken","targetAccess","blobs","limit","token","summary","deleteFromSource","sourceAccessHint","pollSecret","taskId","JSON","stringify","pathname","message","phase","e","logger","err"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { listBackups, resolveBackupListToken } from '../../core/backup';
2
+ import { isBackupStorageConfigured } from '../../core/storage';
2
3
  import { requireCronBearer } from '../shared';
3
4
  export function createCronListEndpoint() {
4
5
  return {
@@ -9,7 +10,7 @@ export function createCronListEndpoint() {
9
10
  }
10
11
  const { payload } = req;
11
12
  const backupBlobToken = await resolveBackupListToken(payload);
12
- if (!backupBlobToken) {
13
+ if (!isBackupStorageConfigured(backupBlobToken)) {
13
14
  return new Response('Service unavailable', {
14
15
  status: 503
15
16
  });
@@ -1 +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"}
1
+ {"version":3,"sources":["../../../src/endpoints/paths/cron-list.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { listBackups, resolveBackupListToken } from '../../core/backup'\nimport { isBackupStorageConfigured } from '../../core/storage'\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 (!isBackupStorageConfigured(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","isBackupStorageConfigured","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,yBAAyB,QAAQ,qBAAoB;AAC9D,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,MAAMR,uBAAuBO;YACrD,IAAI,CAACN,0BAA0BO,kBAAkB;gBAC/C,OAAO,IAAIC,SAAS,uBAAuB;oBAAEC,QAAQ;gBAAI;YAC3D;YACAH,QAAQI,MAAM,CAACC,IAAI,CAAC;YACpB,MAAMC,QAAQ,MAAMd,YAAYQ,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"}
@@ -1,5 +1,7 @@
1
1
  import { getResolvedCronBackupSettings, resolveBackupArchiveRead, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
2
2
  import { restoreBackup } from '../../core/restore';
3
+ import { buildRestoreWarnings, formatRestoreSummary, restoreTaskStatus } from '../../core/restoreResult';
4
+ import { getBackupStorageKind, isBackupStorageConfigured } from '../../core/storage';
3
5
  import { readRequestJson, requireCronBearer } from '../shared';
4
6
  export function createCronRestoreEndpoint() {
5
7
  return {
@@ -12,7 +14,7 @@ export function createCronRestoreEndpoint() {
12
14
  const settings = await getResolvedCronBackupSettings(payload);
13
15
  const blobToken = resolveBackupBlobToken(settings);
14
16
  const blobAccess = resolveBackupBlobAccess(settings);
15
- if (!blobToken) {
17
+ if (!isBackupStorageConfigured(blobToken)) {
16
18
  return new Response('Service unavailable', {
17
19
  status: 503
18
20
  });
@@ -31,24 +33,32 @@ export function createCronRestoreEndpoint() {
31
33
  });
32
34
  }
33
35
  const backupRead = resolveBackupArchiveRead(settings, pathname);
34
- if (blobAccess === 'private' && !backupRead) {
36
+ if (getBackupStorageKind() === 'vercel-blob' && blobAccess === 'private' && !backupRead) {
35
37
  return new Response('Missing pathname (required for dedicated backup blob store)', {
36
38
  status: 400
37
39
  });
38
40
  }
41
+ const archivePathname = typeof pathname === 'string' && pathname.startsWith('backups/') ? pathname : undefined;
39
42
  payload.logger.info({
40
43
  url
41
44
  }, '[backup-endpoint] Restore request accepted');
42
- await restoreBackup(payload, url, [], false, undefined, {
45
+ const result = await restoreBackup(payload, url, [], false, undefined, {
46
+ archivePathname,
43
47
  backupRead: backupRead ?? undefined,
44
48
  blobAccess,
45
49
  blobToken
46
50
  });
51
+ const warnings = buildRestoreWarnings(result);
52
+ const status = restoreTaskStatus(result);
47
53
  payload.logger.info({
54
+ result,
55
+ status,
48
56
  url
49
57
  }, '[backup-endpoint] Restore request finished');
50
58
  return Response.json({
51
- message: 'Backup restore finished'
59
+ message: formatRestoreSummary(result),
60
+ status,
61
+ warnings
52
62
  }, {
53
63
  status: 202
54
64
  });
@@ -1 +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"}
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 {\n buildRestoreWarnings,\n formatRestoreSummary,\n restoreTaskStatus,\n} from '../../core/restoreResult'\nimport { getBackupStorageKind, isBackupStorageConfigured } from '../../core/storage'\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 (!isBackupStorageConfigured(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 (getBackupStorageKind() === 'vercel-blob' && blobAccess === 'private' && !backupRead) {\n return new Response('Missing pathname (required for dedicated backup blob store)', {\n status: 400,\n })\n }\n\n const archivePathname =\n typeof pathname === 'string' && pathname.startsWith('backups/') ? pathname : undefined\n\n payload.logger.info({ url }, '[backup-endpoint] Restore request accepted')\n const result = await restoreBackup(payload, url, [], false, undefined, {\n archivePathname,\n backupRead: backupRead ?? undefined,\n blobAccess,\n blobToken,\n })\n const warnings = buildRestoreWarnings(result)\n const status = restoreTaskStatus(result)\n payload.logger.info({ result, status, url }, '[backup-endpoint] Restore request finished')\n return Response.json(\n {\n message: formatRestoreSummary(result),\n status,\n warnings,\n },\n { status: 202 },\n )\n },\n method: 'post',\n path: '/backup-mongodb/cron/restore',\n }\n}\n"],"names":["getResolvedCronBackupSettings","resolveBackupArchiveRead","resolveBackupBlobAccess","resolveBackupBlobToken","restoreBackup","buildRestoreWarnings","formatRestoreSummary","restoreTaskStatus","getBackupStorageKind","isBackupStorageConfigured","readRequestJson","requireCronBearer","createCronRestoreEndpoint","handler","req","cronErr","payload","settings","blobToken","blobAccess","Response","status","pathname","url","URL","backupRead","archivePathname","startsWith","undefined","logger","info","result","warnings","json","message","method","path"],"mappings":"AAEA,SACEA,6BAA6B,EAC7BC,wBAAwB,EACxBC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SACEC,oBAAoB,EACpBC,oBAAoB,EACpBC,iBAAiB,QACZ,2BAA0B;AACjC,SAASC,oBAAoB,EAAEC,yBAAyB,QAAQ,qBAAoB;AACpF,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,MAAMjB,8BAA8BgB;YACrD,MAAME,YAAYf,uBAAuBc;YACzC,MAAME,aAAajB,wBAAwBe;YAC3C,IAAI,CAACR,0BAA0BS,YAAY;gBACzC,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,aAAaxB,yBAAyBgB,UAAUK;YACtD,IAAId,2BAA2B,iBAAiBW,eAAe,aAAa,CAACM,YAAY;gBACvF,OAAO,IAAIL,SAAS,+DAA+D;oBACjFC,QAAQ;gBACV;YACF;YAEA,MAAMK,kBACJ,OAAOJ,aAAa,YAAYA,SAASK,UAAU,CAAC,cAAcL,WAAWM;YAE/EZ,QAAQa,MAAM,CAACC,IAAI,CAAC;gBAAEP;YAAI,GAAG;YAC7B,MAAMQ,SAAS,MAAM3B,cAAcY,SAASO,KAAK,EAAE,EAAE,OAAOK,WAAW;gBACrEF;gBACAD,YAAYA,cAAcG;gBAC1BT;gBACAD;YACF;YACA,MAAMc,WAAW3B,qBAAqB0B;YACtC,MAAMV,SAASd,kBAAkBwB;YACjCf,QAAQa,MAAM,CAACC,IAAI,CAAC;gBAAEC;gBAAQV;gBAAQE;YAAI,GAAG;YAC7C,OAAOH,SAASa,IAAI,CAClB;gBACEC,SAAS5B,qBAAqByB;gBAC9BV;gBACAW;YACF,GACA;gBAAEX,QAAQ;YAAI;QAElB;QACAc,QAAQ;QACRC,MAAM;IACR;AACF"}
@@ -1,6 +1,7 @@
1
1
  import { after } from 'next/server';
2
2
  import { createBackup } from '../../core/backup';
3
3
  import { getResolvedCronBackupSettings, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
4
+ import { isBackupStorageConfigured } from '../../core/storage';
4
5
  import { requireCronBearer } from '../shared';
5
6
  export function createCronRunEndpoint(options) {
6
7
  return {
@@ -13,7 +14,7 @@ export function createCronRunEndpoint(options) {
13
14
  const settings = await getResolvedCronBackupSettings(payload);
14
15
  const blobToken = resolveBackupBlobToken(settings);
15
16
  const blobAccess = resolveBackupBlobAccess(settings);
16
- if (!blobToken) {
17
+ if (!isBackupStorageConfigured(blobToken)) {
17
18
  return new Response('Service unavailable', {
18
19
  status: 503
19
20
  });
@@ -1 +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"}
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 { isBackupStorageConfigured } from '../../core/storage'\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 (!isBackupStorageConfigured(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","isBackupStorageConfigured","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,yBAAyB,QAAQ,qBAAoB;AAC9D,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,MAAMX,8BAA8BU;YACrD,MAAME,YAAYV,uBAAuBS;YACzC,MAAME,aAAaZ,wBAAwBU;YAC3C,IAAI,CAACR,0BAA0BS,YAAY;gBACzC,OAAO,IAAIE,SAAS,uBAAuB;oBAAEC,QAAQ;gBAAI;YAC3D;YACAL,QAAQM,MAAM,CAACC,IAAI,CAAC;YACpBnB,MACEC,aAAaW,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"}
@@ -7,7 +7,6 @@ export declare function readRequestJson(req: PayloadRequest): Promise<unknown>;
7
7
  * with `Unexpected token ... is not valid JSON`.
8
8
  */
9
9
  export declare function jsonError(message: string, status: number): Response;
10
- export declare function requireBlobEnv(): null | Response;
11
10
  export declare function requireCronBearer(req: PayloadRequest): null | Response;
12
11
  export declare function getAuthorizedBackupAdmin(req: PayloadRequest, options: BackupPluginOptions): Promise<null | Record<string, unknown>>;
13
12
  export declare function requireBackupAdmin(req: PayloadRequest, options: BackupPluginOptions): Promise<Record<string, unknown> | Response>;
@@ -13,12 +13,6 @@ export async function readRequestJson(req) {
13
13
  status
14
14
  });
15
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
16
  export function requireCronBearer(req) {
23
17
  // Security gate for cron/external backup routes: only a matching CRON_SECRET bearer may pass.
24
18
  const authHeader = req.headers.get('authorization');
@@ -1 +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"}
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 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 =\n fromRequest ? fromRequest : (\n ((await req.payload.auth({ headers: req.headers }))?.user as\n | null\n | Record<string, unknown>\n | undefined)\n )\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","requireCronBearer","authHeader","headers","get","process","env","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,kBAAkBP,GAAmB;IACnD,8FAA8F;IAC9F,MAAMQ,aAAaR,IAAIS,OAAO,CAACC,GAAG,CAAC;IACnC,IAAIF,eAAe,CAAC,OAAO,EAAEG,QAAQC,GAAG,CAACC,WAAW,EAAE,EAAE;QACtD,OAAO,IAAIR,SAAS,gBAAgB;YAAED,QAAQ;QAAI;IACpD;IACA,OAAO;AACT;AAEA,OAAO,eAAeU,yBACpBd,GAAmB,EACnBe,OAA4B;IAE5B,MAAMC,cAAc,AAAChB,IAAmEiB,IAAI;IAC5F,MAAMC,WACJF,cAAcA,cACV,CAAA,MAAMhB,IAAImB,OAAO,CAACC,IAAI,CAAC;QAAEX,SAAST,IAAIS,OAAO;IAAC,EAAC,GAAIQ;IAKzD,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,OAAOnB,wBAAwBmB,QAAQA,OAAO;AAChD;AAEA,OAAO,eAAeK,mBACpBtB,GAAmB,EACnBe,OAA4B;IAE5B,2FAA2F;IAC3F,MAAME,OAAO,MAAMH,yBAAyBd,KAAKe;IACjD,IAAI,CAACE,MAAM;QACT,OAAOf,UAAU,gBAAgB;IACnC;IACA,OAAOe;AACT"}
package/dist/index.d.ts CHANGED
@@ -4,11 +4,13 @@ export { getBackupSourcePreviewForManual } from './core/backupSourcePreview';
4
4
  export type { BackupSourcePreviewResponse } from './core/backupSourcePreview';
5
5
  export { getDb } from './core/db';
6
6
  export type { MongoDb } from './core/db';
7
- export { restoreBackup, restoreSeedMedia } from './core/restore';
8
- export type { RestoreBackupOptions } from './core/restore';
7
+ export { restoreBackup } from './core/restore';
8
+ export type { RestoreBackupOptions, RestoreBackupResult } from './core/restore';
9
9
  export { buildCollectionPreviewGroups, buildRestorePreviewGroups, getRestorePreviewForAdminRestore, loadRestoreBackupIndex, navVisibleCollectionOrderIndex, } from './core/restorePreview';
10
10
  export type { RestorePreviewAdminHiddenReason, RestorePreviewFileKind, RestorePreviewGroup, RestorePreviewResponse, } from './core/restorePreview';
11
- export { completeBackupTask, createBackupTask, failBackupTask, getBackupTask, pollSecretsMatch, stripPollSecretForClient, updateBackupTask, } from './core/taskProgress';
11
+ export { buildRestoreWarnings, formatRestoreSummary, restoreTaskStatus } from './core/restoreResult';
12
+ export type { RestoreArchiveKind, RestoreCollectionOutcome, RestoreMediaOutcome, } from './core/restoreResult';
13
+ export { completeBackupTask, completeRestoreBackupTask, createBackupTask, failBackupTask, getBackupTask, pollSecretsMatch, stripPollSecretForClient, updateBackupTask, } from './core/taskProgress';
12
14
  export type { BackupTaskKind, BackupTaskProgress, BackupTaskStatus, BackupTaskWithPollSecret, } from './core/taskProgress';
13
15
  export { backupMongodbPlugin } from './plugin';
14
16
  export { backupPluginPublicApiPaths } from './publicApiPaths';
package/dist/index.js CHANGED
@@ -2,9 +2,10 @@ export { createTarGzip, resolveTarGzip } from './core/archive';
2
2
  export { createBackup, createMediaBackupFile, listBackups, resolveBackupListToken } from './core/backup';
3
3
  export { getBackupSourcePreviewForManual } from './core/backupSourcePreview';
4
4
  export { getDb } from './core/db';
5
- export { restoreBackup, restoreSeedMedia } from './core/restore';
5
+ export { restoreBackup } from './core/restore';
6
6
  export { buildCollectionPreviewGroups, buildRestorePreviewGroups, getRestorePreviewForAdminRestore, loadRestoreBackupIndex, navVisibleCollectionOrderIndex } from './core/restorePreview';
7
- export { completeBackupTask, createBackupTask, failBackupTask, getBackupTask, pollSecretsMatch, stripPollSecretForClient, updateBackupTask } from './core/taskProgress';
7
+ export { buildRestoreWarnings, formatRestoreSummary, restoreTaskStatus } from './core/restoreResult';
8
+ export { completeBackupTask, completeRestoreBackupTask, createBackupTask, failBackupTask, getBackupTask, pollSecretsMatch, stripPollSecretForClient, updateBackupTask } from './core/taskProgress';
8
9
  export { backupMongodbPlugin } from './plugin';
9
10
  export { backupPluginPublicApiPaths } from './publicApiPaths';
10
11
  export { createBlobName, getBackupSortTimeMs, transformBlobName } from './utils/blobName';
package/dist/index.js.map CHANGED
@@ -1 +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"}
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 } from './core/restore'\nexport type { RestoreBackupOptions, RestoreBackupResult } 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 { buildRestoreWarnings, formatRestoreSummary, restoreTaskStatus } from './core/restoreResult'\nexport type {\n RestoreArchiveKind,\n RestoreCollectionOutcome,\n RestoreMediaOutcome,\n} from './core/restoreResult'\nexport {\n completeBackupTask,\n completeRestoreBackupTask,\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","buildCollectionPreviewGroups","buildRestorePreviewGroups","getRestorePreviewForAdminRestore","loadRestoreBackupIndex","navVisibleCollectionOrderIndex","buildRestoreWarnings","formatRestoreSummary","restoreTaskStatus","completeBackupTask","completeRestoreBackupTask","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,QAAQ,iBAAgB;AAE9C,SACEC,4BAA4B,EAC5BC,yBAAyB,EACzBC,gCAAgC,EAChCC,sBAAsB,EACtBC,8BAA8B,QACzB,wBAAuB;AAO9B,SAASC,oBAAoB,EAAEC,oBAAoB,EAAEC,iBAAiB,QAAQ,uBAAsB;AAMpG,SACEC,kBAAkB,EAClBC,yBAAyB,EACzBC,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"}
@@ -1 +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"}
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\n .collection('backup-tasks')\n .createIndex({ updatedAt: 1 }, { background: true, expireAfterSeconds: 1800 })\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,GACHlB,UAAU,CAAC,gBACXsB,WAAW,CAAC;gCAAEC,WAAW;4BAAE,GAAG;gCAAEC,YAAY;gCAAMC,oBAAoB;4BAAK;wBAChF;oBACF;gBACF,EAAE,OAAM;oBACN5B,QAAQiB,MAAM,CAACG,IAAI,CAAC;gBACtB;YACF;QACF;IACF;AACF,EAAC"}
@@ -9,7 +9,6 @@ export declare const backupPluginPublicApiPaths: {
9
9
  readonly adminManual: "/api/backup-mongodb/admin/manual";
10
10
  readonly adminRestore: "/api/backup-mongodb/admin/restore";
11
11
  readonly adminRestorePreview: "/api/backup-mongodb/admin/restore-preview";
12
- readonly adminSeed: "/api/backup-mongodb/admin/seed";
13
12
  readonly adminSettings: "/api/backup-mongodb/admin/settings";
14
13
  readonly adminTask: (id: string) => string;
15
14
  readonly adminValidateBlobToken: "/api/backup-mongodb/admin/validate-blob-token";
@@ -8,7 +8,6 @@
8
8
  adminManual: '/api/backup-mongodb/admin/manual',
9
9
  adminRestore: '/api/backup-mongodb/admin/restore',
10
10
  adminRestorePreview: '/api/backup-mongodb/admin/restore-preview',
11
- adminSeed: '/api/backup-mongodb/admin/seed',
12
11
  adminSettings: '/api/backup-mongodb/admin/settings',
13
12
  adminTask: (id)=>`/api/backup-mongodb/admin/task/${encodeURIComponent(id)}`,
14
13
  adminValidateBlobToken: '/api/backup-mongodb/admin/validate-blob-token',
@@ -1 +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"}
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 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","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,eAAe;IACfC,WAAW,CAACC,KAAe,CAAC,+BAA+B,EAAEC,mBAAmBD,KAAK;IACrFE,wBAAwB;IACxBC,MAAM;IACNC,aAAa;IACbC,MAAM;AACR,EAAU"}
package/dist/types.d.ts CHANGED
@@ -13,9 +13,4 @@ export type BackupPluginOptions = {
13
13
  backupsToKeep?: number;
14
14
  /** Enable/disable the plugin entirely. Default: true */
15
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
16
  };
package/dist/types.js.map CHANGED
@@ -1 +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"}
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"],"names":[],"mappings":"AAAA,WAeC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/blobName.ts"],"sourcesContent":["const SEPARATOR = '---'\n\n/**\n * Last segment is `{collectionCount}-{timestampMs}` (new) or legacy `{timestampMs}` only.\n * A hyphenated tail that does not pass {@link isPlausibleNewFormatTail} is treated as legacy\n * (whole `last` string, no collection count) so older blobs are never mis-parsed.\n */\nconst NEW_NAME_TAIL = /^(\\d+)-(\\d{10,})$/\n\nconst NEW_TAIL_MAX_COLLECTIONS = 100_000\n\n/** Max length for a user-supplied manual backup label (after sanitization). */\nexport const BACKUP_LABEL_MAX_LENGTH = 64\n\nfunction isPlausibleNewFormatTail(collectionCount: number, timestampMs: number): boolean {\n return (\n Number.isFinite(collectionCount) &&\n Number.isFinite(timestampMs) &&\n collectionCount >= 0 &&\n collectionCount <= NEW_TAIL_MAX_COLLECTIONS &&\n timestampMs > 0 &&\n timestampMs < 1e15\n )\n}\n\n/**\n * Normalize a user-supplied manual backup label so it is safe to embed as a\n * blob-name segment. Trims, collapses whitespace, removes characters that would\n * confuse the separator, and enforces {@link BACKUP_LABEL_MAX_LENGTH}.\n * Returns an empty string when nothing usable remains.\n */\nexport function sanitizeBackupLabel(raw: unknown): string {\n if (typeof raw !== 'string') {return ''}\n const collapsed = raw.replace(/\\s+/g, ' ').trim()\n if (!collapsed) {return ''}\n // The blob-name separator is three hyphens; collapse any hyphen run in the\n // label to a single hyphen so round-trip parsing stays unambiguous.\n const deHyphen = collapsed.replace(/-{2,}/g, '-')\n return deHyphen.slice(0, BACKUP_LABEL_MAX_LENGTH).trim()\n}\n\nexport interface TransformBlobNameResult {\n collectionCount?: number\n date: string\n dbName: string\n fileType: 'json' | 'na' | 'tar.gz'\n hostname: string\n label?: string\n type: string\n}\n\nexport function transformBlobName(blobName: string): TransformBlobNameResult {\n const fileType: 'json' | 'na' | 'tar.gz' = blobName.endsWith('json')\n ? 'json'\n : blobName.endsWith('tar.gz')\n ? 'tar.gz'\n : 'na'\n const [type = '', dbName = '', hostname = '', last = '', labelEncoded = ''] = blobName\n .replace(/\\.(?:json|tar\\.gz)$/, '')\n .replace(/^backups\\//, '')\n .split(SEPARATOR)\n\n let label: string | undefined\n if (labelEncoded) {\n try {\n const decoded = decodeURIComponent(labelEncoded)\n if (decoded) {label = decoded}\n } catch {\n label = labelEncoded\n }\n }\n\n const m = NEW_NAME_TAIL.exec(last)\n if (m) {\n const collectionCount = Number(m[1])\n const timestampMs = Number(m[2])\n if (isPlausibleNewFormatTail(collectionCount, timestampMs)) {\n return {\n type,\n collectionCount,\n date: m[2],\n dbName: decodeURIComponent(dbName),\n fileType,\n hostname: decodeURIComponent(hostname),\n label,\n }\n }\n }\n\n return {\n type,\n date: last,\n dbName: decodeURIComponent(dbName),\n fileType,\n hostname: decodeURIComponent(hostname),\n label,\n }\n}\n\n/**\n * Backup blob pathname segment (without `backups/` prefix or extension).\n * New format: `{collectionCount}-{timestampMs}` encodes included collection count + backup time.\n * When a non-empty `label` is supplied it is appended as a separate URL-encoded segment\n * (`---{encodedLabel}`) so older parsers that only read the first 4 segments still work.\n */\nexport function createBlobName(\n type: string,\n dbName: string,\n hostname: string,\n collectionCount: number,\n timestampMs: number,\n fileType: 'json' | 'tar.gz',\n label?: string,\n): string {\n const tail = `${Math.max(0, Math.floor(collectionCount))}-${Math.floor(timestampMs)}`\n const sanitizedLabel = sanitizeBackupLabel(label)\n const labelSegment = sanitizedLabel ? `${SEPARATOR}${encodeURIComponent(sanitizedLabel)}` : ''\n return `${type}${SEPARATOR}${encodeURIComponent(dbName)}${SEPARATOR}${encodeURIComponent(hostname)}${SEPARATOR}${tail}${labelSegment}.${fileType}`\n}\n\n/**\n * Prefer timestamp encoded in filename (stable across blob store migration); else blob upload time.\n * Legacy tails are digits-only epoch values; if {@link TransformBlobNameResult.collectionCount}\n * is absent, values below 1e12 are treated as **Unix seconds** (common older convention), else ms.\n */\nexport function getBackupSortTimeMs(parsed: TransformBlobNameResult, uploadedAt: Date): number {\n if (!parsed.date || !/^\\d+$/.test(parsed.date)) {\n return uploadedAt.getTime()\n }\n const n = Number(parsed.date)\n if (!Number.isFinite(n)) {\n return uploadedAt.getTime()\n }\n const isLegacyTimestampOnly = parsed.collectionCount === undefined\n if (isLegacyTimestampOnly && n < 1e12) {\n return n * 1000\n }\n return n\n}\n"],"names":["SEPARATOR","NEW_NAME_TAIL","NEW_TAIL_MAX_COLLECTIONS","BACKUP_LABEL_MAX_LENGTH","isPlausibleNewFormatTail","collectionCount","timestampMs","Number","isFinite","sanitizeBackupLabel","raw","collapsed","replace","trim","deHyphen","slice","transformBlobName","blobName","fileType","endsWith","type","dbName","hostname","last","labelEncoded","split","label","decoded","decodeURIComponent","m","exec","date","createBlobName","tail","Math","max","floor","sanitizedLabel","labelSegment","encodeURIComponent","getBackupSortTimeMs","parsed","uploadedAt","test","getTime","n","isLegacyTimestampOnly","undefined"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utils/blobName.ts"],"sourcesContent":["const SEPARATOR = '---'\n\n/**\n * Last segment is `{collectionCount}-{timestampMs}` (new) or legacy `{timestampMs}` only.\n * A hyphenated tail that does not pass {@link isPlausibleNewFormatTail} is treated as legacy\n * (whole `last` string, no collection count) so older blobs are never mis-parsed.\n */\nconst NEW_NAME_TAIL = /^(\\d+)-(\\d{10,})$/\n\nconst NEW_TAIL_MAX_COLLECTIONS = 100_000\n\n/** Max length for a user-supplied manual backup label (after sanitization). */\nexport const BACKUP_LABEL_MAX_LENGTH = 64\n\nfunction isPlausibleNewFormatTail(collectionCount: number, timestampMs: number): boolean {\n return (\n Number.isFinite(collectionCount) &&\n Number.isFinite(timestampMs) &&\n collectionCount >= 0 &&\n collectionCount <= NEW_TAIL_MAX_COLLECTIONS &&\n timestampMs > 0 &&\n timestampMs < 1e15\n )\n}\n\n/**\n * Normalize a user-supplied manual backup label so it is safe to embed as a\n * blob-name segment. Trims, collapses whitespace, removes characters that would\n * confuse the separator, and enforces {@link BACKUP_LABEL_MAX_LENGTH}.\n * Returns an empty string when nothing usable remains.\n */\nexport function sanitizeBackupLabel(raw: unknown): string {\n if (typeof raw !== 'string') {\n return ''\n }\n const collapsed = raw.replace(/\\s+/g, ' ').trim()\n if (!collapsed) {\n return ''\n }\n // The blob-name separator is three hyphens; collapse any hyphen run in the\n // label to a single hyphen so round-trip parsing stays unambiguous.\n const deHyphen = collapsed.replace(/-{2,}/g, '-')\n return deHyphen.slice(0, BACKUP_LABEL_MAX_LENGTH).trim()\n}\n\nexport interface TransformBlobNameResult {\n collectionCount?: number\n date: string\n dbName: string\n fileType: 'json' | 'na' | 'tar.gz'\n hostname: string\n label?: string\n type: string\n}\n\nexport function transformBlobName(blobName: string): TransformBlobNameResult {\n const fileType: 'json' | 'na' | 'tar.gz' =\n blobName.endsWith('json') ? 'json'\n : blobName.endsWith('tar.gz') ? 'tar.gz'\n : 'na'\n const [type = '', dbName = '', hostname = '', last = '', labelEncoded = ''] = blobName\n .replace(/\\.(?:json|tar\\.gz)$/, '')\n .replace(/^backups\\//, '')\n .split(SEPARATOR)\n\n let label: string | undefined\n if (labelEncoded) {\n try {\n const decoded = decodeURIComponent(labelEncoded)\n if (decoded) {\n label = decoded\n }\n } catch {\n label = labelEncoded\n }\n }\n\n const m = NEW_NAME_TAIL.exec(last)\n if (m) {\n const collectionCount = Number(m[1])\n const timestampMs = Number(m[2])\n if (isPlausibleNewFormatTail(collectionCount, timestampMs)) {\n return {\n type,\n collectionCount,\n date: m[2],\n dbName: decodeURIComponent(dbName),\n fileType,\n hostname: decodeURIComponent(hostname),\n label,\n }\n }\n }\n\n return {\n type,\n date: last,\n dbName: decodeURIComponent(dbName),\n fileType,\n hostname: decodeURIComponent(hostname),\n label,\n }\n}\n\n/**\n * Backup blob pathname segment (without `backups/` prefix or extension).\n * New format: `{collectionCount}-{timestampMs}` encodes included collection count + backup time.\n * When a non-empty `label` is supplied it is appended as a separate URL-encoded segment\n * (`---{encodedLabel}`) so older parsers that only read the first 4 segments still work.\n */\nexport function createBlobName(\n type: string,\n dbName: string,\n hostname: string,\n collectionCount: number,\n timestampMs: number,\n fileType: 'json' | 'tar.gz',\n label?: string,\n): string {\n const tail = `${Math.max(0, Math.floor(collectionCount))}-${Math.floor(timestampMs)}`\n const sanitizedLabel = sanitizeBackupLabel(label)\n const labelSegment = sanitizedLabel ? `${SEPARATOR}${encodeURIComponent(sanitizedLabel)}` : ''\n return `${type}${SEPARATOR}${encodeURIComponent(dbName)}${SEPARATOR}${encodeURIComponent(hostname)}${SEPARATOR}${tail}${labelSegment}.${fileType}`\n}\n\n/**\n * Prefer timestamp encoded in filename (stable across blob store migration); else blob upload time.\n * Legacy tails are digits-only epoch values; if {@link TransformBlobNameResult.collectionCount}\n * is absent, values below 1e12 are treated as **Unix seconds** (common older convention), else ms.\n */\nexport function getBackupSortTimeMs(parsed: TransformBlobNameResult, uploadedAt: Date): number {\n if (!parsed.date || !/^\\d+$/.test(parsed.date)) {\n return uploadedAt.getTime()\n }\n const n = Number(parsed.date)\n if (!Number.isFinite(n)) {\n return uploadedAt.getTime()\n }\n const isLegacyTimestampOnly = parsed.collectionCount === undefined\n if (isLegacyTimestampOnly && n < 1e12) {\n return n * 1000\n }\n return n\n}\n"],"names":["SEPARATOR","NEW_NAME_TAIL","NEW_TAIL_MAX_COLLECTIONS","BACKUP_LABEL_MAX_LENGTH","isPlausibleNewFormatTail","collectionCount","timestampMs","Number","isFinite","sanitizeBackupLabel","raw","collapsed","replace","trim","deHyphen","slice","transformBlobName","blobName","fileType","endsWith","type","dbName","hostname","last","labelEncoded","split","label","decoded","decodeURIComponent","m","exec","date","createBlobName","tail","Math","max","floor","sanitizedLabel","labelSegment","encodeURIComponent","getBackupSortTimeMs","parsed","uploadedAt","test","getTime","n","isLegacyTimestampOnly","undefined"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/dashboardRoleAccess.ts"],"sourcesContent":["/**\n * Resolves which users may see/use the backup dashboard when no custom `access`\n * option is passed to the plugin. Driven by the `PAYLOAD_BACKUP_ALLOWED_ROLES`\n * environment variable.\n *\n * Syntax:\n * PAYLOAD_BACKUP_ALLOWED_ROLES=admin,editor // any user whose roles[].slug matches\n * PAYLOAD_BACKUP_ALLOWED_ROLES=* // any authenticated user\n * (unset / empty) // backwards-compatible default:\n * // - roles present -> require `admin`\n * // - no roles field -> allow\n */\n\nexport const BACKUP_ALLOWED_ROLES_ENV = 'PAYLOAD_BACKUP_ALLOWED_ROLES'\n\n/** Parses the env var value into a normalized list of role slugs (lower-cased, trimmed). */\nexport function parseAllowedRolesEnv(raw: string | undefined): string[] {\n if (!raw) {return []}\n return raw\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter((s) => s.length > 0)\n}\n\nfunction extractRoleSlugs(user: Record<string, unknown>): null | string[] {\n const roles = user.roles as Array<{ slug?: unknown } | string> | undefined\n if (!Array.isArray(roles)) {return null}\n const slugs: string[] = []\n for (const entry of roles) {\n if (typeof entry === 'string') {\n slugs.push(entry.toLowerCase())\n } else if (entry && typeof entry === 'object' && typeof entry.slug === 'string') {\n slugs.push(entry.slug.toLowerCase())\n }\n }\n return slugs\n}\n\n/**\n * Returns true when the authenticated user is allowed to view the backup dashboard based\n * on the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var. Unauthenticated users always return false.\n *\n * Exported for unit testing and for advanced consumers that want to reuse the same check\n * (e.g. wrap it inside their own `access` option).\n */\nexport function isUserAllowedByEnvRoles(\n user: null | Record<string, unknown>,\n envValue: string | undefined = process.env[BACKUP_ALLOWED_ROLES_ENV],\n): boolean {\n if (!user) {return false}\n const allowed = parseAllowedRolesEnv(envValue)\n const userRoles = extractRoleSlugs(user)\n\n // Env var not set -> backwards-compatible default.\n if (allowed.length === 0) {\n if (userRoles === null || userRoles.length === 0) {return true}\n return userRoles.includes('admin')\n }\n\n // Wildcard -> any authenticated user.\n if (allowed.includes('*')) {return true}\n\n // Explicit allow-list requires the user to actually expose roles that match.\n if (!userRoles || userRoles.length === 0) {return false}\n return userRoles.some((slug) => allowed.includes(slug))\n}\n"],"names":["BACKUP_ALLOWED_ROLES_ENV","parseAllowedRolesEnv","raw","split","map","s","trim","toLowerCase","filter","length","extractRoleSlugs","user","roles","Array","isArray","slugs","entry","push","slug","isUserAllowedByEnvRoles","envValue","process","env","allowed","userRoles","includes","some"],"mappings":"AAAA;;;;;;;;;;;CAWC,GAED,OAAO,MAAMA,2BAA2B,+BAA8B;AAEtE,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,GAAuB;IAC1D,IAAI,CAACA,KAAK;QAAC,OAAO,EAAE;IAAA;IACpB,OAAOA,IACJC,KAAK,CAAC,KACNC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI,GAAGC,WAAW,IAC/BC,MAAM,CAAC,CAACH,IAAMA,EAAEI,MAAM,GAAG;AAC9B;AAEA,SAASC,iBAAiBC,IAA6B;IACrD,MAAMC,QAAQD,KAAKC,KAAK;IACxB,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QAAC,OAAO;IAAI;IACvC,MAAMG,QAAkB,EAAE;IAC1B,KAAK,MAAMC,SAASJ,MAAO;QACzB,IAAI,OAAOI,UAAU,UAAU;YAC7BD,MAAME,IAAI,CAACD,MAAMT,WAAW;QAC9B,OAAO,IAAIS,SAAS,OAAOA,UAAU,YAAY,OAAOA,MAAME,IAAI,KAAK,UAAU;YAC/EH,MAAME,IAAI,CAACD,MAAME,IAAI,CAACX,WAAW;QACnC;IACF;IACA,OAAOQ;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASI,wBACdR,IAAoC,EACpCS,WAA+BC,QAAQC,GAAG,CAACtB,yBAAyB;IAEpE,IAAI,CAACW,MAAM;QAAC,OAAO;IAAK;IACxB,MAAMY,UAAUtB,qBAAqBmB;IACrC,MAAMI,YAAYd,iBAAiBC;IAEnC,mDAAmD;IACnD,IAAIY,QAAQd,MAAM,KAAK,GAAG;QACxB,IAAIe,cAAc,QAAQA,UAAUf,MAAM,KAAK,GAAG;YAAC,OAAO;QAAI;QAC9D,OAAOe,UAAUC,QAAQ,CAAC;IAC5B;IAEA,sCAAsC;IACtC,IAAIF,QAAQE,QAAQ,CAAC,MAAM;QAAC,OAAO;IAAI;IAEvC,6EAA6E;IAC7E,IAAI,CAACD,aAAaA,UAAUf,MAAM,KAAK,GAAG;QAAC,OAAO;IAAK;IACvD,OAAOe,UAAUE,IAAI,CAAC,CAACR,OAASK,QAAQE,QAAQ,CAACP;AACnD"}
1
+ {"version":3,"sources":["../../src/utils/dashboardRoleAccess.ts"],"sourcesContent":["/**\n * Resolves which users may see/use the backup dashboard when no custom `access`\n * option is passed to the plugin. Driven by the `PAYLOAD_BACKUP_ALLOWED_ROLES`\n * environment variable.\n *\n * Syntax:\n * PAYLOAD_BACKUP_ALLOWED_ROLES=admin,editor // any user whose roles[].slug matches\n * PAYLOAD_BACKUP_ALLOWED_ROLES=* // any authenticated user\n * (unset / empty) // backwards-compatible default:\n * // - roles present -> require `admin`\n * // - no roles field -> allow\n */\n\nexport const BACKUP_ALLOWED_ROLES_ENV = 'PAYLOAD_BACKUP_ALLOWED_ROLES'\n\n/** Parses the env var value into a normalized list of role slugs (lower-cased, trimmed). */\nexport function parseAllowedRolesEnv(raw: string | undefined): string[] {\n if (!raw) {\n return []\n }\n return raw\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter((s) => s.length > 0)\n}\n\nfunction extractRoleSlugs(user: Record<string, unknown>): null | string[] {\n const roles = user.roles as Array<{ slug?: unknown } | string> | undefined\n if (!Array.isArray(roles)) {\n return null\n }\n const slugs: string[] = []\n for (const entry of roles) {\n if (typeof entry === 'string') {\n slugs.push(entry.toLowerCase())\n } else if (entry && typeof entry === 'object' && typeof entry.slug === 'string') {\n slugs.push(entry.slug.toLowerCase())\n }\n }\n return slugs\n}\n\n/**\n * Returns true when the authenticated user is allowed to view the backup dashboard based\n * on the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var. Unauthenticated users always return false.\n *\n * Exported for unit testing and for advanced consumers that want to reuse the same check\n * (e.g. wrap it inside their own `access` option).\n */\nexport function isUserAllowedByEnvRoles(\n user: null | Record<string, unknown>,\n envValue: string | undefined = process.env[BACKUP_ALLOWED_ROLES_ENV],\n): boolean {\n if (!user) {\n return false\n }\n const allowed = parseAllowedRolesEnv(envValue)\n const userRoles = extractRoleSlugs(user)\n\n // Env var not set -> backwards-compatible default.\n if (allowed.length === 0) {\n if (userRoles === null || userRoles.length === 0) {\n return true\n }\n return userRoles.includes('admin')\n }\n\n // Wildcard -> any authenticated user.\n if (allowed.includes('*')) {\n return true\n }\n\n // Explicit allow-list requires the user to actually expose roles that match.\n if (!userRoles || userRoles.length === 0) {\n return false\n }\n return userRoles.some((slug) => allowed.includes(slug))\n}\n"],"names":["BACKUP_ALLOWED_ROLES_ENV","parseAllowedRolesEnv","raw","split","map","s","trim","toLowerCase","filter","length","extractRoleSlugs","user","roles","Array","isArray","slugs","entry","push","slug","isUserAllowedByEnvRoles","envValue","process","env","allowed","userRoles","includes","some"],"mappings":"AAAA;;;;;;;;;;;CAWC,GAED,OAAO,MAAMA,2BAA2B,+BAA8B;AAEtE,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,GAAuB;IAC1D,IAAI,CAACA,KAAK;QACR,OAAO,EAAE;IACX;IACA,OAAOA,IACJC,KAAK,CAAC,KACNC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI,GAAGC,WAAW,IAC/BC,MAAM,CAAC,CAACH,IAAMA,EAAEI,MAAM,GAAG;AAC9B;AAEA,SAASC,iBAAiBC,IAA6B;IACrD,MAAMC,QAAQD,KAAKC,KAAK;IACxB,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QACzB,OAAO;IACT;IACA,MAAMG,QAAkB,EAAE;IAC1B,KAAK,MAAMC,SAASJ,MAAO;QACzB,IAAI,OAAOI,UAAU,UAAU;YAC7BD,MAAME,IAAI,CAACD,MAAMT,WAAW;QAC9B,OAAO,IAAIS,SAAS,OAAOA,UAAU,YAAY,OAAOA,MAAME,IAAI,KAAK,UAAU;YAC/EH,MAAME,IAAI,CAACD,MAAME,IAAI,CAACX,WAAW;QACnC;IACF;IACA,OAAOQ;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASI,wBACdR,IAAoC,EACpCS,WAA+BC,QAAQC,GAAG,CAACtB,yBAAyB;IAEpE,IAAI,CAACW,MAAM;QACT,OAAO;IACT;IACA,MAAMY,UAAUtB,qBAAqBmB;IACrC,MAAMI,YAAYd,iBAAiBC;IAEnC,mDAAmD;IACnD,IAAIY,QAAQd,MAAM,KAAK,GAAG;QACxB,IAAIe,cAAc,QAAQA,UAAUf,MAAM,KAAK,GAAG;YAChD,OAAO;QACT;QACA,OAAOe,UAAUC,QAAQ,CAAC;IAC5B;IAEA,sCAAsC;IACtC,IAAIF,QAAQE,QAAQ,CAAC,MAAM;QACzB,OAAO;IACT;IAEA,6EAA6E;IAC7E,IAAI,CAACD,aAAaA,UAAUf,MAAM,KAAK,GAAG;QACxC,OAAO;IACT;IACA,OAAOe,UAAUE,IAAI,CAAC,CAACR,OAASK,QAAQE,QAAQ,CAACP;AACnD"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/dialogBackdrop.ts"],"sourcesContent":["import type { MouseEvent, PointerEvent, RefObject } from 'react'\n\n/** Mouse or pointer event with viewport coordinates (native {@code <dialog>} backdrop close). */\nexport type DialogBackdropPointer =\n | Pick<MouseEvent<HTMLDialogElement>, 'clientX' | 'clientY'>\n | Pick<PointerEvent<HTMLDialogElement>, 'clientX' | 'clientY'>\n\n/**\n * Closes a native {@code <dialog>} when the pointer event is outside the dialog panel\n * (on the dimmed backdrop). Modal dialogs only expose the panel in {@code getBoundingClientRect()},\n * not the full viewport overlay.\n */\nexport function closeNativeDialogOnBackdropPointer(\n event: DialogBackdropPointer,\n dialogRef: RefObject<HTMLDialogElement | null>,\n): void {\n const el = dialogRef.current\n if (!el) {return}\n const r = el.getBoundingClientRect()\n const { clientX: x, clientY: y } = event\n if (x < r.left || x > r.right || y < r.top || y > r.bottom) {\n el.close()\n }\n}\n"],"names":["closeNativeDialogOnBackdropPointer","event","dialogRef","el","current","r","getBoundingClientRect","clientX","x","clientY","y","left","right","top","bottom","close"],"mappings":"AAOA;;;;CAIC,GACD,OAAO,SAASA,mCACdC,KAA4B,EAC5BC,SAA8C;IAE9C,MAAMC,KAAKD,UAAUE,OAAO;IAC5B,IAAI,CAACD,IAAI;QAAC;IAAM;IAChB,MAAME,IAAIF,GAAGG,qBAAqB;IAClC,MAAM,EAAEC,SAASC,CAAC,EAAEC,SAASC,CAAC,EAAE,GAAGT;IACnC,IAAIO,IAAIH,EAAEM,IAAI,IAAIH,IAAIH,EAAEO,KAAK,IAAIF,IAAIL,EAAEQ,GAAG,IAAIH,IAAIL,EAAES,MAAM,EAAE;QAC1DX,GAAGY,KAAK;IACV;AACF"}
1
+ {"version":3,"sources":["../../src/utils/dialogBackdrop.ts"],"sourcesContent":["import type { MouseEvent, PointerEvent, RefObject } from 'react'\n\n/** Mouse or pointer event with viewport coordinates (native {@code <dialog>} backdrop close). */\nexport type DialogBackdropPointer =\n | Pick<MouseEvent<HTMLDialogElement>, 'clientX' | 'clientY'>\n | Pick<PointerEvent<HTMLDialogElement>, 'clientX' | 'clientY'>\n\n/**\n * Closes a native {@code <dialog>} when the pointer event is outside the dialog panel\n * (on the dimmed backdrop). Modal dialogs only expose the panel in {@code getBoundingClientRect()},\n * not the full viewport overlay.\n */\nexport function closeNativeDialogOnBackdropPointer(\n event: DialogBackdropPointer,\n dialogRef: RefObject<HTMLDialogElement | null>,\n): void {\n const el = dialogRef.current\n if (!el) {\n return\n }\n const r = el.getBoundingClientRect()\n const { clientX: x, clientY: y } = event\n if (x < r.left || x > r.right || y < r.top || y > r.bottom) {\n el.close()\n }\n}\n"],"names":["closeNativeDialogOnBackdropPointer","event","dialogRef","el","current","r","getBoundingClientRect","clientX","x","clientY","y","left","right","top","bottom","close"],"mappings":"AAOA;;;;CAIC,GACD,OAAO,SAASA,mCACdC,KAA4B,EAC5BC,SAA8C;IAE9C,MAAMC,KAAKD,UAAUE,OAAO;IAC5B,IAAI,CAACD,IAAI;QACP;IACF;IACA,MAAME,IAAIF,GAAGG,qBAAqB;IAClC,MAAM,EAAEC,SAASC,CAAC,EAAEC,SAASC,CAAC,EAAE,GAAGT;IACnC,IAAIO,IAAIH,EAAEM,IAAI,IAAIH,IAAIH,EAAEO,KAAK,IAAIF,IAAIL,EAAEQ,GAAG,IAAIH,IAAIL,EAAES,MAAM,EAAE;QAC1DX,GAAGY,KAAK;IACV;AACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/hostname.ts"],"sourcesContent":["export function getCurrentDbName(): string {\n try {\n const { hostname, pathname } = new URL(process.env.MONGODB_URI!)\n return hostname + pathname\n } catch {\n return 'none'\n }\n}\n\nexport function getCurrentHostname(): string {\n try {\n return process.env.NEXT_PUBLIC_SERVER_URL\n ? new URL(process.env.NEXT_PUBLIC_SERVER_URL).hostname\n : (process.env.VERCEL_URL ?? 'none')\n } catch {\n return process.env.VERCEL_URL! || 'none'\n }\n}\n"],"names":["getCurrentDbName","hostname","pathname","URL","process","env","MONGODB_URI","getCurrentHostname","NEXT_PUBLIC_SERVER_URL","VERCEL_URL"],"mappings":"AAAA,OAAO,SAASA;IACd,IAAI;QACF,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAIC,QAAQC,GAAG,CAACC,WAAW;QAC9D,OAAOL,WAAWC;IACpB,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,SAASK;IACd,IAAI;QACF,OAAOH,QAAQC,GAAG,CAACG,sBAAsB,GACrC,IAAIL,IAAIC,QAAQC,GAAG,CAACG,sBAAsB,EAAEP,QAAQ,GACnDG,QAAQC,GAAG,CAACI,UAAU,IAAI;IACjC,EAAE,OAAM;QACN,OAAOL,QAAQC,GAAG,CAACI,UAAU,IAAK;IACpC;AACF"}
1
+ {"version":3,"sources":["../../src/utils/hostname.ts"],"sourcesContent":["export function getCurrentDbName(): string {\n try {\n const { hostname, pathname } = new URL(process.env.MONGODB_URI!)\n return hostname + pathname\n } catch {\n return 'none'\n }\n}\n\nexport function getCurrentHostname(): string {\n try {\n return process.env.NEXT_PUBLIC_SERVER_URL ?\n new URL(process.env.NEXT_PUBLIC_SERVER_URL).hostname\n : (process.env.VERCEL_URL ?? 'none')\n } catch {\n return process.env.VERCEL_URL! || 'none'\n }\n}\n"],"names":["getCurrentDbName","hostname","pathname","URL","process","env","MONGODB_URI","getCurrentHostname","NEXT_PUBLIC_SERVER_URL","VERCEL_URL"],"mappings":"AAAA,OAAO,SAASA;IACd,IAAI;QACF,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAIC,QAAQC,GAAG,CAACC,WAAW;QAC9D,OAAOL,WAAWC;IACpB,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,SAASK;IACd,IAAI;QACF,OAAOH,QAAQC,GAAG,CAACG,sBAAsB,GACrC,IAAIL,IAAIC,QAAQC,GAAG,CAACG,sBAAsB,EAAEP,QAAQ,GACnDG,QAAQC,GAAG,CAACI,UAAU,IAAI;IACjC,EAAE,OAAM;QACN,OAAOL,QAAQC,GAAG,CAACI,UAAU,IAAK;IACpC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/maskBlobToken.ts"],"sourcesContent":["/**\n * Mask a Vercel Blob read/write token for display in the admin UI (never send the full secret).\n * Shape: prefix + fixed-length asterisk run + suffix (length of real token does not change star count).\n * Example: vercel_blob_rw_yfn + 23×* + bsM7\n */\nexport function maskBlobReadWriteToken(raw: string): string {\n const t = raw.trim()\n if (t.length === 0) {return ''}\n\n /** Fixed number of asterisks between prefix and suffix (UI stays compact for any token length). */\n const middleRun = 32\n\n const prefixLen = Math.min(18, Math.max(8, Math.floor(t.length * 0.42)))\n const suffixLen = Math.min(4, Math.max(3, Math.floor(t.length * 0.12)))\n\n if (t.length <= prefixLen + suffixLen + 6) {\n const headLen = Math.min(6, t.length)\n const head = t.slice(0, headLen)\n const tailLen = Math.min(3, Math.max(0, t.length - headLen))\n const tail = tailLen > 0 ? t.slice(-tailLen) : ''\n const middle = '*'.repeat(middleRun)\n return tail ? `${head}${middle}${tail}` : `${head}${middle}`\n }\n\n const prefix = t.slice(0, prefixLen)\n const suffix = t.slice(-suffixLen)\n return `${prefix}${'*'.repeat(middleRun)}${suffix}`\n}\n\n/**\n * When a token is already stored, treat the client value as \"do not change the stored token\" if\n * they left the masked placeholder or cleared the field while editing other settings.\n */\nexport function shouldPreserveBackupBlobTokenField(\n clientValue: string,\n hadStoredToken: boolean,\n): boolean {\n if (!hadStoredToken) {return false}\n const v = clientValue.trim()\n if (v.length === 0) {return true}\n if (/\\*{2,}/.test(v)) {return true}\n return false\n}\n"],"names":["maskBlobReadWriteToken","raw","t","trim","length","middleRun","prefixLen","Math","min","max","floor","suffixLen","headLen","head","slice","tailLen","tail","middle","repeat","prefix","suffix","shouldPreserveBackupBlobTokenField","clientValue","hadStoredToken","v","test"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,SAASA,uBAAuBC,GAAW;IAChD,MAAMC,IAAID,IAAIE,IAAI;IAClB,IAAID,EAAEE,MAAM,KAAK,GAAG;QAAC,OAAO;IAAE;IAE9B,iGAAiG,GACjG,MAAMC,YAAY;IAElB,MAAMC,YAAYC,KAAKC,GAAG,CAAC,IAAID,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACR,EAAEE,MAAM,GAAG;IACjE,MAAMO,YAAYJ,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACR,EAAEE,MAAM,GAAG;IAEhE,IAAIF,EAAEE,MAAM,IAAIE,YAAYK,YAAY,GAAG;QACzC,MAAMC,UAAUL,KAAKC,GAAG,CAAC,GAAGN,EAAEE,MAAM;QACpC,MAAMS,OAAOX,EAAEY,KAAK,CAAC,GAAGF;QACxB,MAAMG,UAAUR,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGP,EAAEE,MAAM,GAAGQ;QACnD,MAAMI,OAAOD,UAAU,IAAIb,EAAEY,KAAK,CAAC,CAACC,WAAW;QAC/C,MAAME,SAAS,IAAIC,MAAM,CAACb;QAC1B,OAAOW,OAAO,GAAGH,OAAOI,SAASD,MAAM,GAAG,GAAGH,OAAOI,QAAQ;IAC9D;IAEA,MAAME,SAASjB,EAAEY,KAAK,CAAC,GAAGR;IAC1B,MAAMc,SAASlB,EAAEY,KAAK,CAAC,CAACH;IACxB,OAAO,GAAGQ,SAAS,IAAID,MAAM,CAACb,aAAae,QAAQ;AACrD;AAEA;;;CAGC,GACD,OAAO,SAASC,mCACdC,WAAmB,EACnBC,cAAuB;IAEvB,IAAI,CAACA,gBAAgB;QAAC,OAAO;IAAK;IAClC,MAAMC,IAAIF,YAAYnB,IAAI;IAC1B,IAAIqB,EAAEpB,MAAM,KAAK,GAAG;QAAC,OAAO;IAAI;IAChC,IAAI,SAASqB,IAAI,CAACD,IAAI;QAAC,OAAO;IAAI;IAClC,OAAO;AACT"}
1
+ {"version":3,"sources":["../../src/utils/maskBlobToken.ts"],"sourcesContent":["/**\n * Mask a Vercel Blob read/write token for display in the admin UI (never send the full secret).\n * Shape: prefix + fixed-length asterisk run + suffix (length of real token does not change star count).\n * Example: vercel_blob_rw_yfn + 23×* + bsM7\n */\nexport function maskBlobReadWriteToken(raw: string): string {\n const t = raw.trim()\n if (t.length === 0) {\n return ''\n }\n\n /** Fixed number of asterisks between prefix and suffix (UI stays compact for any token length). */\n const middleRun = 32\n\n const prefixLen = Math.min(18, Math.max(8, Math.floor(t.length * 0.42)))\n const suffixLen = Math.min(4, Math.max(3, Math.floor(t.length * 0.12)))\n\n if (t.length <= prefixLen + suffixLen + 6) {\n const headLen = Math.min(6, t.length)\n const head = t.slice(0, headLen)\n const tailLen = Math.min(3, Math.max(0, t.length - headLen))\n const tail = tailLen > 0 ? t.slice(-tailLen) : ''\n const middle = '*'.repeat(middleRun)\n return tail ? `${head}${middle}${tail}` : `${head}${middle}`\n }\n\n const prefix = t.slice(0, prefixLen)\n const suffix = t.slice(-suffixLen)\n return `${prefix}${'*'.repeat(middleRun)}${suffix}`\n}\n\n/**\n * When a token is already stored, treat the client value as \"do not change the stored token\" if\n * they left the masked placeholder or cleared the field while editing other settings.\n */\nexport function shouldPreserveBackupBlobTokenField(\n clientValue: string,\n hadStoredToken: boolean,\n): boolean {\n if (!hadStoredToken) {\n return false\n }\n const v = clientValue.trim()\n if (v.length === 0) {\n return true\n }\n if (/\\*{2,}/.test(v)) {\n return true\n }\n return false\n}\n"],"names":["maskBlobReadWriteToken","raw","t","trim","length","middleRun","prefixLen","Math","min","max","floor","suffixLen","headLen","head","slice","tailLen","tail","middle","repeat","prefix","suffix","shouldPreserveBackupBlobTokenField","clientValue","hadStoredToken","v","test"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,SAASA,uBAAuBC,GAAW;IAChD,MAAMC,IAAID,IAAIE,IAAI;IAClB,IAAID,EAAEE,MAAM,KAAK,GAAG;QAClB,OAAO;IACT;IAEA,iGAAiG,GACjG,MAAMC,YAAY;IAElB,MAAMC,YAAYC,KAAKC,GAAG,CAAC,IAAID,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACR,EAAEE,MAAM,GAAG;IACjE,MAAMO,YAAYJ,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACR,EAAEE,MAAM,GAAG;IAEhE,IAAIF,EAAEE,MAAM,IAAIE,YAAYK,YAAY,GAAG;QACzC,MAAMC,UAAUL,KAAKC,GAAG,CAAC,GAAGN,EAAEE,MAAM;QACpC,MAAMS,OAAOX,EAAEY,KAAK,CAAC,GAAGF;QACxB,MAAMG,UAAUR,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGP,EAAEE,MAAM,GAAGQ;QACnD,MAAMI,OAAOD,UAAU,IAAIb,EAAEY,KAAK,CAAC,CAACC,WAAW;QAC/C,MAAME,SAAS,IAAIC,MAAM,CAACb;QAC1B,OAAOW,OAAO,GAAGH,OAAOI,SAASD,MAAM,GAAG,GAAGH,OAAOI,QAAQ;IAC9D;IAEA,MAAME,SAASjB,EAAEY,KAAK,CAAC,GAAGR;IAC1B,MAAMc,SAASlB,EAAEY,KAAK,CAAC,CAACH;IACxB,OAAO,GAAGQ,SAAS,IAAID,MAAM,CAACb,aAAae,QAAQ;AACrD;AAEA;;;CAGC,GACD,OAAO,SAASC,mCACdC,WAAmB,EACnBC,cAAuB;IAEvB,IAAI,CAACA,gBAAgB;QACnB,OAAO;IACT;IACA,MAAMC,IAAIF,YAAYnB,IAAI;IAC1B,IAAIqB,EAAEpB,MAAM,KAAK,GAAG;QAClB,OAAO;IACT;IACA,IAAI,SAASqB,IAAI,CAACD,IAAI;QACpB,OAAO;IACT;IACA,OAAO;AACT"}