@trieb.work/payload-plugin-backup-mongodb 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -0
- package/dist/backup-dashboard.css +1132 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +3 -0
- package/dist/client.js.map +1 -0
- package/dist/collections/BackupSettings.d.ts +6 -0
- package/dist/collections/BackupSettings.js +91 -0
- package/dist/collections/BackupSettings.js.map +1 -0
- package/dist/collections/BackupTasks.d.ts +2 -0
- package/dist/collections/BackupTasks.js +56 -0
- package/dist/collections/BackupTasks.js.map +1 -0
- package/dist/components/BackupDashboard/BackupItemActions.client.d.ts +7 -0
- package/dist/components/BackupDashboard/BackupItemActions.client.js +90 -0
- package/dist/components/BackupDashboard/BackupItemActions.client.js.map +1 -0
- package/dist/components/BackupDashboard/BackupListCollapsible.client.d.ts +16 -0
- package/dist/components/BackupDashboard/BackupListCollapsible.client.js +597 -0
- package/dist/components/BackupDashboard/BackupListCollapsible.client.js.map +1 -0
- package/dist/components/BackupDashboard/BackupSettingsModal.client.d.ts +2 -0
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js +939 -0
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -0
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.d.ts +18 -0
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js +267 -0
- package/dist/components/BackupDashboard/CollectionBackupPreviewBody.client.js.map +1 -0
- package/dist/components/BackupDashboard/ManualBackupDialog.client.d.ts +1 -0
- package/dist/components/BackupDashboard/ManualBackupDialog.client.js +276 -0
- package/dist/components/BackupDashboard/ManualBackupDialog.client.js.map +1 -0
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.d.ts +6 -0
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js +551 -0
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -0
- package/dist/components/BackupDashboard/TaskActionButton.client.d.ts +29 -0
- package/dist/components/BackupDashboard/TaskActionButton.client.js +240 -0
- package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -0
- package/dist/components/BackupDashboard/backupDashboardInlineCss.d.ts +1 -0
- package/dist/components/BackupDashboard/backupDashboardInlineCss.js +3 -0
- package/dist/components/BackupDashboard/backupDashboardInlineCss.js.map +1 -0
- package/dist/components/BackupDashboard/index.client.d.ts +8 -0
- package/dist/components/BackupDashboard/index.client.js +9 -0
- package/dist/components/BackupDashboard/index.client.js.map +1 -0
- package/dist/components/BackupDashboard/index.d.ts +8 -0
- package/dist/components/BackupDashboard/index.js +196 -0
- package/dist/components/BackupDashboard/index.js.map +1 -0
- package/dist/core/archive.d.ts +8 -0
- package/dist/core/archive.js +47 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/backup.d.ts +41 -0
- package/dist/core/backup.js +188 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/backupBlobIO.d.ts +54 -0
- package/dist/core/backupBlobIO.js +179 -0
- package/dist/core/backupBlobIO.js.map +1 -0
- package/dist/core/backupBlobTransfer.d.ts +29 -0
- package/dist/core/backupBlobTransfer.js +85 -0
- package/dist/core/backupBlobTransfer.js.map +1 -0
- package/dist/core/backupSettings.d.ts +35 -0
- package/dist/core/backupSettings.js +95 -0
- package/dist/core/backupSettings.js.map +1 -0
- package/dist/core/backupSourcePreview.d.ts +10 -0
- package/dist/core/backupSourcePreview.js +33 -0
- package/dist/core/backupSourcePreview.js.map +1 -0
- package/dist/core/blobTokenValidate.d.ts +14 -0
- package/dist/core/blobTokenValidate.js +75 -0
- package/dist/core/blobTokenValidate.js.map +1 -0
- package/dist/core/db.d.ts +24 -0
- package/dist/core/db.js +12 -0
- package/dist/core/db.js.map +1 -0
- package/dist/core/restore.d.ts +27 -0
- package/dist/core/restore.js +199 -0
- package/dist/core/restore.js.map +1 -0
- package/dist/core/restorePreview.d.ts +64 -0
- package/dist/core/restorePreview.js +266 -0
- package/dist/core/restorePreview.js.map +1 -0
- package/dist/core/taskProgress.d.ts +26 -0
- package/dist/core/taskProgress.js +73 -0
- package/dist/core/taskProgress.js.map +1 -0
- package/dist/core/vercelBackupCron.d.ts +8 -0
- package/dist/core/vercelBackupCron.js +38 -0
- package/dist/core/vercelBackupCron.js.map +1 -0
- package/dist/endpoints/createBackupEndpoints.d.ts +1 -0
- package/dist/endpoints/createBackupEndpoints.js +3 -0
- package/dist/endpoints/createBackupEndpoints.js.map +1 -0
- package/dist/endpoints/index.d.ts +3 -0
- package/dist/endpoints/index.js +34 -0
- package/dist/endpoints/index.js.map +1 -0
- package/dist/endpoints/paths/admin-backup-download.d.ts +8 -0
- package/dist/endpoints/paths/admin-backup-download.js +72 -0
- package/dist/endpoints/paths/admin-backup-download.js.map +1 -0
- package/dist/endpoints/paths/admin-delete.d.ts +3 -0
- package/dist/endpoints/paths/admin-delete.js +55 -0
- package/dist/endpoints/paths/admin-delete.js.map +1 -0
- package/dist/endpoints/paths/admin-manual.d.ts +3 -0
- package/dist/endpoints/paths/admin-manual.js +61 -0
- package/dist/endpoints/paths/admin-manual.js.map +1 -0
- package/dist/endpoints/paths/admin-preview.d.ts +6 -0
- package/dist/endpoints/paths/admin-preview.js +102 -0
- package/dist/endpoints/paths/admin-preview.js.map +1 -0
- package/dist/endpoints/paths/admin-restore.d.ts +3 -0
- package/dist/endpoints/paths/admin-restore.js +74 -0
- package/dist/endpoints/paths/admin-restore.js.map +1 -0
- package/dist/endpoints/paths/admin-seed.d.ts +3 -0
- package/dist/endpoints/paths/admin-seed.js +47 -0
- package/dist/endpoints/paths/admin-seed.js.map +1 -0
- package/dist/endpoints/paths/admin-settings.d.ts +3 -0
- package/dist/endpoints/paths/admin-settings.js +230 -0
- package/dist/endpoints/paths/admin-settings.js.map +1 -0
- package/dist/endpoints/paths/admin-task.d.ts +3 -0
- package/dist/endpoints/paths/admin-task.js +34 -0
- package/dist/endpoints/paths/admin-task.js.map +1 -0
- package/dist/endpoints/paths/admin-validate-blob-token.d.ts +8 -0
- package/dist/endpoints/paths/admin-validate-blob-token.js +26 -0
- package/dist/endpoints/paths/admin-validate-blob-token.js.map +1 -0
- package/dist/endpoints/paths/cron-list.d.ts +2 -0
- package/dist/endpoints/paths/cron-list.js +33 -0
- package/dist/endpoints/paths/cron-list.js.map +1 -0
- package/dist/endpoints/paths/cron-restore.d.ts +2 -0
- package/dist/endpoints/paths/cron-restore.js +61 -0
- package/dist/endpoints/paths/cron-restore.js.map +1 -0
- package/dist/endpoints/paths/cron-run.d.ts +3 -0
- package/dist/endpoints/paths/cron-run.js +45 -0
- package/dist/endpoints/paths/cron-run.js.map +1 -0
- package/dist/endpoints/shared.d.ts +13 -0
- package/dist/endpoints/shared.js +58 -0
- package/dist/endpoints/shared.js.map +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +3 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +82 -0
- package/dist/plugin.js.map +1 -0
- package/dist/publicApiPaths.d.ts +19 -0
- package/dist/publicApiPaths.js +20 -0
- package/dist/publicApiPaths.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/backupSelection.d.ts +6 -0
- package/dist/utils/backupSelection.js +16 -0
- package/dist/utils/backupSelection.js.map +1 -0
- package/dist/utils/blobName.d.ts +32 -0
- package/dist/utils/blobName.js +99 -0
- package/dist/utils/blobName.js.map +1 -0
- package/dist/utils/dashboardRoleAccess.d.ts +23 -0
- package/dist/utils/dashboardRoleAccess.js +64 -0
- package/dist/utils/dashboardRoleAccess.js.map +1 -0
- package/dist/utils/dialogBackdrop.d.ts +9 -0
- package/dist/utils/dialogBackdrop.js +17 -0
- package/dist/utils/dialogBackdrop.js.map +1 -0
- package/dist/utils/formatBytes.d.ts +2 -0
- package/dist/utils/formatBytes.js +20 -0
- package/dist/utils/formatBytes.js.map +1 -0
- package/dist/utils/hostname.d.ts +2 -0
- package/dist/utils/hostname.js +17 -0
- package/dist/utils/hostname.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/maskBlobToken.d.ts +11 -0
- package/dist/utils/maskBlobToken.js +42 -0
- package/dist/utils/maskBlobToken.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/restorePreview.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport { EJSON } from 'bson'\n\nimport { resolveTarGzip } from './archive'\nimport { COLLECTION_FILE_NAME } from './backup'\nimport { readBackupBlobContentFlexible } from './backupBlobIO'\n\n/** Mongo collection names that invalidate the current admin session when replaced. */\nconst AUTH_SESSION_MONGO_NAMES = new Set(['payload-preferences', 'roles', 'sessions', 'users'])\n\nconst MONGO_VERSIONS = /^_(.+)_versions$/i\n\nexport type RestorePreviewFileKind = 'json' | 'tar-gzip'\n\nexport type RestorePreviewAdminHiddenReason = 'collection-config' | 'version-history'\n\nexport type RestorePreviewGroup = {\n adminHidden: boolean\n adminHiddenReasons: RestorePreviewAdminHiddenReason[]\n affectsAuthSession: boolean\n /** e.g. \"Seiten (pages)\" */\n displayTitle: string\n /** Stable id for UI + skip payload (Payload collection slug when known, else mongo name). */\n groupId: string\n main?: { docCount: number; mongoName: string }\n /** All Mongo names restored when this group is checked. */\n mongoNames: string[]\n versions?: { docCount: number; mongoName: string }\n}\n\nexport type RestorePreviewResponse = {\n fileKind: RestorePreviewFileKind\n groups: RestorePreviewGroup[]\n mediaBlobCount: number\n}\n\nfunction docCount(byName: Record<string, unknown[]>, name: string): number {\n const docs = byName[name]\n return Array.isArray(docs) ? docs.length : 0\n}\n\nfunction isVersionsMongoName(name: string): boolean {\n return MONGO_VERSIONS.test(name)\n}\n\nfunction resolveAdminHidden(\n payload: Payload,\n mongoName: string,\n): { adminHidden: boolean; adminHiddenReason: null | RestorePreviewAdminHiddenReason } {\n const col = payload.config.collections.find((c) => {\n const dbName = (c as { dbName?: string }).dbName ?? c.slug\n return dbName === mongoName || c.slug === mongoName\n })\n if (col?.admin?.hidden) {\n return { adminHidden: true, adminHiddenReason: 'collection-config' }\n }\n if (isVersionsMongoName(mongoName)) {\n return { adminHidden: true, adminHiddenReason: 'version-history' }\n }\n return { adminHidden: false, adminHiddenReason: null }\n}\n\nfunction pickLocalizedLabel(labels: unknown, preferredLocales: string[]): string | undefined {\n if (!labels || typeof labels !== 'object') {\n return undefined\n }\n const l = labels as { plural?: unknown; singular?: unknown }\n\n const pickField = (field: unknown): string | undefined => {\n if (typeof field === 'string') {\n return field\n }\n if (field && typeof field === 'object') {\n const map = field as Record<string, string>\n for (const loc of preferredLocales) {\n const v = map[loc]\n if (v) {\n return v\n }\n }\n const first = Object.values(map).find(Boolean)\n return first\n }\n return undefined\n }\n\n return pickField(l.plural) || pickField(l.singular)\n}\n\nfunction humanizeSlug(slug: string): string {\n return slug\n .split('-')\n .map((w) => (w ? w.charAt(0).toUpperCase() + w.slice(1) : ''))\n .join(' ')\n}\n\n/** Collections shown in the default Payload admin sidebar: config order, skipping `admin.hidden`. */\nfunction getNavVisibleCollections(payload: Payload) {\n return payload.config.collections.filter((c) => c.admin?.hidden !== true)\n}\n\n/**\n * Index in sidebar order, or `null` if the group is not a visible nav collection\n * (hidden config, unknown mongo name, etc.).\n */\nexport function navVisibleCollectionOrderIndex(payload: Payload, groupId: string): null | number {\n const visible = getNavVisibleCollections(payload)\n const idx = visible.findIndex((c) => {\n const dbName = (c as { dbName?: string }).dbName ?? c.slug\n return c.slug === groupId || dbName === groupId\n })\n return idx === -1 ? null : idx\n}\n\nfunction sortPreviewGroupsLikeAdminNav(\n payload: Payload,\n groups: RestorePreviewGroup[],\n preferredLocales: string[],\n): void {\n const locale = preferredLocales[0] ?? 'en'\n groups.sort((a, b) => {\n const ia = navVisibleCollectionOrderIndex(payload, a.groupId)\n const ib = navVisibleCollectionOrderIndex(payload, b.groupId)\n if (ia !== null && ib !== null && ia !== ib) {\n return ia - ib\n }\n if (ia !== null && ib === null) {\n return -1\n }\n if (ia === null && ib !== null) {\n return 1\n }\n return a.displayTitle.localeCompare(b.displayTitle, locale)\n })\n}\n\nfunction buildDisplayTitle(\n payload: Payload,\n groupSlug: string,\n preferredLocales: string[],\n): string {\n const col = payload.config.collections.find((c) => c.slug === groupSlug)\n const label = pickLocalizedLabel(col?.labels, preferredLocales) || humanizeSlug(groupSlug)\n return `${label} (${groupSlug})`\n}\n\nexport async function loadRestoreBackupIndex(\n downloadUrl: string,\n readAuth?: { pathname: string; token: string },\n): Promise<{\n byName: Record<string, unknown[]>\n fileKind: RestorePreviewFileKind\n mediaBlobCount: number\n}> {\n const urlBase = downloadUrl.split('?')?.[0]\n const bytes = readAuth\n ? await readBackupBlobContentFlexible(readAuth.pathname, downloadUrl, readAuth.token)\n : await (async () => {\n const res = await fetch(downloadUrl)\n if (!res.ok) {\n throw new Error(`Failed to download backup (${res.status})`)\n }\n return Buffer.from(await res.arrayBuffer())\n })()\n\n let byName: Record<string, unknown[]> = {}\n let mediaBlobCount = 0\n let fileKind: RestorePreviewFileKind\n\n if (urlBase?.endsWith('.json')) {\n fileKind = 'json'\n byName = EJSON.parse(bytes.toString('utf8')) as Record<string, unknown[]>\n } else if (urlBase?.endsWith('.gz')) {\n fileKind = 'tar-gzip'\n const files = await resolveTarGzip(bytes)\n byName = EJSON.parse(\n files.find((f) => f.name === COLLECTION_FILE_NAME)?.content?.toString() || '{}',\n ) as Record<string, unknown[]>\n mediaBlobCount = files.filter((f) => f.name !== COLLECTION_FILE_NAME).length\n } else {\n throw new Error('Unsupported backup file type (expected .json or .gz)')\n }\n\n return { byName, fileKind, mediaBlobCount }\n}\n\ntype GroupAgg = {\n groupId: string\n main?: { docCount: number; mongoName: string }\n versions?: { docCount: number; mongoName: string }\n}\n\nfunction rebuildMongoNames(g: GroupAgg): string[] {\n return [g.main?.mongoName, g.versions?.mongoName].filter(Boolean) as string[]\n}\n\nfunction countsFromByName(byName: Record<string, unknown[]>): Record<string, number> {\n const out: Record<string, number> = {}\n for (const k of Object.keys(byName)) {\n out[k] = docCount(byName, k)\n }\n return out\n}\n\n/** Ensures every configured Payload collection appears (0 if absent from backup JSON), like live-DB preview. */\nfunction mergeConfiguredCollectionZeros(\n payload: Payload,\n fileCounts: Record<string, number>,\n excludeMongo: Set<string>,\n): Record<string, number> {\n const merged = { ...fileCounts }\n for (const col of payload.config.collections) {\n const mongo = (col as { dbName?: string }).dbName ?? col.slug\n if (excludeMongo.has(mongo)) {\n continue\n }\n if (!(mongo in merged)) {\n merged[mongo] = 0\n }\n }\n return merged\n}\n\n/** Group Mongo collection names + doc counts (live DB or backup file) for admin UI lists. */\nexport function buildCollectionPreviewGroups(\n payload: Payload,\n countsByMongo: Record<string, number>,\n options: {\n excludeMongo?: Set<string>\n /** When true, list collections with 0 documents (e.g. manual backup UI). Default false. */\n includeEmptyCollections?: boolean\n preferredLocales?: string[]\n /** When true, order like Payload admin sidebar; unknown / hidden-only groups last by title. */\n sortLikeAdminNav?: boolean\n } = {},\n): RestorePreviewGroup[] {\n const preferredLocales = options.preferredLocales?.length\n ? options.preferredLocales\n : ['de', 'en']\n const excludeMongo = options.excludeMongo ?? new Set(['backup-tasks'])\n const includeEmptyCollections = options.includeEmptyCollections ?? false\n const sortLikeAdminNav = options.sortLikeAdminNav ?? false\n\n const aggs = new Map<string, GroupAgg>()\n\n for (const [mongoName, count] of Object.entries(countsByMongo)) {\n if (excludeMongo.has(mongoName)) {\n continue\n }\n if (!includeEmptyCollections && count === 0) {\n continue\n }\n\n const versionMatch = mongoName.match(MONGO_VERSIONS)\n if (versionMatch) {\n const groupId = versionMatch[1]\n const prev = aggs.get(groupId) ?? { groupId }\n aggs.set(groupId, {\n ...prev,\n groupId,\n versions: { docCount: count, mongoName },\n })\n } else {\n const groupId = mongoName\n const prev = aggs.get(groupId) ?? { groupId }\n aggs.set(groupId, {\n ...prev,\n groupId,\n main: { docCount: count, mongoName },\n })\n }\n }\n\n const groups: RestorePreviewGroup[] = [...aggs.values()].map((agg) => {\n const mongoNames = rebuildMongoNames(agg)\n const reasons = new Set<RestorePreviewAdminHiddenReason>()\n let adminHidden = false\n for (const mn of mongoNames) {\n const r = resolveAdminHidden(payload, mn)\n if (r.adminHidden && r.adminHiddenReason) {\n adminHidden = true\n reasons.add(r.adminHiddenReason)\n }\n }\n const affectsAuthSession = mongoNames.some((n) => AUTH_SESSION_MONGO_NAMES.has(n))\n\n return {\n adminHidden,\n adminHiddenReasons: [...reasons],\n affectsAuthSession,\n displayTitle: buildDisplayTitle(payload, agg.groupId, preferredLocales),\n groupId: agg.groupId,\n main: agg.main,\n mongoNames,\n versions: agg.versions,\n }\n })\n\n if (sortLikeAdminNav) {\n sortPreviewGroupsLikeAdminNav(payload, groups, preferredLocales)\n } else {\n const locale = preferredLocales[0] ?? 'en'\n groups.sort((a, b) => a.displayTitle.localeCompare(b.displayTitle, locale))\n }\n return groups\n}\n\nexport function buildRestorePreviewGroups(\n payload: Payload,\n parsed: {\n byName: Record<string, unknown[]>\n fileKind: RestorePreviewFileKind\n mediaBlobCount: number\n },\n options: { excludeMongo?: Set<string>; preferredLocales?: string[] } = {},\n): RestorePreviewResponse {\n const excludeMongo = options.excludeMongo ?? new Set(['backup-tasks'])\n const rawCounts = countsFromByName(parsed.byName)\n const counts = mergeConfiguredCollectionZeros(payload, rawCounts, excludeMongo)\n const groups = buildCollectionPreviewGroups(payload, counts, {\n excludeMongo,\n includeEmptyCollections: true,\n preferredLocales: options.preferredLocales,\n sortLikeAdminNav: true,\n })\n\n return {\n fileKind: parsed.fileKind,\n groups,\n mediaBlobCount: parsed.mediaBlobCount,\n }\n}\n\nexport async function getRestorePreviewForAdminRestore(\n payload: Payload,\n downloadUrl: string,\n options: {\n backupRead?: { pathname: string; token: string }\n preferredLocales?: string[]\n } = {},\n): Promise<RestorePreviewResponse> {\n const parsed = await loadRestoreBackupIndex(downloadUrl, options.backupRead)\n return buildRestorePreviewGroups(payload, parsed, options)\n}\n"],"names":["EJSON","resolveTarGzip","COLLECTION_FILE_NAME","readBackupBlobContentFlexible","AUTH_SESSION_MONGO_NAMES","Set","MONGO_VERSIONS","docCount","byName","name","docs","Array","isArray","length","isVersionsMongoName","test","resolveAdminHidden","payload","mongoName","col","config","collections","find","c","dbName","slug","admin","hidden","adminHidden","adminHiddenReason","pickLocalizedLabel","labels","preferredLocales","undefined","l","pickField","field","map","loc","v","first","Object","values","Boolean","plural","singular","humanizeSlug","split","w","charAt","toUpperCase","slice","join","getNavVisibleCollections","filter","navVisibleCollectionOrderIndex","groupId","visible","idx","findIndex","sortPreviewGroupsLikeAdminNav","groups","locale","sort","a","b","ia","ib","displayTitle","localeCompare","buildDisplayTitle","groupSlug","label","loadRestoreBackupIndex","downloadUrl","readAuth","urlBase","bytes","pathname","token","res","fetch","ok","Error","status","Buffer","from","arrayBuffer","mediaBlobCount","fileKind","endsWith","parse","toString","files","f","content","rebuildMongoNames","g","main","versions","countsFromByName","out","k","keys","mergeConfiguredCollectionZeros","fileCounts","excludeMongo","merged","mongo","has","buildCollectionPreviewGroups","countsByMongo","options","includeEmptyCollections","sortLikeAdminNav","aggs","Map","count","entries","versionMatch","match","prev","get","set","agg","mongoNames","reasons","mn","r","add","affectsAuthSession","some","n","adminHiddenReasons","buildRestorePreviewGroups","parsed","rawCounts","counts","getRestorePreviewForAdminRestore","backupRead"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Payload } from 'payload';
|
|
2
|
+
export type BackupTaskKind = 'backup' | 'blobTransfer' | 'delete' | 'restore' | 'seed';
|
|
3
|
+
export type BackupTaskStatus = 'completed' | 'failed' | 'queued' | 'running';
|
|
4
|
+
export type BackupTaskProgress = {
|
|
5
|
+
createdAt: string;
|
|
6
|
+
error?: null | string;
|
|
7
|
+
id: string;
|
|
8
|
+
kind: BackupTaskKind;
|
|
9
|
+
message: string;
|
|
10
|
+
status: BackupTaskStatus;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
};
|
|
13
|
+
/** Task row as stored / returned from Payload (includes server-only poll secret). */
|
|
14
|
+
export type BackupTaskWithPollSecret = {
|
|
15
|
+
pollSecret?: null | string;
|
|
16
|
+
} & BackupTaskProgress;
|
|
17
|
+
export declare function pollSecretsMatch(provided: string, stored: null | string | undefined): boolean;
|
|
18
|
+
export declare function stripPollSecretForClient(task: BackupTaskWithPollSecret): BackupTaskProgress;
|
|
19
|
+
export declare function createBackupTask(payload: Payload, kind: BackupTaskKind, message: string): Promise<{
|
|
20
|
+
pollSecret: string;
|
|
21
|
+
taskId: string;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function getBackupTask(payload: Payload, id: string): Promise<BackupTaskWithPollSecret | undefined>;
|
|
24
|
+
export declare function updateBackupTask(payload: Payload, id: string, patch: Partial<Pick<BackupTaskProgress, 'error' | 'message' | 'status'>>): Promise<BackupTaskProgress | undefined>;
|
|
25
|
+
export declare function completeBackupTask(payload: Payload, id: string, message: string): Promise<BackupTaskProgress | undefined>;
|
|
26
|
+
export declare function failBackupTask(payload: Payload, id: string, error: unknown): Promise<BackupTaskProgress | undefined>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
export function pollSecretsMatch(provided, stored) {
|
|
3
|
+
if (!stored || !provided) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const a = Buffer.from(provided, 'utf8');
|
|
7
|
+
const b = Buffer.from(stored, 'utf8');
|
|
8
|
+
if (a.length !== b.length) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return timingSafeEqual(a, b);
|
|
12
|
+
}
|
|
13
|
+
export function stripPollSecretForClient(task) {
|
|
14
|
+
const { pollSecret: _pollSecret, ...rest } = task;
|
|
15
|
+
return rest;
|
|
16
|
+
}
|
|
17
|
+
export async function createBackupTask(payload, kind, message) {
|
|
18
|
+
const pollSecret = randomBytes(32).toString('hex');
|
|
19
|
+
const doc = await payload.create({
|
|
20
|
+
collection: 'backup-tasks',
|
|
21
|
+
data: {
|
|
22
|
+
kind,
|
|
23
|
+
message,
|
|
24
|
+
pollSecret,
|
|
25
|
+
status: 'queued'
|
|
26
|
+
},
|
|
27
|
+
overrideAccess: true
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
pollSecret,
|
|
31
|
+
taskId: String(doc.id)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function getBackupTask(payload, id) {
|
|
35
|
+
try {
|
|
36
|
+
const doc = await payload.findByID({
|
|
37
|
+
id,
|
|
38
|
+
collection: 'backup-tasks',
|
|
39
|
+
overrideAccess: true
|
|
40
|
+
});
|
|
41
|
+
return doc;
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function updateBackupTask(payload, id, patch) {
|
|
47
|
+
try {
|
|
48
|
+
const doc = await payload.update({
|
|
49
|
+
id,
|
|
50
|
+
collection: 'backup-tasks',
|
|
51
|
+
data: patch,
|
|
52
|
+
overrideAccess: true
|
|
53
|
+
});
|
|
54
|
+
return doc;
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function completeBackupTask(payload, id, message) {
|
|
60
|
+
return updateBackupTask(payload, id, {
|
|
61
|
+
message,
|
|
62
|
+
status: 'completed'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export async function failBackupTask(payload, id, error) {
|
|
66
|
+
return updateBackupTask(payload, id, {
|
|
67
|
+
error: error instanceof Error ? error.message : String(error),
|
|
68
|
+
message: 'Task failed',
|
|
69
|
+
status: 'failed'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=taskProgress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/taskProgress.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport { randomBytes, timingSafeEqual } from 'node:crypto'\n\nexport type BackupTaskKind = 'backup' | 'blobTransfer' | 'delete' | 'restore' | 'seed'\nexport type BackupTaskStatus = 'completed' | 'failed' | 'queued' | 'running'\n\nexport type BackupTaskProgress = {\n createdAt: string\n error?: null | string\n id: string\n kind: BackupTaskKind\n message: string\n status: BackupTaskStatus\n updatedAt: string\n}\n\n/** Task row as stored / returned from Payload (includes server-only poll secret). */\nexport type BackupTaskWithPollSecret = {\n pollSecret?: null | string\n} & BackupTaskProgress\n\nexport function pollSecretsMatch(provided: string, stored: null | string | undefined): boolean {\n if (!stored || !provided) {\n return false\n }\n const a = Buffer.from(provided, 'utf8')\n const b = Buffer.from(stored, 'utf8')\n if (a.length !== b.length) {\n return false\n }\n return timingSafeEqual(a, b)\n}\n\nexport function stripPollSecretForClient(task: BackupTaskWithPollSecret): BackupTaskProgress {\n const { pollSecret: _pollSecret, ...rest } = task\n return rest as BackupTaskProgress\n}\n\nexport async function createBackupTask(\n payload: Payload,\n kind: BackupTaskKind,\n message: string,\n): Promise<{ pollSecret: string; taskId: string }> {\n const pollSecret = randomBytes(32).toString('hex')\n const doc = await payload.create({\n collection: 'backup-tasks',\n data: { kind, message, pollSecret, status: 'queued' },\n overrideAccess: true,\n })\n return { pollSecret, taskId: String(doc.id) }\n}\n\nexport async function getBackupTask(\n payload: Payload,\n id: string,\n): Promise<BackupTaskWithPollSecret | undefined> {\n try {\n const doc = await payload.findByID({\n id,\n collection: 'backup-tasks',\n overrideAccess: true,\n })\n return doc as unknown as BackupTaskWithPollSecret\n } catch {\n return undefined\n }\n}\n\nexport async function updateBackupTask(\n payload: Payload,\n id: string,\n patch: Partial<Pick<BackupTaskProgress, 'error' | 'message' | 'status'>>,\n): Promise<BackupTaskProgress | undefined> {\n try {\n const doc = await payload.update({\n id,\n collection: 'backup-tasks',\n data: patch,\n overrideAccess: true,\n })\n return doc as unknown as BackupTaskProgress\n } catch {\n return undefined\n }\n}\n\nexport async function completeBackupTask(\n payload: Payload,\n id: string,\n message: string,\n): Promise<BackupTaskProgress | undefined> {\n return updateBackupTask(payload, id, { message, status: 'completed' })\n}\n\nexport async function failBackupTask(\n payload: Payload,\n id: string,\n error: unknown,\n): Promise<BackupTaskProgress | undefined> {\n return updateBackupTask(payload, id, {\n error: error instanceof Error ? error.message : String(error),\n message: 'Task failed',\n status: 'failed',\n })\n}\n"],"names":["randomBytes","timingSafeEqual","pollSecretsMatch","provided","stored","a","Buffer","from","b","length","stripPollSecretForClient","task","pollSecret","_pollSecret","rest","createBackupTask","payload","kind","message","toString","doc","create","collection","data","status","overrideAccess","taskId","String","id","getBackupTask","findByID","undefined","updateBackupTask","patch","update","completeBackupTask","failBackupTask","error","Error"],"mappings":"AAEA,SAASA,WAAW,EAAEC,eAAe,QAAQ,cAAa;AAoB1D,OAAO,SAASC,iBAAiBC,QAAgB,EAAEC,MAAiC;IAClF,IAAI,CAACA,UAAU,CAACD,UAAU;QACxB,OAAO;IACT;IACA,MAAME,IAAIC,OAAOC,IAAI,CAACJ,UAAU;IAChC,MAAMK,IAAIF,OAAOC,IAAI,CAACH,QAAQ;IAC9B,IAAIC,EAAEI,MAAM,KAAKD,EAAEC,MAAM,EAAE;QACzB,OAAO;IACT;IACA,OAAOR,gBAAgBI,GAAGG;AAC5B;AAEA,OAAO,SAASE,yBAAyBC,IAA8B;IACrE,MAAM,EAAEC,YAAYC,WAAW,EAAE,GAAGC,MAAM,GAAGH;IAC7C,OAAOG;AACT;AAEA,OAAO,eAAeC,iBACpBC,OAAgB,EAChBC,IAAoB,EACpBC,OAAe;IAEf,MAAMN,aAAaZ,YAAY,IAAImB,QAAQ,CAAC;IAC5C,MAAMC,MAAM,MAAMJ,QAAQK,MAAM,CAAC;QAC/BC,YAAY;QACZC,MAAM;YAAEN;YAAMC;YAASN;YAAYY,QAAQ;QAAS;QACpDC,gBAAgB;IAClB;IACA,OAAO;QAAEb;QAAYc,QAAQC,OAAOP,IAAIQ,EAAE;IAAE;AAC9C;AAEA,OAAO,eAAeC,cACpBb,OAAgB,EAChBY,EAAU;IAEV,IAAI;QACF,MAAMR,MAAM,MAAMJ,QAAQc,QAAQ,CAAC;YACjCF;YACAN,YAAY;YACZG,gBAAgB;QAClB;QACA,OAAOL;IACT,EAAE,OAAM;QACN,OAAOW;IACT;AACF;AAEA,OAAO,eAAeC,iBACpBhB,OAAgB,EAChBY,EAAU,EACVK,KAAwE;IAExE,IAAI;QACF,MAAMb,MAAM,MAAMJ,QAAQkB,MAAM,CAAC;YAC/BN;YACAN,YAAY;YACZC,MAAMU;YACNR,gBAAgB;QAClB;QACA,OAAOL;IACT,EAAE,OAAM;QACN,OAAOW;IACT;AACF;AAEA,OAAO,eAAeI,mBACpBnB,OAAgB,EAChBY,EAAU,EACVV,OAAe;IAEf,OAAOc,iBAAiBhB,SAASY,IAAI;QAAEV;QAASM,QAAQ;IAAY;AACtE;AAEA,OAAO,eAAeY,eACpBpB,OAAgB,EAChBY,EAAU,EACVS,KAAc;IAEd,OAAOL,iBAAiBhB,SAASY,IAAI;QACnCS,OAAOA,iBAAiBC,QAAQD,MAAMnB,OAAO,GAAGS,OAAOU;QACvDnB,SAAS;QACTM,QAAQ;IACV;AACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface VercelBackupCronInfo {
|
|
2
|
+
/** Path relative to the app root (where `vercel.json` lives). */
|
|
3
|
+
configFileRelative: string;
|
|
4
|
+
path: string;
|
|
5
|
+
schedule: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function readVercelBackupCronFromRepo(): null | VercelBackupCronInfo;
|
|
8
|
+
export declare function describeCronSchedule(schedule: string): null | string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import cronstrue from 'cronstrue';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function readVercelBackupCronFromRepo() {
|
|
5
|
+
const configFileRelative = 'vercel.json';
|
|
6
|
+
try {
|
|
7
|
+
const abs = path.join(process.cwd(), configFileRelative);
|
|
8
|
+
if (!fs.existsSync(abs)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const raw = fs.readFileSync(abs, 'utf8');
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
const crons = parsed.crons;
|
|
14
|
+
if (!Array.isArray(crons)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const hit = crons.find((c)=>typeof c?.path === 'string' && c.path.includes('backup-mongodb/cron') && typeof c?.schedule === 'string');
|
|
18
|
+
if (!hit?.schedule || !hit.path) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
configFileRelative,
|
|
23
|
+
path: hit.path,
|
|
24
|
+
schedule: hit.schedule
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function describeCronSchedule(schedule) {
|
|
31
|
+
try {
|
|
32
|
+
return cronstrue.toString(schedule);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=vercelBackupCron.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/vercelBackupCron.ts"],"sourcesContent":["import cronstrue from 'cronstrue'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nexport interface VercelBackupCronInfo {\n /** Path relative to the app root (where `vercel.json` lives). */\n configFileRelative: string\n path: string\n schedule: string\n}\n\nexport function readVercelBackupCronFromRepo(): null | VercelBackupCronInfo {\n const configFileRelative = 'vercel.json'\n try {\n const abs = path.join(process.cwd(), configFileRelative)\n if (!fs.existsSync(abs)) {return null}\n const raw = fs.readFileSync(abs, 'utf8')\n const parsed = JSON.parse(raw) as { crons?: Array<{ path?: string; schedule?: string }> }\n const crons = parsed.crons\n if (!Array.isArray(crons)) {return null}\n const hit = crons.find(\n (c) =>\n typeof c?.path === 'string' &&\n c.path.includes('backup-mongodb/cron') &&\n typeof c?.schedule === 'string',\n )\n if (!hit?.schedule || !hit.path) {return null}\n return { configFileRelative, path: hit.path, schedule: hit.schedule }\n } catch {\n return null\n }\n}\n\nexport function describeCronSchedule(schedule: string): null | string {\n try {\n return cronstrue.toString(schedule)\n } catch {\n return null\n }\n}\n"],"names":["cronstrue","fs","path","readVercelBackupCronFromRepo","configFileRelative","abs","join","process","cwd","existsSync","raw","readFileSync","parsed","JSON","parse","crons","Array","isArray","hit","find","c","includes","schedule","describeCronSchedule","toString"],"mappings":"AAAA,OAAOA,eAAe,YAAW;AACjC,OAAOC,QAAQ,UAAS;AACxB,OAAOC,UAAU,YAAW;AAS5B,OAAO,SAASC;IACd,MAAMC,qBAAqB;IAC3B,IAAI;QACF,MAAMC,MAAMH,KAAKI,IAAI,CAACC,QAAQC,GAAG,IAAIJ;QACrC,IAAI,CAACH,GAAGQ,UAAU,CAACJ,MAAM;YAAC,OAAO;QAAI;QACrC,MAAMK,MAAMT,GAAGU,YAAY,CAACN,KAAK;QACjC,MAAMO,SAASC,KAAKC,KAAK,CAACJ;QAC1B,MAAMK,QAAQH,OAAOG,KAAK;QAC1B,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;YAAC,OAAO;QAAI;QACvC,MAAMG,MAAMH,MAAMI,IAAI,CACpB,CAACC,IACC,OAAOA,GAAGlB,SAAS,YACnBkB,EAAElB,IAAI,CAACmB,QAAQ,CAAC,0BAChB,OAAOD,GAAGE,aAAa;QAE3B,IAAI,CAACJ,KAAKI,YAAY,CAACJ,IAAIhB,IAAI,EAAE;YAAC,OAAO;QAAI;QAC7C,OAAO;YAAEE;YAAoBF,MAAMgB,IAAIhB,IAAI;YAAEoB,UAAUJ,IAAII,QAAQ;QAAC;IACtE,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,SAASC,qBAAqBD,QAAgB;IACnD,IAAI;QACF,OAAOtB,UAAUwB,QAAQ,CAACF;IAC5B,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createBackupMongodbEndpoints } from './index';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/createBackupEndpoints.ts"],"sourcesContent":["export { createBackupMongodbEndpoints } from './index'\n"],"names":["createBackupMongodbEndpoints"],"mappings":"AAAA,SAASA,4BAA4B,QAAQ,UAAS"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createAdminBackupDownloadEndpoint } from './paths/admin-backup-download';
|
|
2
|
+
import { createAdminDeleteEndpoint } from './paths/admin-delete';
|
|
3
|
+
import { createAdminManualEndpoint } from './paths/admin-manual';
|
|
4
|
+
import { createAdminPreviewEndpoints } from './paths/admin-preview';
|
|
5
|
+
import { createAdminRestoreEndpoint } from './paths/admin-restore';
|
|
6
|
+
import { createAdminSeedEndpoint } from './paths/admin-seed';
|
|
7
|
+
import { createAdminSettingsEndpoints } from './paths/admin-settings';
|
|
8
|
+
import { createAdminTaskEndpoint } from './paths/admin-task';
|
|
9
|
+
import { createAdminValidateBlobTokenEndpoint } from './paths/admin-validate-blob-token';
|
|
10
|
+
import { createCronListEndpoint } from './paths/cron-list';
|
|
11
|
+
import { createCronRestoreEndpoint } from './paths/cron-restore';
|
|
12
|
+
import { createCronRunEndpoint } from './paths/cron-run';
|
|
13
|
+
export function createBackupMongodbEndpoints(options) {
|
|
14
|
+
const endpoints = [
|
|
15
|
+
createCronRunEndpoint(options),
|
|
16
|
+
createCronListEndpoint(),
|
|
17
|
+
createCronRestoreEndpoint(),
|
|
18
|
+
createAdminManualEndpoint(options),
|
|
19
|
+
createAdminBackupDownloadEndpoint(options),
|
|
20
|
+
createAdminRestoreEndpoint(options),
|
|
21
|
+
...createAdminPreviewEndpoints(options),
|
|
22
|
+
...createAdminSettingsEndpoints(options),
|
|
23
|
+
createAdminDeleteEndpoint(options),
|
|
24
|
+
createAdminTaskEndpoint(options),
|
|
25
|
+
createAdminValidateBlobTokenEndpoint(options)
|
|
26
|
+
];
|
|
27
|
+
const seed = createAdminSeedEndpoint(options);
|
|
28
|
+
if (seed) {
|
|
29
|
+
endpoints.push(seed);
|
|
30
|
+
}
|
|
31
|
+
return endpoints;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/index.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { BackupPluginOptions } from '../types'\n\nimport { createAdminBackupDownloadEndpoint } from './paths/admin-backup-download'\nimport { createAdminDeleteEndpoint } from './paths/admin-delete'\nimport { createAdminManualEndpoint } from './paths/admin-manual'\nimport { createAdminPreviewEndpoints } from './paths/admin-preview'\nimport { createAdminRestoreEndpoint } from './paths/admin-restore'\nimport { createAdminSeedEndpoint } from './paths/admin-seed'\nimport { createAdminSettingsEndpoints } from './paths/admin-settings'\nimport { createAdminTaskEndpoint } from './paths/admin-task'\nimport { createAdminValidateBlobTokenEndpoint } from './paths/admin-validate-blob-token'\nimport { createCronListEndpoint } from './paths/cron-list'\nimport { createCronRestoreEndpoint } from './paths/cron-restore'\nimport { createCronRunEndpoint } from './paths/cron-run'\n\nexport function createBackupMongodbEndpoints(options: BackupPluginOptions): Endpoint[] {\n const endpoints: Endpoint[] = [\n createCronRunEndpoint(options),\n createCronListEndpoint(),\n createCronRestoreEndpoint(),\n createAdminManualEndpoint(options),\n createAdminBackupDownloadEndpoint(options),\n createAdminRestoreEndpoint(options),\n ...createAdminPreviewEndpoints(options),\n ...createAdminSettingsEndpoints(options),\n createAdminDeleteEndpoint(options),\n createAdminTaskEndpoint(options),\n createAdminValidateBlobTokenEndpoint(options),\n ]\n\n const seed = createAdminSeedEndpoint(options)\n if (seed) {\n endpoints.push(seed)\n }\n\n return endpoints\n}\n"],"names":["createAdminBackupDownloadEndpoint","createAdminDeleteEndpoint","createAdminManualEndpoint","createAdminPreviewEndpoints","createAdminRestoreEndpoint","createAdminSeedEndpoint","createAdminSettingsEndpoints","createAdminTaskEndpoint","createAdminValidateBlobTokenEndpoint","createCronListEndpoint","createCronRestoreEndpoint","createCronRunEndpoint","createBackupMongodbEndpoints","options","endpoints","seed","push"],"mappings":"AAIA,SAASA,iCAAiC,QAAQ,gCAA+B;AACjF,SAASC,yBAAyB,QAAQ,uBAAsB;AAChE,SAASC,yBAAyB,QAAQ,uBAAsB;AAChE,SAASC,2BAA2B,QAAQ,wBAAuB;AACnE,SAASC,0BAA0B,QAAQ,wBAAuB;AAClE,SAASC,uBAAuB,QAAQ,qBAAoB;AAC5D,SAASC,4BAA4B,QAAQ,yBAAwB;AACrE,SAASC,uBAAuB,QAAQ,qBAAoB;AAC5D,SAASC,oCAAoC,QAAQ,oCAAmC;AACxF,SAASC,sBAAsB,QAAQ,oBAAmB;AAC1D,SAASC,yBAAyB,QAAQ,uBAAsB;AAChE,SAASC,qBAAqB,QAAQ,mBAAkB;AAExD,OAAO,SAASC,6BAA6BC,OAA4B;IACvE,MAAMC,YAAwB;QAC5BH,sBAAsBE;QACtBJ;QACAC;QACAR,0BAA0BW;QAC1Bb,kCAAkCa;QAClCT,2BAA2BS;WACxBV,4BAA4BU;WAC5BP,6BAA6BO;QAChCZ,0BAA0BY;QAC1BN,wBAAwBM;QACxBL,qCAAqCK;KACtC;IAED,MAAME,OAAOV,wBAAwBQ;IACrC,IAAIE,MAAM;QACRD,UAAUE,IAAI,CAACD;IACjB;IAEA,OAAOD;AACT"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
import type { BackupPluginOptions } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Authenticated download of a backup archive. Streams via Vercel Blob `get` (private + public) and
|
|
5
|
+
* optionally anonymous `fetch` of the list `downloadUrl` for public blobs. Pass `url` and
|
|
6
|
+
* `downloadUrl` from `list()` so the correct public/private hostname is used.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createAdminBackupDownloadEndpoint(options: BackupPluginOptions): Endpoint;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { streamBackupBlobForDownload } from '../../core/backupBlobIO';
|
|
2
|
+
import { getResolvedCronBackupSettings, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
3
|
+
import { requireBackupAdmin } from '../shared';
|
|
4
|
+
function queryParam(req, key) {
|
|
5
|
+
const fromPayload = req.searchParams.get(key);
|
|
6
|
+
if (fromPayload) {
|
|
7
|
+
return fromPayload.trim();
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
return new URL(req.url).searchParams.get(key)?.trim() || '';
|
|
11
|
+
} catch {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Authenticated download of a backup archive. Streams via Vercel Blob `get` (private + public) and
|
|
17
|
+
* optionally anonymous `fetch` of the list `downloadUrl` for public blobs. Pass `url` and
|
|
18
|
+
* `downloadUrl` from `list()` so the correct public/private hostname is used.
|
|
19
|
+
*/ export function createAdminBackupDownloadEndpoint(options) {
|
|
20
|
+
return {
|
|
21
|
+
handler: async (req)=>{
|
|
22
|
+
const auth = await requireBackupAdmin(req, options);
|
|
23
|
+
if (auth instanceof Response) {
|
|
24
|
+
return auth;
|
|
25
|
+
}
|
|
26
|
+
const { payload } = req;
|
|
27
|
+
const settings = await getResolvedCronBackupSettings(payload);
|
|
28
|
+
const token = resolveBackupBlobToken(settings).trim();
|
|
29
|
+
if (!token) {
|
|
30
|
+
return new Response('Service unavailable', {
|
|
31
|
+
status: 503
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const pathname = queryParam(req, 'pathname');
|
|
35
|
+
if (!pathname.startsWith('backups/')) {
|
|
36
|
+
return new Response('Invalid pathname', {
|
|
37
|
+
status: 400
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const blobUrl = queryParam(req, 'url');
|
|
41
|
+
const downloadUrl = queryParam(req, 'downloadUrl');
|
|
42
|
+
const preferred = resolveBackupBlobAccess(settings);
|
|
43
|
+
const opened = await streamBackupBlobForDownload({
|
|
44
|
+
blobUrl: blobUrl || undefined,
|
|
45
|
+
downloadUrl: downloadUrl || undefined,
|
|
46
|
+
pathname,
|
|
47
|
+
preferredAccess: preferred,
|
|
48
|
+
token
|
|
49
|
+
});
|
|
50
|
+
if (!opened) {
|
|
51
|
+
payload.logger.warn({
|
|
52
|
+
pathname
|
|
53
|
+
}, '[backup-download] Blob not found or unreadable');
|
|
54
|
+
return new Response('Backup not found', {
|
|
55
|
+
status: 404
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const filename = pathname.split('/').pop() || 'backup';
|
|
59
|
+
const safeName = filename.replace(/["\r\n]/g, '_');
|
|
60
|
+
return new Response(opened.stream, {
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Disposition': `attachment; filename="${safeName}"`,
|
|
63
|
+
'Content-Type': opened.contentType || 'application/octet-stream'
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
method: 'get',
|
|
68
|
+
path: '/backup-mongodb/admin/backup-download'
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=admin-backup-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/endpoints/paths/admin-backup-download.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { streamBackupBlobForDownload } from '../../core/backupBlobIO'\nimport {\n getResolvedCronBackupSettings,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { requireBackupAdmin } from '../shared'\n\nfunction queryParam(req: PayloadRequest, key: string): string {\n const fromPayload = req.searchParams.get(key)\n if (fromPayload) {\n return fromPayload.trim()\n }\n try {\n return new URL((req as unknown as Request).url).searchParams.get(key)?.trim() || ''\n } catch {\n return ''\n }\n}\n\n/**\n * Authenticated download of a backup archive. Streams via Vercel Blob `get` (private + public) and\n * optionally anonymous `fetch` of the list `downloadUrl` for public blobs. Pass `url` and\n * `downloadUrl` from `list()` so the correct public/private hostname is used.\n */\nexport function createAdminBackupDownloadEndpoint(options: BackupPluginOptions): Endpoint {\n return {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const settings = await getResolvedCronBackupSettings(payload)\n const token = resolveBackupBlobToken(settings).trim()\n if (!token) {\n return new Response('Service unavailable', { status: 503 })\n }\n\n const pathname = queryParam(req, 'pathname')\n if (!pathname.startsWith('backups/')) {\n return new Response('Invalid pathname', { status: 400 })\n }\n\n const blobUrl = queryParam(req, 'url')\n const downloadUrl = queryParam(req, 'downloadUrl')\n\n const preferred = resolveBackupBlobAccess(settings)\n const opened = await streamBackupBlobForDownload({\n blobUrl: blobUrl || undefined,\n downloadUrl: downloadUrl || undefined,\n pathname,\n preferredAccess: preferred,\n token,\n })\n\n if (!opened) {\n payload.logger.warn({ pathname }, '[backup-download] Blob not found or unreadable')\n return new Response('Backup not found', { status: 404 })\n }\n\n const filename = pathname.split('/').pop() || 'backup'\n const safeName = filename.replace(/[\"\\r\\n]/g, '_')\n return new Response(opened.stream, {\n headers: {\n 'Content-Disposition': `attachment; filename=\"${safeName}\"`,\n 'Content-Type': opened.contentType || 'application/octet-stream',\n },\n })\n },\n method: 'get',\n path: '/backup-mongodb/admin/backup-download',\n }\n}\n"],"names":["streamBackupBlobForDownload","getResolvedCronBackupSettings","resolveBackupBlobAccess","resolveBackupBlobToken","requireBackupAdmin","queryParam","req","key","fromPayload","searchParams","get","trim","URL","url","createAdminBackupDownloadEndpoint","options","handler","auth","Response","payload","settings","token","status","pathname","startsWith","blobUrl","downloadUrl","preferred","opened","undefined","preferredAccess","logger","warn","filename","split","pop","safeName","replace","stream","headers","contentType","method","path"],"mappings":"AAIA,SAASA,2BAA2B,QAAQ,0BAAyB;AACrE,SACEC,6BAA6B,EAC7BC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,kBAAkB,QAAQ,YAAW;AAE9C,SAASC,WAAWC,GAAmB,EAAEC,GAAW;IAClD,MAAMC,cAAcF,IAAIG,YAAY,CAACC,GAAG,CAACH;IACzC,IAAIC,aAAa;QACf,OAAOA,YAAYG,IAAI;IACzB;IACA,IAAI;QACF,OAAO,IAAIC,IAAI,AAACN,IAA2BO,GAAG,EAAEJ,YAAY,CAACC,GAAG,CAACH,MAAMI,UAAU;IACnF,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASG,kCAAkCC,OAA4B;IAC5E,OAAO;QACLC,SAAS,OAAOV;YACd,MAAMW,OAAO,MAAMb,mBAAmBE,KAAKS;YAC3C,IAAIE,gBAAgBC,UAAU;gBAC5B,OAAOD;YACT;YAEA,MAAM,EAAEE,OAAO,EAAE,GAAGb;YACpB,MAAMc,WAAW,MAAMnB,8BAA8BkB;YACrD,MAAME,QAAQlB,uBAAuBiB,UAAUT,IAAI;YACnD,IAAI,CAACU,OAAO;gBACV,OAAO,IAAIH,SAAS,uBAAuB;oBAAEI,QAAQ;gBAAI;YAC3D;YAEA,MAAMC,WAAWlB,WAAWC,KAAK;YACjC,IAAI,CAACiB,SAASC,UAAU,CAAC,aAAa;gBACpC,OAAO,IAAIN,SAAS,oBAAoB;oBAAEI,QAAQ;gBAAI;YACxD;YAEA,MAAMG,UAAUpB,WAAWC,KAAK;YAChC,MAAMoB,cAAcrB,WAAWC,KAAK;YAEpC,MAAMqB,YAAYzB,wBAAwBkB;YAC1C,MAAMQ,SAAS,MAAM5B,4BAA4B;gBAC/CyB,SAASA,WAAWI;gBACpBH,aAAaA,eAAeG;gBAC5BN;gBACAO,iBAAiBH;gBACjBN;YACF;YAEA,IAAI,CAACO,QAAQ;gBACXT,QAAQY,MAAM,CAACC,IAAI,CAAC;oBAAET;gBAAS,GAAG;gBAClC,OAAO,IAAIL,SAAS,oBAAoB;oBAAEI,QAAQ;gBAAI;YACxD;YAEA,MAAMW,WAAWV,SAASW,KAAK,CAAC,KAAKC,GAAG,MAAM;YAC9C,MAAMC,WAAWH,SAASI,OAAO,CAAC,YAAY;YAC9C,OAAO,IAAInB,SAASU,OAAOU,MAAM,EAAE;gBACjCC,SAAS;oBACP,uBAAuB,CAAC,sBAAsB,EAAEH,SAAS,CAAC,CAAC;oBAC3D,gBAAgBR,OAAOY,WAAW,IAAI;gBACxC;YACF;QACF;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { del } from '@vercel/blob';
|
|
2
|
+
import { after } from 'next/server';
|
|
3
|
+
import { getResolvedCronBackupSettings, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
4
|
+
import { completeBackupTask, createBackupTask, failBackupTask, updateBackupTask } from '../../core/taskProgress';
|
|
5
|
+
import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
6
|
+
export function createAdminDeleteEndpoint(options) {
|
|
7
|
+
return {
|
|
8
|
+
handler: async (req)=>{
|
|
9
|
+
const auth = await requireBackupAdmin(req, options);
|
|
10
|
+
if (auth instanceof Response) {
|
|
11
|
+
return auth;
|
|
12
|
+
}
|
|
13
|
+
const { payload } = req;
|
|
14
|
+
const settings = await getResolvedCronBackupSettings(payload);
|
|
15
|
+
const blobToken = resolveBackupBlobToken(settings);
|
|
16
|
+
if (!blobToken) {
|
|
17
|
+
return jsonError('Service unavailable', 503);
|
|
18
|
+
}
|
|
19
|
+
const body = await readRequestJson(req);
|
|
20
|
+
const blobUrl = body?.url;
|
|
21
|
+
const pathname = body?.pathname;
|
|
22
|
+
if (!blobUrl || !pathname || typeof blobUrl !== 'string' || typeof pathname !== 'string') {
|
|
23
|
+
return jsonError('Missing url or pathname', 400);
|
|
24
|
+
}
|
|
25
|
+
const { pollSecret, taskId } = await createBackupTask(payload, 'delete', `Queued deletion of ${pathname}`);
|
|
26
|
+
payload.logger.info({
|
|
27
|
+
pathname,
|
|
28
|
+
taskId
|
|
29
|
+
}, '[backup-endpoint] Delete queued');
|
|
30
|
+
after(updateBackupTask(payload, taskId, {
|
|
31
|
+
message: `Deleting backup ${pathname}`,
|
|
32
|
+
status: 'running'
|
|
33
|
+
}).then(()=>del(blobUrl, {
|
|
34
|
+
token: blobToken
|
|
35
|
+
})).then(()=>completeBackupTask(payload, taskId, `Deleted backup ${pathname}`)).catch(async (error)=>{
|
|
36
|
+
await failBackupTask(payload, taskId, error);
|
|
37
|
+
payload.logger.error({
|
|
38
|
+
err: error,
|
|
39
|
+
pathname,
|
|
40
|
+
taskId
|
|
41
|
+
}, '[backup-endpoint] Delete failed');
|
|
42
|
+
}));
|
|
43
|
+
return Response.json({
|
|
44
|
+
pollSecret,
|
|
45
|
+
taskId
|
|
46
|
+
}, {
|
|
47
|
+
status: 202
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
method: 'post',
|
|
51
|
+
path: '/backup-mongodb/admin/delete'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=admin-delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/endpoints/paths/admin-delete.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { del } from '@vercel/blob'\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { getResolvedCronBackupSettings, resolveBackupBlobToken } from '../../core/backupSettings'\nimport {\n completeBackupTask,\n createBackupTask,\n failBackupTask,\n updateBackupTask,\n} from '../../core/taskProgress'\nimport { jsonError, readRequestJson, requireBackupAdmin } from '../shared'\n\nexport function createAdminDeleteEndpoint(options: BackupPluginOptions): Endpoint {\n return {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const settings = await getResolvedCronBackupSettings(payload)\n const blobToken = resolveBackupBlobToken(settings)\n if (!blobToken) {\n return jsonError('Service unavailable', 503)\n }\n\n const body = (await readRequestJson(req)) as { pathname?: string; url?: string }\n const blobUrl = body?.url\n const pathname = body?.pathname\n\n if (!blobUrl || !pathname || typeof blobUrl !== 'string' || typeof pathname !== 'string') {\n return jsonError('Missing url or pathname', 400)\n }\n\n const { pollSecret, taskId } = await createBackupTask(\n payload,\n 'delete',\n `Queued deletion of ${pathname}`,\n )\n\n payload.logger.info({ pathname, taskId }, '[backup-endpoint] Delete queued')\n\n after(\n updateBackupTask(payload, taskId, {\n message: `Deleting backup ${pathname}`,\n status: 'running',\n })\n .then(() => del(blobUrl, { token: blobToken }))\n .then(() => completeBackupTask(payload, taskId, `Deleted backup ${pathname}`))\n .catch(async (error) => {\n await failBackupTask(payload, taskId, error)\n payload.logger.error(\n { err: error, pathname, taskId },\n '[backup-endpoint] Delete failed',\n )\n }),\n )\n\n return Response.json({ pollSecret, taskId }, { status: 202 })\n },\n method: 'post',\n path: '/backup-mongodb/admin/delete',\n }\n}\n"],"names":["del","after","getResolvedCronBackupSettings","resolveBackupBlobToken","completeBackupTask","createBackupTask","failBackupTask","updateBackupTask","jsonError","readRequestJson","requireBackupAdmin","createAdminDeleteEndpoint","options","handler","req","auth","Response","payload","settings","blobToken","body","blobUrl","url","pathname","pollSecret","taskId","logger","info","message","status","then","token","catch","error","err","json","method","path"],"mappings":"AAEA,SAASA,GAAG,QAAQ,eAAc;AAClC,SAASC,KAAK,QAAQ,cAAa;AAInC,SAASC,6BAA6B,EAAEC,sBAAsB,QAAQ,4BAA2B;AACjG,SACEC,kBAAkB,EAClBC,gBAAgB,EAChBC,cAAc,EACdC,gBAAgB,QACX,0BAAyB;AAChC,SAASC,SAAS,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,YAAW;AAE1E,OAAO,SAASC,0BAA0BC,OAA4B;IACpE,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,OAAO,MAAML,mBAAmBI,KAAKF;YAC3C,IAAIG,gBAAgBC,UAAU;gBAC5B,OAAOD;YACT;YAEA,MAAM,EAAEE,OAAO,EAAE,GAAGH;YACpB,MAAMI,WAAW,MAAMhB,8BAA8Be;YACrD,MAAME,YAAYhB,uBAAuBe;YACzC,IAAI,CAACC,WAAW;gBACd,OAAOX,UAAU,uBAAuB;YAC1C;YAEA,MAAMY,OAAQ,MAAMX,gBAAgBK;YACpC,MAAMO,UAAUD,MAAME;YACtB,MAAMC,WAAWH,MAAMG;YAEvB,IAAI,CAACF,WAAW,CAACE,YAAY,OAAOF,YAAY,YAAY,OAAOE,aAAa,UAAU;gBACxF,OAAOf,UAAU,2BAA2B;YAC9C;YAEA,MAAM,EAAEgB,UAAU,EAAEC,MAAM,EAAE,GAAG,MAAMpB,iBACnCY,SACA,UACA,CAAC,mBAAmB,EAAEM,UAAU;YAGlCN,QAAQS,MAAM,CAACC,IAAI,CAAC;gBAAEJ;gBAAUE;YAAO,GAAG;YAE1CxB,MACEM,iBAAiBU,SAASQ,QAAQ;gBAChCG,SAAS,CAAC,gBAAgB,EAAEL,UAAU;gBACtCM,QAAQ;YACV,GACGC,IAAI,CAAC,IAAM9B,IAAIqB,SAAS;oBAAEU,OAAOZ;gBAAU,IAC3CW,IAAI,CAAC,IAAM1B,mBAAmBa,SAASQ,QAAQ,CAAC,eAAe,EAAEF,UAAU,GAC3ES,KAAK,CAAC,OAAOC;gBACZ,MAAM3B,eAAeW,SAASQ,QAAQQ;gBACtChB,QAAQS,MAAM,CAACO,KAAK,CAClB;oBAAEC,KAAKD;oBAAOV;oBAAUE;gBAAO,GAC/B;YAEJ;YAGJ,OAAOT,SAASmB,IAAI,CAAC;gBAAEX;gBAAYC;YAAO,GAAG;gBAAEI,QAAQ;YAAI;QAC7D;QACAO,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { after } from 'next/server';
|
|
2
|
+
import { createBackup } from '../../core/backup';
|
|
3
|
+
import { getResolvedCronBackupSettings, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
4
|
+
import { completeBackupTask, createBackupTask, failBackupTask } from '../../core/taskProgress';
|
|
5
|
+
import { sanitizeBackupLabel } from '../../utils/index';
|
|
6
|
+
import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
7
|
+
export function createAdminManualEndpoint(options) {
|
|
8
|
+
return {
|
|
9
|
+
handler: async (req)=>{
|
|
10
|
+
const auth = await requireBackupAdmin(req, options);
|
|
11
|
+
if (auth instanceof Response) {
|
|
12
|
+
return auth;
|
|
13
|
+
}
|
|
14
|
+
const { payload } = req;
|
|
15
|
+
const settings = await getResolvedCronBackupSettings(payload);
|
|
16
|
+
const blobToken = resolveBackupBlobToken(settings);
|
|
17
|
+
const blobAccess = resolveBackupBlobAccess(settings);
|
|
18
|
+
if (!blobToken) {
|
|
19
|
+
return jsonError('Service unavailable', 503);
|
|
20
|
+
}
|
|
21
|
+
const body = await readRequestJson(req);
|
|
22
|
+
const clientSkipRaw = body?.skipCollections;
|
|
23
|
+
const clientSkip = Array.isArray(clientSkipRaw) ? clientSkipRaw.filter((x)=>typeof x === 'string' && x.length > 0 && x.length < 512) : [];
|
|
24
|
+
const wantsBlobMedia = body?.includeMedia === true;
|
|
25
|
+
const includeMedia = wantsBlobMedia && !clientSkip.includes('media');
|
|
26
|
+
const label = sanitizeBackupLabel(body?.label);
|
|
27
|
+
const { pollSecret, taskId } = await createBackupTask(payload, 'backup', 'Backup queued');
|
|
28
|
+
payload.logger.info({
|
|
29
|
+
hasLabel: Boolean(label),
|
|
30
|
+
includeMedia,
|
|
31
|
+
skipCount: clientSkip.length,
|
|
32
|
+
taskId
|
|
33
|
+
}, '[backup-endpoint] Manual backup queued');
|
|
34
|
+
after(createBackup(payload, {
|
|
35
|
+
blobAccess,
|
|
36
|
+
blobToken,
|
|
37
|
+
cron: false,
|
|
38
|
+
includeMedia,
|
|
39
|
+
label: label || undefined,
|
|
40
|
+
skipCollections: clientSkip,
|
|
41
|
+
taskId
|
|
42
|
+
}).then(()=>completeBackupTask(payload, taskId, 'Backup completed')).catch(async (error)=>{
|
|
43
|
+
await failBackupTask(payload, taskId, error);
|
|
44
|
+
payload.logger.error({
|
|
45
|
+
err: error,
|
|
46
|
+
taskId
|
|
47
|
+
}, '[backup-endpoint] Manual backup failed');
|
|
48
|
+
}));
|
|
49
|
+
return Response.json({
|
|
50
|
+
pollSecret,
|
|
51
|
+
taskId
|
|
52
|
+
}, {
|
|
53
|
+
status: 202
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
method: 'post',
|
|
57
|
+
path: '/backup-mongodb/admin/manual'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=admin-manual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/endpoints/paths/admin-manual.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 { completeBackupTask, createBackupTask, failBackupTask } from '../../core/taskProgress'\nimport { sanitizeBackupLabel } from '../../utils/index'\nimport { jsonError, readRequestJson, requireBackupAdmin } from '../shared'\n\nexport function createAdminManualEndpoint(options: BackupPluginOptions): Endpoint {\n return {\n handler: async (req) => {\n const auth = await requireBackupAdmin(req, options)\n if (auth instanceof Response) {\n return auth\n }\n\n const { payload } = req\n const settings = await getResolvedCronBackupSettings(payload)\n const blobToken = resolveBackupBlobToken(settings)\n const blobAccess = resolveBackupBlobAccess(settings)\n if (!blobToken) {\n return jsonError('Service unavailable', 503)\n }\n\n const body = (await readRequestJson(req)) as Record<string, unknown>\n const clientSkipRaw = body?.skipCollections\n const clientSkip = Array.isArray(clientSkipRaw)\n ? clientSkipRaw.filter(\n (x: unknown): x is string => typeof x === 'string' && x.length > 0 && x.length < 512,\n )\n : []\n const wantsBlobMedia = body?.includeMedia === true\n const includeMedia = wantsBlobMedia && !clientSkip.includes('media')\n const label = sanitizeBackupLabel(body?.label)\n\n const { pollSecret, taskId } = await createBackupTask(payload, 'backup', 'Backup queued')\n\n payload.logger.info(\n { hasLabel: Boolean(label), includeMedia, skipCount: clientSkip.length, taskId },\n '[backup-endpoint] Manual backup queued',\n )\n\n after(\n createBackup(payload, {\n blobAccess,\n blobToken,\n cron: false,\n includeMedia,\n label: label || undefined,\n skipCollections: clientSkip,\n taskId,\n })\n .then(() => completeBackupTask(payload, taskId, 'Backup completed'))\n .catch(async (error) => {\n await failBackupTask(payload, taskId, error)\n payload.logger.error({ err: error, taskId }, '[backup-endpoint] Manual backup failed')\n }),\n )\n\n return Response.json({ pollSecret, taskId }, { status: 202 })\n },\n method: 'post',\n path: '/backup-mongodb/admin/manual',\n }\n}\n"],"names":["after","createBackup","getResolvedCronBackupSettings","resolveBackupBlobAccess","resolveBackupBlobToken","completeBackupTask","createBackupTask","failBackupTask","sanitizeBackupLabel","jsonError","readRequestJson","requireBackupAdmin","createAdminManualEndpoint","options","handler","req","auth","Response","payload","settings","blobToken","blobAccess","body","clientSkipRaw","skipCollections","clientSkip","Array","isArray","filter","x","length","wantsBlobMedia","includeMedia","includes","label","pollSecret","taskId","logger","info","hasLabel","Boolean","skipCount","cron","undefined","then","catch","error","err","json","status","method","path"],"mappings":"AAEA,SAASA,KAAK,QAAQ,cAAa;AAInC,SAASC,YAAY,QAAQ,oBAAmB;AAChD,SACEC,6BAA6B,EAC7BC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,kBAAkB,EAAEC,gBAAgB,EAAEC,cAAc,QAAQ,0BAAyB;AAC9F,SAASC,mBAAmB,QAAQ,oBAAmB;AACvD,SAASC,SAAS,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,YAAW;AAE1E,OAAO,SAASC,0BAA0BC,OAA4B;IACpE,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,OAAO,MAAML,mBAAmBI,KAAKF;YAC3C,IAAIG,gBAAgBC,UAAU;gBAC5B,OAAOD;YACT;YAEA,MAAM,EAAEE,OAAO,EAAE,GAAGH;YACpB,MAAMI,WAAW,MAAMjB,8BAA8BgB;YACrD,MAAME,YAAYhB,uBAAuBe;YACzC,MAAME,aAAalB,wBAAwBgB;YAC3C,IAAI,CAACC,WAAW;gBACd,OAAOX,UAAU,uBAAuB;YAC1C;YAEA,MAAMa,OAAQ,MAAMZ,gBAAgBK;YACpC,MAAMQ,gBAAgBD,MAAME;YAC5B,MAAMC,aAAaC,MAAMC,OAAO,CAACJ,iBAC7BA,cAAcK,MAAM,CAClB,CAACC,IAA4B,OAAOA,MAAM,YAAYA,EAAEC,MAAM,GAAG,KAAKD,EAAEC,MAAM,GAAG,OAEnF,EAAE;YACN,MAAMC,iBAAiBT,MAAMU,iBAAiB;YAC9C,MAAMA,eAAeD,kBAAkB,CAACN,WAAWQ,QAAQ,CAAC;YAC5D,MAAMC,QAAQ1B,oBAAoBc,MAAMY;YAExC,MAAM,EAAEC,UAAU,EAAEC,MAAM,EAAE,GAAG,MAAM9B,iBAAiBY,SAAS,UAAU;YAEzEA,QAAQmB,MAAM,CAACC,IAAI,CACjB;gBAAEC,UAAUC,QAAQN;gBAAQF;gBAAcS,WAAWhB,WAAWK,MAAM;gBAAEM;YAAO,GAC/E;YAGFpC,MACEC,aAAaiB,SAAS;gBACpBG;gBACAD;gBACAsB,MAAM;gBACNV;gBACAE,OAAOA,SAASS;gBAChBnB,iBAAiBC;gBACjBW;YACF,GACGQ,IAAI,CAAC,IAAMvC,mBAAmBa,SAASkB,QAAQ,qBAC/CS,KAAK,CAAC,OAAOC;gBACZ,MAAMvC,eAAeW,SAASkB,QAAQU;gBACtC5B,QAAQmB,MAAM,CAACS,KAAK,CAAC;oBAAEC,KAAKD;oBAAOV;gBAAO,GAAG;YAC/C;YAGJ,OAAOnB,SAAS+B,IAAI,CAAC;gBAAEb;gBAAYC;YAAO,GAAG;gBAAEa,QAAQ;YAAI;QAC7D;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload';
|
|
2
|
+
import type { BackupPluginOptions } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Admin backup source preview and restore archive preview (both POST).
|
|
5
|
+
*/
|
|
6
|
+
export declare function createAdminPreviewEndpoints(options: BackupPluginOptions): Endpoint[];
|