@trieb.work/payload-plugin-backup-mongodb 0.1.7 → 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.
- package/README.md +130 -62
- package/dist/backup-dashboard.css +18 -1
- package/dist/collections/BackupTasks.js +8 -1
- package/dist/collections/BackupTasks.js.map +1 -1
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js +203 -115
- package/dist/components/BackupDashboard/BackupSettingsModal.client.js.map +1 -1
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js +1 -1
- package/dist/components/BackupDashboard/RestoreBackupDialog.client.js.map +1 -1
- package/dist/components/BackupDashboard/TaskActionButton.client.d.ts +1 -1
- package/dist/components/BackupDashboard/TaskActionButton.client.js +59 -26
- package/dist/components/BackupDashboard/TaskActionButton.client.js.map +1 -1
- package/dist/components/BackupDashboard/backupDashboardInlineCss.js +1 -1
- package/dist/components/BackupDashboard/backupDashboardInlineCss.js.map +1 -1
- package/dist/components/BackupDashboard/index.js +27 -3
- package/dist/components/BackupDashboard/index.js.map +1 -1
- package/dist/core/backup.d.ts +4 -3
- package/dist/core/backup.js +50 -43
- package/dist/core/backup.js.map +1 -1
- package/dist/core/backupArchiveRead.d.ts +15 -0
- package/dist/core/backupArchiveRead.js +30 -0
- package/dist/core/backupArchiveRead.js.map +1 -0
- package/dist/core/backupSettings.js +5 -0
- package/dist/core/backupSettings.js.map +1 -1
- package/dist/core/restore.d.ts +5 -2
- package/dist/core/restore.js +133 -89
- package/dist/core/restore.js.map +1 -1
- package/dist/core/restorePreview.d.ts +12 -3
- package/dist/core/restorePreview.js +17 -13
- package/dist/core/restorePreview.js.map +1 -1
- package/dist/core/restoreResult.d.ts +34 -0
- package/dist/core/restoreResult.js +45 -0
- package/dist/core/restoreResult.js.map +1 -0
- package/dist/core/storage/config.d.ts +31 -0
- package/dist/core/storage/config.js +60 -0
- package/dist/core/storage/config.js.map +1 -0
- package/dist/core/storage/index.d.ts +23 -0
- package/dist/core/storage/index.js +25 -0
- package/dist/core/storage/index.js.map +1 -0
- package/dist/core/storage/s3.d.ts +35 -0
- package/dist/core/storage/s3.js +200 -0
- package/dist/core/storage/s3.js.map +1 -0
- package/dist/core/storage/types.d.ts +60 -0
- package/dist/core/storage/types.js +6 -0
- package/dist/core/storage/types.js.map +1 -0
- package/dist/core/storage/vercelBlob.d.ts +25 -0
- package/dist/core/storage/vercelBlob.js +53 -0
- package/dist/core/storage/vercelBlob.js.map +1 -0
- package/dist/core/taskProgress.d.ts +10 -4
- package/dist/core/taskProgress.js +11 -2
- package/dist/core/taskProgress.js.map +1 -1
- package/dist/endpoints/index.js +0 -5
- package/dist/endpoints/index.js.map +1 -1
- package/dist/endpoints/paths/admin-backup-download.js +8 -7
- package/dist/endpoints/paths/admin-backup-download.js.map +1 -1
- package/dist/endpoints/paths/admin-delete.js +11 -5
- package/dist/endpoints/paths/admin-delete.js.map +1 -1
- package/dist/endpoints/paths/admin-manual.js +2 -1
- package/dist/endpoints/paths/admin-manual.js.map +1 -1
- package/dist/endpoints/paths/admin-preview.js +9 -2
- package/dist/endpoints/paths/admin-preview.js.map +1 -1
- package/dist/endpoints/paths/admin-restore.js +7 -4
- package/dist/endpoints/paths/admin-restore.js.map +1 -1
- package/dist/endpoints/paths/admin-settings.js +35 -2
- package/dist/endpoints/paths/admin-settings.js.map +1 -1
- package/dist/endpoints/paths/cron-list.js +2 -1
- package/dist/endpoints/paths/cron-list.js.map +1 -1
- package/dist/endpoints/paths/cron-restore.js +14 -4
- package/dist/endpoints/paths/cron-restore.js.map +1 -1
- package/dist/endpoints/paths/cron-run.js +2 -1
- package/dist/endpoints/paths/cron-run.js.map +1 -1
- package/dist/endpoints/shared.d.ts +0 -1
- package/dist/endpoints/shared.js +0 -6
- package/dist/endpoints/shared.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/publicApiPaths.d.ts +0 -1
- package/dist/publicApiPaths.js +0 -1
- package/dist/publicApiPaths.js.map +1 -1
- package/dist/types.d.ts +0 -5
- package/dist/types.js.map +1 -1
- package/package.json +22 -4
- package/dist/endpoints/paths/admin-seed.d.ts +0 -3
- package/dist/endpoints/paths/admin-seed.js +0 -47
- package/dist/endpoints/paths/admin-seed.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/storage/vercelBlob.ts"],"sourcesContent":["import { del, list } from '@vercel/blob'\n\nimport type {\n BackupStorageAdapter,\n BackupStorageObject,\n BackupStorageRef,\n BackupStorageValidation,\n} from './types'\n\nimport {\n type BackupBlobAccessLevel,\n putBackupBlobContent,\n readBackupBlobContentFlexible,\n streamBackupBlobForDownload,\n} from '../backupBlobIO'\nimport { validateBackupBlobToken } from '../blobTokenValidate'\n\n/**\n * Vercel Blob backup target — the historical default. This is a thin wrapper around the existing\n * `@vercel/blob` helpers so behaviour (and the test seams that mock `@vercel/blob`) is unchanged;\n * the adapter only generalizes the call shape so S3 can slot in alongside it.\n */\nexport class VercelBlobStorage implements BackupStorageAdapter {\n private readonly access: BackupBlobAccessLevel\n\n private readonly token: string | undefined\n readonly kind = 'vercel-blob'\n\n constructor(options: { access?: BackupBlobAccessLevel; token?: string }) {\n this.token = options.token\n this.access = options.access ?? 'public'\n }\n\n async del(ref: BackupStorageRef): Promise<void> {\n await del(ref.url ?? ref.pathname, { token: this.token })\n }\n\n async list(prefix: string): Promise<BackupStorageObject[]> {\n const { blobs } = await list({ limit: 1000, prefix, token: this.token })\n return blobs as unknown as BackupStorageObject[]\n }\n\n async openDownloadStream(ref: BackupStorageRef) {\n return streamBackupBlobForDownload({\n blobUrl: ref.url,\n downloadUrl: ref.downloadUrl,\n pathname: ref.pathname,\n preferredAccess: this.access,\n token: this.token ?? '',\n })\n }\n\n async put(pathname: string, body: Buffer | string | Uint8Array) {\n await putBackupBlobContent(pathname, body, this.token, this.access)\n return { pathname, url: '' }\n }\n\n async read(ref: BackupStorageRef): Promise<Buffer> {\n return readBackupBlobContentFlexible(\n ref.pathname,\n ref.downloadUrl ?? ref.url ?? '',\n this.token ?? '',\n )\n }\n\n async validate(): Promise<BackupStorageValidation> {\n return validateBackupBlobToken(this.token ?? '')\n }\n}\n"],"names":["del","list","putBackupBlobContent","readBackupBlobContentFlexible","streamBackupBlobForDownload","validateBackupBlobToken","VercelBlobStorage","access","token","kind","options","ref","url","pathname","prefix","blobs","limit","openDownloadStream","blobUrl","downloadUrl","preferredAccess","put","body","read","validate"],"mappings":"AAAA,SAASA,GAAG,EAAEC,IAAI,QAAQ,eAAc;AASxC,SAEEC,oBAAoB,EACpBC,6BAA6B,EAC7BC,2BAA2B,QACtB,kBAAiB;AACxB,SAASC,uBAAuB,QAAQ,uBAAsB;AAE9D;;;;CAIC,GACD,OAAO,MAAMC;IACMC,OAA6B;IAE7BC,MAAyB;IACjCC,OAAO,cAAa;IAE7B,YAAYC,OAA2D,CAAE;QACvE,IAAI,CAACF,KAAK,GAAGE,QAAQF,KAAK;QAC1B,IAAI,CAACD,MAAM,GAAGG,QAAQH,MAAM,IAAI;IAClC;IAEA,MAAMP,IAAIW,GAAqB,EAAiB;QAC9C,MAAMX,IAAIW,IAAIC,GAAG,IAAID,IAAIE,QAAQ,EAAE;YAAEL,OAAO,IAAI,CAACA,KAAK;QAAC;IACzD;IAEA,MAAMP,KAAKa,MAAc,EAAkC;QACzD,MAAM,EAAEC,KAAK,EAAE,GAAG,MAAMd,KAAK;YAAEe,OAAO;YAAMF;YAAQN,OAAO,IAAI,CAACA,KAAK;QAAC;QACtE,OAAOO;IACT;IAEA,MAAME,mBAAmBN,GAAqB,EAAE;QAC9C,OAAOP,4BAA4B;YACjCc,SAASP,IAAIC,GAAG;YAChBO,aAAaR,IAAIQ,WAAW;YAC5BN,UAAUF,IAAIE,QAAQ;YACtBO,iBAAiB,IAAI,CAACb,MAAM;YAC5BC,OAAO,IAAI,CAACA,KAAK,IAAI;QACvB;IACF;IAEA,MAAMa,IAAIR,QAAgB,EAAES,IAAkC,EAAE;QAC9D,MAAMpB,qBAAqBW,UAAUS,MAAM,IAAI,CAACd,KAAK,EAAE,IAAI,CAACD,MAAM;QAClE,OAAO;YAAEM;YAAUD,KAAK;QAAG;IAC7B;IAEA,MAAMW,KAAKZ,GAAqB,EAAmB;QACjD,OAAOR,8BACLQ,IAAIE,QAAQ,EACZF,IAAIQ,WAAW,IAAIR,IAAIC,GAAG,IAAI,IAC9B,IAAI,CAACJ,KAAK,IAAI;IAElB;IAEA,MAAMgB,WAA6C;QACjD,OAAOnB,wBAAwB,IAAI,CAACG,KAAK,IAAI;IAC/C;AACF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Payload } from 'payload';
|
|
2
|
-
|
|
3
|
-
export type
|
|
2
|
+
import type { RestoreBackupResult } from './restoreResult';
|
|
3
|
+
export type BackupTaskKind = 'backup' | 'blobTransfer' | 'delete' | 'restore';
|
|
4
|
+
export type BackupTaskStatus = 'completed' | 'completed_with_warnings' | 'failed' | 'queued' | 'running';
|
|
4
5
|
export type BackupTaskProgress = {
|
|
5
6
|
createdAt: string;
|
|
6
7
|
error?: null | string;
|
|
@@ -9,6 +10,7 @@ export type BackupTaskProgress = {
|
|
|
9
10
|
message: string;
|
|
10
11
|
status: BackupTaskStatus;
|
|
11
12
|
updatedAt: string;
|
|
13
|
+
warnings?: null | string;
|
|
12
14
|
};
|
|
13
15
|
/** Task row as stored / returned from Payload (includes server-only poll secret). */
|
|
14
16
|
export type BackupTaskWithPollSecret = {
|
|
@@ -21,6 +23,10 @@ export declare function createBackupTask(payload: Payload, kind: BackupTaskKind,
|
|
|
21
23
|
taskId: string;
|
|
22
24
|
}>;
|
|
23
25
|
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
|
|
26
|
+
export declare function updateBackupTask(payload: Payload, id: string, patch: Partial<Pick<BackupTaskProgress, 'error' | 'message' | 'status' | 'warnings'>>): Promise<BackupTaskProgress | undefined>;
|
|
27
|
+
export declare function completeBackupTask(payload: Payload, id: string, message: string, options?: {
|
|
28
|
+
status?: 'completed' | 'completed_with_warnings';
|
|
29
|
+
warnings?: string;
|
|
30
|
+
}): Promise<BackupTaskProgress | undefined>;
|
|
31
|
+
export declare function completeRestoreBackupTask(payload: Payload, id: string, result: RestoreBackupResult): Promise<BackupTaskProgress | undefined>;
|
|
26
32
|
export declare function failBackupTask(payload: Payload, id: string, error: unknown): Promise<BackupTaskProgress | undefined>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { buildRestoreWarnings, formatRestoreSummary, restoreTaskStatus } from './restoreResult';
|
|
2
3
|
export function pollSecretsMatch(provided, stored) {
|
|
3
4
|
if (!stored || !provided) {
|
|
4
5
|
return false;
|
|
@@ -56,10 +57,18 @@ export async function updateBackupTask(payload, id, patch) {
|
|
|
56
57
|
return undefined;
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
export async function completeBackupTask(payload, id, message) {
|
|
60
|
+
export async function completeBackupTask(payload, id, message, options) {
|
|
60
61
|
return updateBackupTask(payload, id, {
|
|
61
62
|
message,
|
|
62
|
-
status: 'completed'
|
|
63
|
+
status: options?.status ?? 'completed',
|
|
64
|
+
warnings: options?.warnings ?? null
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export async function completeRestoreBackupTask(payload, id, result) {
|
|
68
|
+
const warnings = buildRestoreWarnings(result);
|
|
69
|
+
return completeBackupTask(payload, id, formatRestoreSummary(result), {
|
|
70
|
+
status: restoreTaskStatus(result),
|
|
71
|
+
warnings: warnings.length > 0 ? warnings.join('\n') : undefined
|
|
63
72
|
});
|
|
64
73
|
}
|
|
65
74
|
export async function failBackupTask(payload, id, error) {
|
|
@@ -1 +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'
|
|
1
|
+
{"version":3,"sources":["../../src/core/taskProgress.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport { randomBytes, timingSafeEqual } from 'node:crypto'\n\nimport type { RestoreBackupResult } from './restoreResult'\n\nimport { buildRestoreWarnings, formatRestoreSummary, restoreTaskStatus } from './restoreResult'\n\nexport type BackupTaskKind = 'backup' | 'blobTransfer' | 'delete' | 'restore'\nexport type BackupTaskStatus =\n | 'completed'\n | 'completed_with_warnings'\n | 'failed'\n | 'queued'\n | '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 warnings?: null | 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' | 'warnings'>>,\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 options?: { status?: 'completed' | 'completed_with_warnings'; warnings?: string },\n): Promise<BackupTaskProgress | undefined> {\n return updateBackupTask(payload, id, {\n message,\n status: options?.status ?? 'completed',\n warnings: options?.warnings ?? null,\n })\n}\n\nexport async function completeRestoreBackupTask(\n payload: Payload,\n id: string,\n result: RestoreBackupResult,\n): Promise<BackupTaskProgress | undefined> {\n const warnings = buildRestoreWarnings(result)\n return completeBackupTask(payload, id, formatRestoreSummary(result), {\n status: restoreTaskStatus(result),\n warnings: warnings.length > 0 ? warnings.join('\\n') : undefined,\n })\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","buildRestoreWarnings","formatRestoreSummary","restoreTaskStatus","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","options","warnings","completeRestoreBackupTask","result","join","failBackupTask","error","Error"],"mappings":"AAEA,SAASA,WAAW,EAAEC,eAAe,QAAQ,cAAa;AAI1D,SAASC,oBAAoB,EAAEC,oBAAoB,EAAEC,iBAAiB,QAAQ,kBAAiB;AA0B/F,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,OAAOX,gBAAgBO,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,aAAaf,YAAY,IAAIsB,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,KAAqF;IAErF,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,EACfkB,OAAiF;IAEjF,OAAOJ,iBAAiBhB,SAASY,IAAI;QACnCV;QACAM,QAAQY,SAASZ,UAAU;QAC3Ba,UAAUD,SAASC,YAAY;IACjC;AACF;AAEA,OAAO,eAAeC,0BACpBtB,OAAgB,EAChBY,EAAU,EACVW,MAA2B;IAE3B,MAAMF,WAAWtC,qBAAqBwC;IACtC,OAAOJ,mBAAmBnB,SAASY,IAAI5B,qBAAqBuC,SAAS;QACnEf,QAAQvB,kBAAkBsC;QAC1BF,UAAUA,SAAS5B,MAAM,GAAG,IAAI4B,SAASG,IAAI,CAAC,QAAQT;IACxD;AACF;AAEA,OAAO,eAAeU,eACpBzB,OAAgB,EAChBY,EAAU,EACVc,KAAc;IAEd,OAAOV,iBAAiBhB,SAASY,IAAI;QACnCc,OAAOA,iBAAiBC,QAAQD,MAAMxB,OAAO,GAAGS,OAAOe;QACvDxB,SAAS;QACTM,QAAQ;IACV;AACF"}
|
package/dist/endpoints/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import { createAdminDeleteEndpoint } from './paths/admin-delete';
|
|
|
3
3
|
import { createAdminManualEndpoint } from './paths/admin-manual';
|
|
4
4
|
import { createAdminPreviewEndpoints } from './paths/admin-preview';
|
|
5
5
|
import { createAdminRestoreEndpoint } from './paths/admin-restore';
|
|
6
|
-
import { createAdminSeedEndpoint } from './paths/admin-seed';
|
|
7
6
|
import { createAdminSettingsEndpoints } from './paths/admin-settings';
|
|
8
7
|
import { createAdminTaskEndpoint } from './paths/admin-task';
|
|
9
8
|
import { createAdminValidateBlobTokenEndpoint } from './paths/admin-validate-blob-token';
|
|
@@ -24,10 +23,6 @@ export function createBackupMongodbEndpoints(options) {
|
|
|
24
23
|
createAdminTaskEndpoint(options),
|
|
25
24
|
createAdminValidateBlobTokenEndpoint(options)
|
|
26
25
|
];
|
|
27
|
-
const seed = createAdminSeedEndpoint(options);
|
|
28
|
-
if (seed) {
|
|
29
|
-
endpoints.push(seed);
|
|
30
|
-
}
|
|
31
26
|
return endpoints;
|
|
32
27
|
}
|
|
33
28
|
|
|
@@ -1 +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 {
|
|
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 { 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 return endpoints\n}\n"],"names":["createAdminBackupDownloadEndpoint","createAdminDeleteEndpoint","createAdminManualEndpoint","createAdminPreviewEndpoints","createAdminRestoreEndpoint","createAdminSettingsEndpoints","createAdminTaskEndpoint","createAdminValidateBlobTokenEndpoint","createCronListEndpoint","createCronRestoreEndpoint","createCronRunEndpoint","createBackupMongodbEndpoints","options","endpoints"],"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,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;QACAP,0BAA0BU;QAC1BZ,kCAAkCY;QAClCR,2BAA2BQ;WACxBT,4BAA4BS;WAC5BP,6BAA6BO;QAChCX,0BAA0BW;QAC1BN,wBAAwBM;QACxBL,qCAAqCK;KACtC;IAED,OAAOC;AACT"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { streamBackupBlobForDownload } from '../../core/backupBlobIO';
|
|
2
1
|
import { getResolvedCronBackupSettings, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
2
|
+
import { isBackupStorageConfigured, resolveBackupStorage } from '../../core/storage';
|
|
3
3
|
import { requireBackupAdmin } from '../shared';
|
|
4
4
|
function queryParam(req, key) {
|
|
5
5
|
const fromPayload = req.searchParams.get(key);
|
|
@@ -26,7 +26,7 @@ function queryParam(req, key) {
|
|
|
26
26
|
const { payload } = req;
|
|
27
27
|
const settings = await getResolvedCronBackupSettings(payload);
|
|
28
28
|
const token = resolveBackupBlobToken(settings).trim();
|
|
29
|
-
if (!token) {
|
|
29
|
+
if (!isBackupStorageConfigured(token)) {
|
|
30
30
|
return new Response('Service unavailable', {
|
|
31
31
|
status: 503
|
|
32
32
|
});
|
|
@@ -39,13 +39,14 @@ function queryParam(req, key) {
|
|
|
39
39
|
}
|
|
40
40
|
const blobUrl = queryParam(req, 'url');
|
|
41
41
|
const downloadUrl = queryParam(req, 'downloadUrl');
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const storage = resolveBackupStorage({
|
|
43
|
+
blobAccess: resolveBackupBlobAccess(settings),
|
|
44
|
+
blobToken: token
|
|
45
|
+
});
|
|
46
|
+
const opened = await storage.openDownloadStream({
|
|
45
47
|
downloadUrl: downloadUrl || undefined,
|
|
46
48
|
pathname,
|
|
47
|
-
|
|
48
|
-
token
|
|
49
|
+
url: blobUrl || undefined
|
|
49
50
|
});
|
|
50
51
|
if (!opened) {
|
|
51
52
|
payload.logger.warn({
|
|
@@ -1 +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 {
|
|
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 {\n getResolvedCronBackupSettings,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { isBackupStorageConfigured, resolveBackupStorage } from '../../core/storage'\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 (!isBackupStorageConfigured(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 storage = resolveBackupStorage({\n blobAccess: resolveBackupBlobAccess(settings),\n blobToken: token,\n })\n const opened = await storage.openDownloadStream({\n downloadUrl: downloadUrl || undefined,\n pathname,\n url: blobUrl || undefined,\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":["getResolvedCronBackupSettings","resolveBackupBlobAccess","resolveBackupBlobToken","isBackupStorageConfigured","resolveBackupStorage","requireBackupAdmin","queryParam","req","key","fromPayload","searchParams","get","trim","URL","url","createAdminBackupDownloadEndpoint","options","handler","auth","Response","payload","settings","token","status","pathname","startsWith","blobUrl","downloadUrl","storage","blobAccess","blobToken","opened","openDownloadStream","undefined","logger","warn","filename","split","pop","safeName","replace","stream","headers","contentType","method","path"],"mappings":"AAIA,SACEA,6BAA6B,EAC7BC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,yBAAyB,EAAEC,oBAAoB,QAAQ,qBAAoB;AACpF,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,MAAMrB,8BAA8BoB;YACrD,MAAME,QAAQpB,uBAAuBmB,UAAUT,IAAI;YACnD,IAAI,CAACT,0BAA0BmB,QAAQ;gBACrC,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,UAAUxB,qBAAqB;gBACnCyB,YAAY5B,wBAAwBoB;gBACpCS,WAAWR;YACb;YACA,MAAMS,SAAS,MAAMH,QAAQI,kBAAkB,CAAC;gBAC9CL,aAAaA,eAAeM;gBAC5BT;gBACAV,KAAKY,WAAWO;YAClB;YAEA,IAAI,CAACF,QAAQ;gBACXX,QAAQc,MAAM,CAACC,IAAI,CAAC;oBAAEX;gBAAS,GAAG;gBAClC,OAAO,IAAIL,SAAS,oBAAoB;oBAAEI,QAAQ;gBAAI;YACxD;YAEA,MAAMa,WAAWZ,SAASa,KAAK,CAAC,KAAKC,GAAG,MAAM;YAC9C,MAAMC,WAAWH,SAASI,OAAO,CAAC,YAAY;YAC9C,OAAO,IAAIrB,SAASY,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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { del } from '@vercel/blob';
|
|
2
1
|
import { after } from 'next/server';
|
|
3
|
-
import { getResolvedCronBackupSettings, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
2
|
+
import { getResolvedCronBackupSettings, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
3
|
+
import { isBackupStorageConfigured, resolveBackupStorage } from '../../core/storage';
|
|
4
4
|
import { completeBackupTask, createBackupTask, failBackupTask, updateBackupTask } from '../../core/taskProgress';
|
|
5
5
|
import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
6
6
|
export function createAdminDeleteEndpoint(options) {
|
|
@@ -13,9 +13,14 @@ export function createAdminDeleteEndpoint(options) {
|
|
|
13
13
|
const { payload } = req;
|
|
14
14
|
const settings = await getResolvedCronBackupSettings(payload);
|
|
15
15
|
const blobToken = resolveBackupBlobToken(settings);
|
|
16
|
-
|
|
16
|
+
const blobAccess = resolveBackupBlobAccess(settings);
|
|
17
|
+
if (!isBackupStorageConfigured(blobToken)) {
|
|
17
18
|
return jsonError('Service unavailable', 503);
|
|
18
19
|
}
|
|
20
|
+
const storage = resolveBackupStorage({
|
|
21
|
+
blobAccess,
|
|
22
|
+
blobToken
|
|
23
|
+
});
|
|
19
24
|
const body = await readRequestJson(req);
|
|
20
25
|
const blobUrl = body?.url;
|
|
21
26
|
const pathname = body?.pathname;
|
|
@@ -30,8 +35,9 @@ export function createAdminDeleteEndpoint(options) {
|
|
|
30
35
|
after(updateBackupTask(payload, taskId, {
|
|
31
36
|
message: `Deleting backup ${pathname}`,
|
|
32
37
|
status: 'running'
|
|
33
|
-
}).then(()=>del(
|
|
34
|
-
|
|
38
|
+
}).then(()=>storage.del({
|
|
39
|
+
pathname,
|
|
40
|
+
url: blobUrl
|
|
35
41
|
})).then(()=>completeBackupTask(payload, taskId, `Deleted backup ${pathname}`)).catch(async (error)=>{
|
|
36
42
|
await failBackupTask(payload, taskId, error);
|
|
37
43
|
payload.logger.error({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/endpoints/paths/admin-delete.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/endpoints/paths/admin-delete.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport {\n getResolvedCronBackupSettings,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { isBackupStorageConfigured, resolveBackupStorage } from '../../core/storage'\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 const blobAccess = resolveBackupBlobAccess(settings)\n if (!isBackupStorageConfigured(blobToken)) {\n return jsonError('Service unavailable', 503)\n }\n const storage = resolveBackupStorage({ blobAccess, blobToken })\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(() => storage.del({ pathname, url: blobUrl }))\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":["after","getResolvedCronBackupSettings","resolveBackupBlobAccess","resolveBackupBlobToken","isBackupStorageConfigured","resolveBackupStorage","completeBackupTask","createBackupTask","failBackupTask","updateBackupTask","jsonError","readRequestJson","requireBackupAdmin","createAdminDeleteEndpoint","options","handler","req","auth","Response","payload","settings","blobToken","blobAccess","storage","body","blobUrl","url","pathname","pollSecret","taskId","logger","info","message","status","then","del","catch","error","err","json","method","path"],"mappings":"AAEA,SAASA,KAAK,QAAQ,cAAa;AAInC,SACEC,6BAA6B,EAC7BC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,yBAAyB,EAAEC,oBAAoB,QAAQ,qBAAoB;AACpF,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,MAAMnB,8BAA8BkB;YACrD,MAAME,YAAYlB,uBAAuBiB;YACzC,MAAME,aAAapB,wBAAwBkB;YAC3C,IAAI,CAAChB,0BAA0BiB,YAAY;gBACzC,OAAOX,UAAU,uBAAuB;YAC1C;YACA,MAAMa,UAAUlB,qBAAqB;gBAAEiB;gBAAYD;YAAU;YAE7D,MAAMG,OAAQ,MAAMb,gBAAgBK;YACpC,MAAMS,UAAUD,MAAME;YACtB,MAAMC,WAAWH,MAAMG;YAEvB,IAAI,CAACF,WAAW,CAACE,YAAY,OAAOF,YAAY,YAAY,OAAOE,aAAa,UAAU;gBACxF,OAAOjB,UAAU,2BAA2B;YAC9C;YAEA,MAAM,EAAEkB,UAAU,EAAEC,MAAM,EAAE,GAAG,MAAMtB,iBACnCY,SACA,UACA,CAAC,mBAAmB,EAAEQ,UAAU;YAGlCR,QAAQW,MAAM,CAACC,IAAI,CAAC;gBAAEJ;gBAAUE;YAAO,GAAG;YAE1C7B,MACES,iBAAiBU,SAASU,QAAQ;gBAChCG,SAAS,CAAC,gBAAgB,EAAEL,UAAU;gBACtCM,QAAQ;YACV,GACGC,IAAI,CAAC,IAAMX,QAAQY,GAAG,CAAC;oBAAER;oBAAUD,KAAKD;gBAAQ,IAChDS,IAAI,CAAC,IAAM5B,mBAAmBa,SAASU,QAAQ,CAAC,eAAe,EAAEF,UAAU,GAC3ES,KAAK,CAAC,OAAOC;gBACZ,MAAM7B,eAAeW,SAASU,QAAQQ;gBACtClB,QAAQW,MAAM,CAACO,KAAK,CAClB;oBAAEC,KAAKD;oBAAOV;oBAAUE;gBAAO,GAC/B;YAEJ;YAGJ,OAAOX,SAASqB,IAAI,CAAC;gBAAEX;gBAAYC;YAAO,GAAG;gBAAEI,QAAQ;YAAI;QAC7D;QACAO,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 { completeBackupTask, createBackupTask, failBackupTask } from '../../core/taskProgress';
|
|
5
6
|
import { sanitizeBackupLabel } from '../../utils/index';
|
|
6
7
|
import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
@@ -15,7 +16,7 @@ export function createAdminManualEndpoint(options) {
|
|
|
15
16
|
const settings = await getResolvedCronBackupSettings(payload);
|
|
16
17
|
const blobToken = resolveBackupBlobToken(settings);
|
|
17
18
|
const blobAccess = resolveBackupBlobAccess(settings);
|
|
18
|
-
if (!blobToken) {
|
|
19
|
+
if (!isBackupStorageConfigured(blobToken)) {
|
|
19
20
|
return jsonError('Service unavailable', 503);
|
|
20
21
|
}
|
|
21
22
|
const body = await readRequestJson(req);
|
|
@@ -1 +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 =\n 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,
|
|
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 { isBackupStorageConfigured } from '../../core/storage'\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 (!isBackupStorageConfigured(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 =\n 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","isBackupStorageConfigured","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,yBAAyB,QAAQ,qBAAoB;AAC9D,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,MAAMlB,8BAA8BiB;YACrD,MAAME,YAAYjB,uBAAuBgB;YACzC,MAAME,aAAanB,wBAAwBiB;YAC3C,IAAI,CAACf,0BAA0BgB,YAAY;gBACzC,OAAOX,UAAU,uBAAuB;YAC1C;YAEA,MAAMa,OAAQ,MAAMZ,gBAAgBK;YACpC,MAAMQ,gBAAgBD,MAAME;YAC5B,MAAMC,aACJC,MAAMC,OAAO,CAACJ,iBACZA,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;YAGFrC,MACEC,aAAakB,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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { getResolvedCronBackupSettings, resolveBackupArchiveRead, resolveBackupBlobAccess } from '../../core/backupSettings';
|
|
1
|
+
import { getResolvedCronBackupSettings, resolveBackupArchiveRead, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
2
2
|
import { getBackupSourcePreviewForManual } from '../../core/backupSourcePreview';
|
|
3
3
|
import { getRestorePreviewForAdminRestore } from '../../core/restorePreview';
|
|
4
|
+
import { getBackupStorageKind } from '../../core/storage';
|
|
4
5
|
import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
5
6
|
/**
|
|
6
7
|
* Admin backup source preview and restore archive preview (both POST).
|
|
@@ -62,9 +63,12 @@ import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
|
62
63
|
}
|
|
63
64
|
const settings = await getResolvedCronBackupSettings(payload);
|
|
64
65
|
const backupRead = resolveBackupArchiveRead(settings, body?.pathname);
|
|
65
|
-
|
|
66
|
+
const blobAccess = resolveBackupBlobAccess(settings);
|
|
67
|
+
const blobToken = resolveBackupBlobToken(settings);
|
|
68
|
+
if (getBackupStorageKind() === 'vercel-blob' && blobAccess === 'private' && !backupRead) {
|
|
66
69
|
return jsonError('Missing pathname (required for dedicated backup blob store)', 400);
|
|
67
70
|
}
|
|
71
|
+
const archivePathname = typeof body?.pathname === 'string' && body.pathname.startsWith('backups/') ? body.pathname : undefined;
|
|
68
72
|
const locale = typeof body?.locale === 'string' ? body.locale : undefined;
|
|
69
73
|
const preferredLocales = locale ? [
|
|
70
74
|
locale,
|
|
@@ -76,7 +80,10 @@ import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
|
76
80
|
];
|
|
77
81
|
try {
|
|
78
82
|
const preview = await getRestorePreviewForAdminRestore(payload, url, {
|
|
83
|
+
archivePathname,
|
|
79
84
|
backupRead: backupRead ?? undefined,
|
|
85
|
+
blobAccess,
|
|
86
|
+
blobToken,
|
|
80
87
|
preferredLocales
|
|
81
88
|
});
|
|
82
89
|
return Response.json(preview);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/endpoints/paths/admin-preview.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport {\n getResolvedCronBackupSettings,\n resolveBackupArchiveRead,\n resolveBackupBlobAccess,\n} from '../../core/backupSettings'\nimport { getBackupSourcePreviewForManual } from '../../core/backupSourcePreview'\nimport { getRestorePreviewForAdminRestore } from '../../core/restorePreview'\nimport { jsonError, readRequestJson, requireBackupAdmin } from '../shared'\n\n/**\n * Admin backup source preview and restore archive preview (both POST).\n */\nexport function createAdminPreviewEndpoints(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 body = (await readRequestJson(req)) as { locale?: string }\n const locale = typeof body?.locale === 'string' ? body.locale : undefined\n const preferredLocales = locale ? [locale, 'de', 'en'] : ['de', 'en']\n\n try {\n const preview = await getBackupSourcePreviewForManual(payload, { preferredLocales })\n return Response.json(preview)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Preview failed'\n payload.logger.error({ err: error }, '[backup-endpoint] Backup source preview failed')\n return Response.json({ error: message }, { status: 422 })\n }\n },\n method: 'post',\n path: '/backup-mongodb/admin/backup-preview',\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 {\n locale?: string\n pathname?: string\n url?: string\n }\n const url = body?.url\n\n if (!url || typeof url !== 'string') {\n return jsonError('Missing url', 400)\n }\n\n try {\n new URL(url)\n } catch {\n return jsonError('Invalid url', 400)\n }\n\n const settings = await getResolvedCronBackupSettings(payload)\n const backupRead = resolveBackupArchiveRead(settings, body?.pathname)\n
|
|
1
|
+
{"version":3,"sources":["../../../src/endpoints/paths/admin-preview.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport {\n getResolvedCronBackupSettings,\n resolveBackupArchiveRead,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { getBackupSourcePreviewForManual } from '../../core/backupSourcePreview'\nimport { getRestorePreviewForAdminRestore } from '../../core/restorePreview'\nimport { getBackupStorageKind } from '../../core/storage'\nimport { jsonError, readRequestJson, requireBackupAdmin } from '../shared'\n\n/**\n * Admin backup source preview and restore archive preview (both POST).\n */\nexport function createAdminPreviewEndpoints(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 body = (await readRequestJson(req)) as { locale?: string }\n const locale = typeof body?.locale === 'string' ? body.locale : undefined\n const preferredLocales = locale ? [locale, 'de', 'en'] : ['de', 'en']\n\n try {\n const preview = await getBackupSourcePreviewForManual(payload, { preferredLocales })\n return Response.json(preview)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Preview failed'\n payload.logger.error({ err: error }, '[backup-endpoint] Backup source preview failed')\n return Response.json({ error: message }, { status: 422 })\n }\n },\n method: 'post',\n path: '/backup-mongodb/admin/backup-preview',\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 {\n locale?: string\n pathname?: string\n url?: string\n }\n const url = body?.url\n\n if (!url || typeof url !== 'string') {\n return jsonError('Missing url', 400)\n }\n\n try {\n new URL(url)\n } catch {\n return jsonError('Invalid url', 400)\n }\n\n const settings = await getResolvedCronBackupSettings(payload)\n const backupRead = resolveBackupArchiveRead(settings, body?.pathname)\n const blobAccess = resolveBackupBlobAccess(settings)\n const blobToken = resolveBackupBlobToken(settings)\n if (getBackupStorageKind() === 'vercel-blob' && blobAccess === 'private' && !backupRead) {\n return jsonError('Missing pathname (required for dedicated backup blob store)', 400)\n }\n\n const archivePathname =\n typeof body?.pathname === 'string' && body.pathname.startsWith('backups/') ?\n body.pathname\n : undefined\n\n const locale = typeof body?.locale === 'string' ? body.locale : undefined\n const preferredLocales = locale ? [locale, 'de', 'en'] : ['de', 'en']\n\n try {\n const preview = await getRestorePreviewForAdminRestore(payload, url, {\n archivePathname,\n backupRead: backupRead ?? undefined,\n blobAccess,\n blobToken,\n preferredLocales,\n })\n return Response.json(preview)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Preview failed'\n payload.logger.error({ err: error, url }, '[backup-endpoint] Restore preview failed')\n return Response.json({ error: message }, { status: 422 })\n }\n },\n method: 'post',\n path: '/backup-mongodb/admin/restore-preview',\n },\n ]\n}\n"],"names":["getResolvedCronBackupSettings","resolveBackupArchiveRead","resolveBackupBlobAccess","resolveBackupBlobToken","getBackupSourcePreviewForManual","getRestorePreviewForAdminRestore","getBackupStorageKind","jsonError","readRequestJson","requireBackupAdmin","createAdminPreviewEndpoints","options","handler","req","auth","Response","payload","body","locale","undefined","preferredLocales","preview","json","error","message","Error","logger","err","status","method","path","url","URL","settings","backupRead","pathname","blobAccess","blobToken","archivePathname","startsWith"],"mappings":"AAIA,SACEA,6BAA6B,EAC7BC,wBAAwB,EACxBC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,+BAA+B,QAAQ,iCAAgC;AAChF,SAASC,gCAAgC,QAAQ,4BAA2B;AAC5E,SAASC,oBAAoB,QAAQ,qBAAoB;AACzD,SAASC,SAAS,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,YAAW;AAE1E;;CAEC,GACD,OAAO,SAASC,4BAA4BC,OAA4B;IACtE,OAAO;QACL;YACEC,SAAS,OAAOC;gBACd,MAAMC,OAAO,MAAML,mBAAmBI,KAAKF;gBAC3C,IAAIG,gBAAgBC,UAAU;oBAC5B,OAAOD;gBACT;gBAEA,MAAM,EAAEE,OAAO,EAAE,GAAGH;gBACpB,MAAMI,OAAQ,MAAMT,gBAAgBK;gBACpC,MAAMK,SAAS,OAAOD,MAAMC,WAAW,WAAWD,KAAKC,MAAM,GAAGC;gBAChE,MAAMC,mBAAmBF,SAAS;oBAACA;oBAAQ;oBAAM;iBAAK,GAAG;oBAAC;oBAAM;iBAAK;gBAErE,IAAI;oBACF,MAAMG,UAAU,MAAMjB,gCAAgCY,SAAS;wBAAEI;oBAAiB;oBAClF,OAAOL,SAASO,IAAI,CAACD;gBACvB,EAAE,OAAOE,OAAO;oBACd,MAAMC,UAAUD,iBAAiBE,QAAQF,MAAMC,OAAO,GAAG;oBACzDR,QAAQU,MAAM,CAACH,KAAK,CAAC;wBAAEI,KAAKJ;oBAAM,GAAG;oBACrC,OAAOR,SAASO,IAAI,CAAC;wBAAEC,OAAOC;oBAAQ,GAAG;wBAAEI,QAAQ;oBAAI;gBACzD;YACF;YACAC,QAAQ;YACRC,MAAM;QACR;QACA;YACElB,SAAS,OAAOC;gBACd,MAAMC,OAAO,MAAML,mBAAmBI,KAAKF;gBAC3C,IAAIG,gBAAgBC,UAAU;oBAC5B,OAAOD;gBACT;gBAEA,MAAM,EAAEE,OAAO,EAAE,GAAGH;gBACpB,MAAMI,OAAQ,MAAMT,gBAAgBK;gBAKpC,MAAMkB,MAAMd,MAAMc;gBAElB,IAAI,CAACA,OAAO,OAAOA,QAAQ,UAAU;oBACnC,OAAOxB,UAAU,eAAe;gBAClC;gBAEA,IAAI;oBACF,IAAIyB,IAAID;gBACV,EAAE,OAAM;oBACN,OAAOxB,UAAU,eAAe;gBAClC;gBAEA,MAAM0B,WAAW,MAAMjC,8BAA8BgB;gBACrD,MAAMkB,aAAajC,yBAAyBgC,UAAUhB,MAAMkB;gBAC5D,MAAMC,aAAalC,wBAAwB+B;gBAC3C,MAAMI,YAAYlC,uBAAuB8B;gBACzC,IAAI3B,2BAA2B,iBAAiB8B,eAAe,aAAa,CAACF,YAAY;oBACvF,OAAO3B,UAAU,+DAA+D;gBAClF;gBAEA,MAAM+B,kBACJ,OAAOrB,MAAMkB,aAAa,YAAYlB,KAAKkB,QAAQ,CAACI,UAAU,CAAC,cAC7DtB,KAAKkB,QAAQ,GACbhB;gBAEJ,MAAMD,SAAS,OAAOD,MAAMC,WAAW,WAAWD,KAAKC,MAAM,GAAGC;gBAChE,MAAMC,mBAAmBF,SAAS;oBAACA;oBAAQ;oBAAM;iBAAK,GAAG;oBAAC;oBAAM;iBAAK;gBAErE,IAAI;oBACF,MAAMG,UAAU,MAAMhB,iCAAiCW,SAASe,KAAK;wBACnEO;wBACAJ,YAAYA,cAAcf;wBAC1BiB;wBACAC;wBACAjB;oBACF;oBACA,OAAOL,SAASO,IAAI,CAACD;gBACvB,EAAE,OAAOE,OAAO;oBACd,MAAMC,UAAUD,iBAAiBE,QAAQF,MAAMC,OAAO,GAAG;oBACzDR,QAAQU,MAAM,CAACH,KAAK,CAAC;wBAAEI,KAAKJ;wBAAOQ;oBAAI,GAAG;oBAC1C,OAAOhB,SAASO,IAAI,CAAC;wBAAEC,OAAOC;oBAAQ,GAAG;wBAAEI,QAAQ;oBAAI;gBACzD;YACF;YACAC,QAAQ;YACRC,MAAM;QACR;KACD;AACH"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { after } from 'next/server';
|
|
2
2
|
import { getResolvedCronBackupSettings, resolveBackupArchiveRead, resolveBackupBlobAccess, resolveBackupBlobToken } from '../../core/backupSettings';
|
|
3
3
|
import { restoreBackup } from '../../core/restore';
|
|
4
|
-
import {
|
|
4
|
+
import { getBackupStorageKind, isBackupStorageConfigured } from '../../core/storage';
|
|
5
|
+
import { completeRestoreBackupTask, createBackupTask, failBackupTask } from '../../core/taskProgress';
|
|
5
6
|
import { jsonError, readRequestJson, requireBackupAdmin } from '../shared';
|
|
6
7
|
export function createAdminRestoreEndpoint(options) {
|
|
7
8
|
return {
|
|
@@ -14,7 +15,7 @@ export function createAdminRestoreEndpoint(options) {
|
|
|
14
15
|
const settings = await getResolvedCronBackupSettings(payload);
|
|
15
16
|
const blobToken = resolveBackupBlobToken(settings);
|
|
16
17
|
const blobAccess = resolveBackupBlobAccess(settings);
|
|
17
|
-
if (!blobToken) {
|
|
18
|
+
if (!isBackupStorageConfigured(blobToken)) {
|
|
18
19
|
return jsonError('Service unavailable', 503);
|
|
19
20
|
}
|
|
20
21
|
const body = await readRequestJson(req);
|
|
@@ -31,9 +32,10 @@ export function createAdminRestoreEndpoint(options) {
|
|
|
31
32
|
return jsonError('Invalid url', 400);
|
|
32
33
|
}
|
|
33
34
|
const backupRead = resolveBackupArchiveRead(settings, pathname);
|
|
34
|
-
if (blobAccess === 'private' && !backupRead) {
|
|
35
|
+
if (getBackupStorageKind() === 'vercel-blob' && blobAccess === 'private' && !backupRead) {
|
|
35
36
|
return jsonError('Missing pathname (required for dedicated backup blob store)', 400);
|
|
36
37
|
}
|
|
38
|
+
const archivePathname = typeof pathname === 'string' && pathname.startsWith('backups/') ? pathname : undefined;
|
|
37
39
|
const { pollSecret, taskId } = await createBackupTask(payload, 'restore', 'Restore queued');
|
|
38
40
|
payload.logger.info({
|
|
39
41
|
taskId,
|
|
@@ -47,11 +49,12 @@ export function createAdminRestoreEndpoint(options) {
|
|
|
47
49
|
];
|
|
48
50
|
const restoreArchiveMedia = body?.restoreArchiveMedia !== false;
|
|
49
51
|
after(restoreBackup(payload, url, collectionBlacklist, false, taskId, {
|
|
52
|
+
archivePathname,
|
|
50
53
|
backupRead: backupRead ?? undefined,
|
|
51
54
|
blobAccess,
|
|
52
55
|
blobToken,
|
|
53
56
|
restoreArchiveMedia
|
|
54
|
-
}).then(()=>
|
|
57
|
+
}).then((result)=>completeRestoreBackupTask(payload, taskId, result)).catch(async (error)=>{
|
|
55
58
|
await failBackupTask(payload, taskId, error);
|
|
56
59
|
payload.logger.error({
|
|
57
60
|
err: error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/endpoints/paths/admin-restore.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport {\n getResolvedCronBackupSettings,\n resolveBackupArchiveRead,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { restoreBackup } from '../../core/restore'\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/endpoints/paths/admin-restore.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport { after } from 'next/server'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport {\n getResolvedCronBackupSettings,\n resolveBackupArchiveRead,\n resolveBackupBlobAccess,\n resolveBackupBlobToken,\n} from '../../core/backupSettings'\nimport { restoreBackup } from '../../core/restore'\nimport { getBackupStorageKind, isBackupStorageConfigured } from '../../core/storage'\nimport {\n completeRestoreBackupTask,\n createBackupTask,\n failBackupTask,\n} from '../../core/taskProgress'\nimport { jsonError, readRequestJson, requireBackupAdmin } from '../shared'\n\nexport function createAdminRestoreEndpoint(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 (!isBackupStorageConfigured(blobToken)) {\n return jsonError('Service unavailable', 503)\n }\n\n const body = (await readRequestJson(req)) as Record<string, unknown>\n const pathname = body?.pathname\n const url = body?.url\n const clientSkipRaw = body?.skipCollections\n const clientSkip =\n Array.isArray(clientSkipRaw) ?\n clientSkipRaw.filter(\n (x: unknown): x is string => typeof x === 'string' && x.length > 0 && x.length < 512,\n )\n : []\n\n if (!url || typeof url !== 'string') {\n return jsonError('Missing url', 400)\n }\n try {\n new URL(url)\n } catch {\n return jsonError('Invalid url', 400)\n }\n\n const backupRead = resolveBackupArchiveRead(settings, pathname)\n if (getBackupStorageKind() === 'vercel-blob' && blobAccess === 'private' && !backupRead) {\n return jsonError('Missing pathname (required for dedicated backup blob store)', 400)\n }\n\n const archivePathname =\n typeof pathname === 'string' && pathname.startsWith('backups/') ? pathname : undefined\n\n const { pollSecret, taskId } = await createBackupTask(payload, 'restore', 'Restore queued')\n\n payload.logger.info({ taskId, url }, '[backup-endpoint] Restore queued')\n\n const collectionBlacklist = [...new Set(['backup-tasks', ...clientSkip])]\n const restoreArchiveMedia = body?.restoreArchiveMedia !== false\n\n after(\n restoreBackup(payload, url, collectionBlacklist, false, taskId, {\n archivePathname,\n backupRead: backupRead ?? undefined,\n blobAccess,\n blobToken,\n restoreArchiveMedia,\n })\n .then((result) => completeRestoreBackupTask(payload, taskId, result))\n .catch(async (error) => {\n await failBackupTask(payload, taskId, error)\n payload.logger.error({ err: error, taskId, url }, '[backup-endpoint] Restore failed')\n }),\n )\n\n return Response.json({ pollSecret, taskId }, { status: 202 })\n },\n method: 'post',\n path: '/backup-mongodb/admin/restore',\n }\n}\n"],"names":["after","getResolvedCronBackupSettings","resolveBackupArchiveRead","resolveBackupBlobAccess","resolveBackupBlobToken","restoreBackup","getBackupStorageKind","isBackupStorageConfigured","completeRestoreBackupTask","createBackupTask","failBackupTask","jsonError","readRequestJson","requireBackupAdmin","createAdminRestoreEndpoint","options","handler","req","auth","Response","payload","settings","blobToken","blobAccess","body","pathname","url","clientSkipRaw","skipCollections","clientSkip","Array","isArray","filter","x","length","URL","backupRead","archivePathname","startsWith","undefined","pollSecret","taskId","logger","info","collectionBlacklist","Set","restoreArchiveMedia","then","result","catch","error","err","json","status","method","path"],"mappings":"AAEA,SAASA,KAAK,QAAQ,cAAa;AAInC,SACEC,6BAA6B,EAC7BC,wBAAwB,EACxBC,uBAAuB,EACvBC,sBAAsB,QACjB,4BAA2B;AAClC,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,oBAAoB,EAAEC,yBAAyB,QAAQ,qBAAoB;AACpF,SACEC,yBAAyB,EACzBC,gBAAgB,EAChBC,cAAc,QACT,0BAAyB;AAChC,SAASC,SAAS,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,YAAW;AAE1E,OAAO,SAASC,2BAA2BC,OAA4B;IACrE,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,MAAMpB,8BAA8BmB;YACrD,MAAME,YAAYlB,uBAAuBiB;YACzC,MAAME,aAAapB,wBAAwBkB;YAC3C,IAAI,CAACd,0BAA0Be,YAAY;gBACzC,OAAOX,UAAU,uBAAuB;YAC1C;YAEA,MAAMa,OAAQ,MAAMZ,gBAAgBK;YACpC,MAAMQ,WAAWD,MAAMC;YACvB,MAAMC,MAAMF,MAAME;YAClB,MAAMC,gBAAgBH,MAAMI;YAC5B,MAAMC,aACJC,MAAMC,OAAO,CAACJ,iBACZA,cAAcK,MAAM,CAClB,CAACC,IAA4B,OAAOA,MAAM,YAAYA,EAAEC,MAAM,GAAG,KAAKD,EAAEC,MAAM,GAAG,OAEnF,EAAE;YAEN,IAAI,CAACR,OAAO,OAAOA,QAAQ,UAAU;gBACnC,OAAOf,UAAU,eAAe;YAClC;YACA,IAAI;gBACF,IAAIwB,IAAIT;YACV,EAAE,OAAM;gBACN,OAAOf,UAAU,eAAe;YAClC;YAEA,MAAMyB,aAAalC,yBAAyBmB,UAAUI;YACtD,IAAInB,2BAA2B,iBAAiBiB,eAAe,aAAa,CAACa,YAAY;gBACvF,OAAOzB,UAAU,+DAA+D;YAClF;YAEA,MAAM0B,kBACJ,OAAOZ,aAAa,YAAYA,SAASa,UAAU,CAAC,cAAcb,WAAWc;YAE/E,MAAM,EAAEC,UAAU,EAAEC,MAAM,EAAE,GAAG,MAAMhC,iBAAiBW,SAAS,WAAW;YAE1EA,QAAQsB,MAAM,CAACC,IAAI,CAAC;gBAAEF;gBAAQf;YAAI,GAAG;YAErC,MAAMkB,sBAAsB;mBAAI,IAAIC,IAAI;oBAAC;uBAAmBhB;iBAAW;aAAE;YACzE,MAAMiB,sBAAsBtB,MAAMsB,wBAAwB;YAE1D9C,MACEK,cAAce,SAASM,KAAKkB,qBAAqB,OAAOH,QAAQ;gBAC9DJ;gBACAD,YAAYA,cAAcG;gBAC1BhB;gBACAD;gBACAwB;YACF,GACGC,IAAI,CAAC,CAACC,SAAWxC,0BAA0BY,SAASqB,QAAQO,SAC5DC,KAAK,CAAC,OAAOC;gBACZ,MAAMxC,eAAeU,SAASqB,QAAQS;gBACtC9B,QAAQsB,MAAM,CAACQ,KAAK,CAAC;oBAAEC,KAAKD;oBAAOT;oBAAQf;gBAAI,GAAG;YACpD;YAGJ,OAAOP,SAASiC,IAAI,CAAC;gBAAEZ;gBAAYC;YAAO,GAAG;gBAAEY,QAAQ;YAAI;QAC7D;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -3,6 +3,7 @@ import { after } from 'next/server';
|
|
|
3
3
|
import { transferBackupBlobsToToken } from '../../core/backupBlobTransfer';
|
|
4
4
|
import { BACKUP_SETTINGS_SLUG, getResolvedCronBackupSettings, normalizeSkipMongoCollections, resolveBackupBlobAccess, resolveBackupBlobToken, toPayloadSkipRows } from '../../core/backupSettings';
|
|
5
5
|
import { validateBackupBlobToken } from '../../core/blobTokenValidate';
|
|
6
|
+
import { getBackupStorageKind, isS3Configured, loadS3Config } from '../../core/storage';
|
|
6
7
|
import { completeBackupTask, createBackupTask, failBackupTask, updateBackupTask } from '../../core/taskProgress';
|
|
7
8
|
import { describeCronSchedule, readVercelBackupCronFromRepo } from '../../core/vercelBackupCron';
|
|
8
9
|
import { maskBlobReadWriteToken, shouldPreserveBackupBlobTokenField } from '../../utils/maskBlobToken';
|
|
@@ -13,6 +14,24 @@ function clampBackupsToKeep(n) {
|
|
|
13
14
|
}
|
|
14
15
|
return Math.min(365, Math.max(1, Math.floor(n)));
|
|
15
16
|
}
|
|
17
|
+
/** Active backup target + a non-secret S3 summary for the admin UI. */ function backupStorageSummary() {
|
|
18
|
+
const kind = getBackupStorageKind();
|
|
19
|
+
if (kind !== 's3' || !isS3Configured()) {
|
|
20
|
+
return {
|
|
21
|
+
kind
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const cfg = loadS3Config();
|
|
25
|
+
return {
|
|
26
|
+
kind,
|
|
27
|
+
s3: {
|
|
28
|
+
bucket: cfg.bucket,
|
|
29
|
+
endpoint: cfg.endpoint,
|
|
30
|
+
prefix: cfg.prefix || undefined,
|
|
31
|
+
region: cfg.region
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
16
35
|
function buildSettingsJson(stored, options, vercelCron, humanDescription, transfer) {
|
|
17
36
|
const t = transfer ?? {
|
|
18
37
|
deferred: false,
|
|
@@ -39,6 +58,7 @@ function buildSettingsJson(stored, options, vercelCron, humanDescription, transf
|
|
|
39
58
|
includeMediaForCron: stored.includeMediaForCron,
|
|
40
59
|
pluginBackupsToKeepOverride: typeof options.backupsToKeep === 'number',
|
|
41
60
|
skipMongoCollections: stored.skipMongoCollections,
|
|
61
|
+
storage: backupStorageSummary(),
|
|
42
62
|
transfer: t
|
|
43
63
|
};
|
|
44
64
|
}
|
|
@@ -82,8 +102,9 @@ export function createAdminSettingsEndpoints(options) {
|
|
|
82
102
|
const tokenForDb = preserveTokenField ? previousBackupBlobToken : requestedTrimmed;
|
|
83
103
|
// Access detection: re-probe when the token actually changes; preserve existing otherwise;
|
|
84
104
|
// clear when the override is removed (falls back to heuristic on the default env token).
|
|
105
|
+
// Skip Vercel validation when the active target is S3.
|
|
85
106
|
let backupBlobAccessForDb = stored.backupBlobAccess;
|
|
86
|
-
if (!preserveTokenField) {
|
|
107
|
+
if (!preserveTokenField && getBackupStorageKind() !== 's3') {
|
|
87
108
|
if (tokenForDb.length === 0) {
|
|
88
109
|
backupBlobAccessForDb = null;
|
|
89
110
|
} else {
|
|
@@ -134,12 +155,24 @@ export function createAdminSettingsEndpoints(options) {
|
|
|
134
155
|
}
|
|
135
156
|
const vercelCron = readVercelBackupCronFromRepo();
|
|
136
157
|
const humanDescription = vercelCron?.schedule != null ? describeCronSchedule(vercelCron.schedule) : null;
|
|
158
|
+
// Blob transfer is a Vercel-specific operation (moving blobs between tokens).
|
|
159
|
+
// Skip entirely when the active target is S3.
|
|
160
|
+
if (getBackupStorageKind() === 's3' || !transferBackupBlobs) {
|
|
161
|
+
return Response.json(buildSettingsJson(stored, options, vercelCron, humanDescription, {
|
|
162
|
+
deferred: false,
|
|
163
|
+
failed: 0,
|
|
164
|
+
performed: false,
|
|
165
|
+
skipped: 0,
|
|
166
|
+
total: 0,
|
|
167
|
+
transferred: 0
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
137
170
|
const newBlobToken = stored.backupBlobReadWriteToken.trim();
|
|
138
171
|
// Read blobs from the *previous* store when rotating tokens; otherwise first-time setup
|
|
139
172
|
// reads from BLOB_READ_WRITE_TOKEN (default Vercel store).
|
|
140
173
|
const sourceTokenForTransfer = previousBackupBlobToken.length > 0 ? previousBackupBlobToken : (process.env.BLOB_READ_WRITE_TOKEN || '').trim();
|
|
141
174
|
const shouldTransferToNewBlobToken = newBlobToken.length > 0 && sourceTokenForTransfer.length > 0 && newBlobToken !== sourceTokenForTransfer;
|
|
142
|
-
if (!
|
|
175
|
+
if (!shouldTransferToNewBlobToken) {
|
|
143
176
|
return Response.json(buildSettingsJson(stored, options, vercelCron, humanDescription, {
|
|
144
177
|
deferred: false,
|
|
145
178
|
failed: 0,
|