@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,99 @@
|
|
|
1
|
+
const SEPARATOR = '---';
|
|
2
|
+
/**
|
|
3
|
+
* Last segment is `{collectionCount}-{timestampMs}` (new) or legacy `{timestampMs}` only.
|
|
4
|
+
* A hyphenated tail that does not pass {@link isPlausibleNewFormatTail} is treated as legacy
|
|
5
|
+
* (whole `last` string, no collection count) so older blobs are never mis-parsed.
|
|
6
|
+
*/ const NEW_NAME_TAIL = /^(\d+)-(\d{10,})$/;
|
|
7
|
+
const NEW_TAIL_MAX_COLLECTIONS = 100_000;
|
|
8
|
+
/** Max length for a user-supplied manual backup label (after sanitization). */ export const BACKUP_LABEL_MAX_LENGTH = 64;
|
|
9
|
+
function isPlausibleNewFormatTail(collectionCount, timestampMs) {
|
|
10
|
+
return Number.isFinite(collectionCount) && Number.isFinite(timestampMs) && collectionCount >= 0 && collectionCount <= NEW_TAIL_MAX_COLLECTIONS && timestampMs > 0 && timestampMs < 1e15;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a user-supplied manual backup label so it is safe to embed as a
|
|
14
|
+
* blob-name segment. Trims, collapses whitespace, removes characters that would
|
|
15
|
+
* confuse the separator, and enforces {@link BACKUP_LABEL_MAX_LENGTH}.
|
|
16
|
+
* Returns an empty string when nothing usable remains.
|
|
17
|
+
*/ export function sanitizeBackupLabel(raw) {
|
|
18
|
+
if (typeof raw !== 'string') {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
const collapsed = raw.replace(/\s+/g, ' ').trim();
|
|
22
|
+
if (!collapsed) {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
// The blob-name separator is three hyphens; collapse any hyphen run in the
|
|
26
|
+
// label to a single hyphen so round-trip parsing stays unambiguous.
|
|
27
|
+
const deHyphen = collapsed.replace(/-{2,}/g, '-');
|
|
28
|
+
return deHyphen.slice(0, BACKUP_LABEL_MAX_LENGTH).trim();
|
|
29
|
+
}
|
|
30
|
+
export function transformBlobName(blobName) {
|
|
31
|
+
const fileType = blobName.endsWith('json') ? 'json' : blobName.endsWith('tar.gz') ? 'tar.gz' : 'na';
|
|
32
|
+
const [type = '', dbName = '', hostname = '', last = '', labelEncoded = ''] = blobName.replace(/\.(?:json|tar\.gz)$/, '').replace(/^backups\//, '').split(SEPARATOR);
|
|
33
|
+
let label;
|
|
34
|
+
if (labelEncoded) {
|
|
35
|
+
try {
|
|
36
|
+
const decoded = decodeURIComponent(labelEncoded);
|
|
37
|
+
if (decoded) {
|
|
38
|
+
label = decoded;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
label = labelEncoded;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const m = NEW_NAME_TAIL.exec(last);
|
|
45
|
+
if (m) {
|
|
46
|
+
const collectionCount = Number(m[1]);
|
|
47
|
+
const timestampMs = Number(m[2]);
|
|
48
|
+
if (isPlausibleNewFormatTail(collectionCount, timestampMs)) {
|
|
49
|
+
return {
|
|
50
|
+
type,
|
|
51
|
+
collectionCount,
|
|
52
|
+
date: m[2],
|
|
53
|
+
dbName: decodeURIComponent(dbName),
|
|
54
|
+
fileType,
|
|
55
|
+
hostname: decodeURIComponent(hostname),
|
|
56
|
+
label
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
type,
|
|
62
|
+
date: last,
|
|
63
|
+
dbName: decodeURIComponent(dbName),
|
|
64
|
+
fileType,
|
|
65
|
+
hostname: decodeURIComponent(hostname),
|
|
66
|
+
label
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Backup blob pathname segment (without `backups/` prefix or extension).
|
|
71
|
+
* New format: `{collectionCount}-{timestampMs}` encodes included collection count + backup time.
|
|
72
|
+
* When a non-empty `label` is supplied it is appended as a separate URL-encoded segment
|
|
73
|
+
* (`---{encodedLabel}`) so older parsers that only read the first 4 segments still work.
|
|
74
|
+
*/ export function createBlobName(type, dbName, hostname, collectionCount, timestampMs, fileType, label) {
|
|
75
|
+
const tail = `${Math.max(0, Math.floor(collectionCount))}-${Math.floor(timestampMs)}`;
|
|
76
|
+
const sanitizedLabel = sanitizeBackupLabel(label);
|
|
77
|
+
const labelSegment = sanitizedLabel ? `${SEPARATOR}${encodeURIComponent(sanitizedLabel)}` : '';
|
|
78
|
+
return `${type}${SEPARATOR}${encodeURIComponent(dbName)}${SEPARATOR}${encodeURIComponent(hostname)}${SEPARATOR}${tail}${labelSegment}.${fileType}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Prefer timestamp encoded in filename (stable across blob store migration); else blob upload time.
|
|
82
|
+
* Legacy tails are digits-only epoch values; if {@link TransformBlobNameResult.collectionCount}
|
|
83
|
+
* is absent, values below 1e12 are treated as **Unix seconds** (common older convention), else ms.
|
|
84
|
+
*/ export function getBackupSortTimeMs(parsed, uploadedAt) {
|
|
85
|
+
if (!parsed.date || !/^\d+$/.test(parsed.date)) {
|
|
86
|
+
return uploadedAt.getTime();
|
|
87
|
+
}
|
|
88
|
+
const n = Number(parsed.date);
|
|
89
|
+
if (!Number.isFinite(n)) {
|
|
90
|
+
return uploadedAt.getTime();
|
|
91
|
+
}
|
|
92
|
+
const isLegacyTimestampOnly = parsed.collectionCount === undefined;
|
|
93
|
+
if (isLegacyTimestampOnly && n < 1e12) {
|
|
94
|
+
return n * 1000;
|
|
95
|
+
}
|
|
96
|
+
return n;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=blobName.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/blobName.ts"],"sourcesContent":["const SEPARATOR = '---'\n\n/**\n * Last segment is `{collectionCount}-{timestampMs}` (new) or legacy `{timestampMs}` only.\n * A hyphenated tail that does not pass {@link isPlausibleNewFormatTail} is treated as legacy\n * (whole `last` string, no collection count) so older blobs are never mis-parsed.\n */\nconst NEW_NAME_TAIL = /^(\\d+)-(\\d{10,})$/\n\nconst NEW_TAIL_MAX_COLLECTIONS = 100_000\n\n/** Max length for a user-supplied manual backup label (after sanitization). */\nexport const BACKUP_LABEL_MAX_LENGTH = 64\n\nfunction isPlausibleNewFormatTail(collectionCount: number, timestampMs: number): boolean {\n return (\n Number.isFinite(collectionCount) &&\n Number.isFinite(timestampMs) &&\n collectionCount >= 0 &&\n collectionCount <= NEW_TAIL_MAX_COLLECTIONS &&\n timestampMs > 0 &&\n timestampMs < 1e15\n )\n}\n\n/**\n * Normalize a user-supplied manual backup label so it is safe to embed as a\n * blob-name segment. Trims, collapses whitespace, removes characters that would\n * confuse the separator, and enforces {@link BACKUP_LABEL_MAX_LENGTH}.\n * Returns an empty string when nothing usable remains.\n */\nexport function sanitizeBackupLabel(raw: unknown): string {\n if (typeof raw !== 'string') {return ''}\n const collapsed = raw.replace(/\\s+/g, ' ').trim()\n if (!collapsed) {return ''}\n // The blob-name separator is three hyphens; collapse any hyphen run in the\n // label to a single hyphen so round-trip parsing stays unambiguous.\n const deHyphen = collapsed.replace(/-{2,}/g, '-')\n return deHyphen.slice(0, BACKUP_LABEL_MAX_LENGTH).trim()\n}\n\nexport interface TransformBlobNameResult {\n collectionCount?: number\n date: string\n dbName: string\n fileType: 'json' | 'na' | 'tar.gz'\n hostname: string\n label?: string\n type: string\n}\n\nexport function transformBlobName(blobName: string): TransformBlobNameResult {\n const fileType: 'json' | 'na' | 'tar.gz' = blobName.endsWith('json')\n ? 'json'\n : blobName.endsWith('tar.gz')\n ? 'tar.gz'\n : 'na'\n const [type = '', dbName = '', hostname = '', last = '', labelEncoded = ''] = blobName\n .replace(/\\.(?:json|tar\\.gz)$/, '')\n .replace(/^backups\\//, '')\n .split(SEPARATOR)\n\n let label: string | undefined\n if (labelEncoded) {\n try {\n const decoded = decodeURIComponent(labelEncoded)\n if (decoded) {label = decoded}\n } catch {\n label = labelEncoded\n }\n }\n\n const m = NEW_NAME_TAIL.exec(last)\n if (m) {\n const collectionCount = Number(m[1])\n const timestampMs = Number(m[2])\n if (isPlausibleNewFormatTail(collectionCount, timestampMs)) {\n return {\n type,\n collectionCount,\n date: m[2],\n dbName: decodeURIComponent(dbName),\n fileType,\n hostname: decodeURIComponent(hostname),\n label,\n }\n }\n }\n\n return {\n type,\n date: last,\n dbName: decodeURIComponent(dbName),\n fileType,\n hostname: decodeURIComponent(hostname),\n label,\n }\n}\n\n/**\n * Backup blob pathname segment (without `backups/` prefix or extension).\n * New format: `{collectionCount}-{timestampMs}` encodes included collection count + backup time.\n * When a non-empty `label` is supplied it is appended as a separate URL-encoded segment\n * (`---{encodedLabel}`) so older parsers that only read the first 4 segments still work.\n */\nexport function createBlobName(\n type: string,\n dbName: string,\n hostname: string,\n collectionCount: number,\n timestampMs: number,\n fileType: 'json' | 'tar.gz',\n label?: string,\n): string {\n const tail = `${Math.max(0, Math.floor(collectionCount))}-${Math.floor(timestampMs)}`\n const sanitizedLabel = sanitizeBackupLabel(label)\n const labelSegment = sanitizedLabel ? `${SEPARATOR}${encodeURIComponent(sanitizedLabel)}` : ''\n return `${type}${SEPARATOR}${encodeURIComponent(dbName)}${SEPARATOR}${encodeURIComponent(hostname)}${SEPARATOR}${tail}${labelSegment}.${fileType}`\n}\n\n/**\n * Prefer timestamp encoded in filename (stable across blob store migration); else blob upload time.\n * Legacy tails are digits-only epoch values; if {@link TransformBlobNameResult.collectionCount}\n * is absent, values below 1e12 are treated as **Unix seconds** (common older convention), else ms.\n */\nexport function getBackupSortTimeMs(parsed: TransformBlobNameResult, uploadedAt: Date): number {\n if (!parsed.date || !/^\\d+$/.test(parsed.date)) {\n return uploadedAt.getTime()\n }\n const n = Number(parsed.date)\n if (!Number.isFinite(n)) {\n return uploadedAt.getTime()\n }\n const isLegacyTimestampOnly = parsed.collectionCount === undefined\n if (isLegacyTimestampOnly && n < 1e12) {\n return n * 1000\n }\n return n\n}\n"],"names":["SEPARATOR","NEW_NAME_TAIL","NEW_TAIL_MAX_COLLECTIONS","BACKUP_LABEL_MAX_LENGTH","isPlausibleNewFormatTail","collectionCount","timestampMs","Number","isFinite","sanitizeBackupLabel","raw","collapsed","replace","trim","deHyphen","slice","transformBlobName","blobName","fileType","endsWith","type","dbName","hostname","last","labelEncoded","split","label","decoded","decodeURIComponent","m","exec","date","createBlobName","tail","Math","max","floor","sanitizedLabel","labelSegment","encodeURIComponent","getBackupSortTimeMs","parsed","uploadedAt","test","getTime","n","isLegacyTimestampOnly","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves which users may see/use the backup dashboard when no custom `access`
|
|
3
|
+
* option is passed to the plugin. Driven by the `PAYLOAD_BACKUP_ALLOWED_ROLES`
|
|
4
|
+
* environment variable.
|
|
5
|
+
*
|
|
6
|
+
* Syntax:
|
|
7
|
+
* PAYLOAD_BACKUP_ALLOWED_ROLES=admin,editor // any user whose roles[].slug matches
|
|
8
|
+
* PAYLOAD_BACKUP_ALLOWED_ROLES=* // any authenticated user
|
|
9
|
+
* (unset / empty) // backwards-compatible default:
|
|
10
|
+
* // - roles present -> require `admin`
|
|
11
|
+
* // - no roles field -> allow
|
|
12
|
+
*/
|
|
13
|
+
export declare const BACKUP_ALLOWED_ROLES_ENV = "PAYLOAD_BACKUP_ALLOWED_ROLES";
|
|
14
|
+
/** Parses the env var value into a normalized list of role slugs (lower-cased, trimmed). */
|
|
15
|
+
export declare function parseAllowedRolesEnv(raw: string | undefined): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Returns true when the authenticated user is allowed to view the backup dashboard based
|
|
18
|
+
* on the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var. Unauthenticated users always return false.
|
|
19
|
+
*
|
|
20
|
+
* Exported for unit testing and for advanced consumers that want to reuse the same check
|
|
21
|
+
* (e.g. wrap it inside their own `access` option).
|
|
22
|
+
*/
|
|
23
|
+
export declare function isUserAllowedByEnvRoles(user: null | Record<string, unknown>, envValue?: string | undefined): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves which users may see/use the backup dashboard when no custom `access`
|
|
3
|
+
* option is passed to the plugin. Driven by the `PAYLOAD_BACKUP_ALLOWED_ROLES`
|
|
4
|
+
* environment variable.
|
|
5
|
+
*
|
|
6
|
+
* Syntax:
|
|
7
|
+
* PAYLOAD_BACKUP_ALLOWED_ROLES=admin,editor // any user whose roles[].slug matches
|
|
8
|
+
* PAYLOAD_BACKUP_ALLOWED_ROLES=* // any authenticated user
|
|
9
|
+
* (unset / empty) // backwards-compatible default:
|
|
10
|
+
* // - roles present -> require `admin`
|
|
11
|
+
* // - no roles field -> allow
|
|
12
|
+
*/ export const BACKUP_ALLOWED_ROLES_ENV = 'PAYLOAD_BACKUP_ALLOWED_ROLES';
|
|
13
|
+
/** Parses the env var value into a normalized list of role slugs (lower-cased, trimmed). */ export function parseAllowedRolesEnv(raw) {
|
|
14
|
+
if (!raw) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return raw.split(',').map((s)=>s.trim().toLowerCase()).filter((s)=>s.length > 0);
|
|
18
|
+
}
|
|
19
|
+
function extractRoleSlugs(user) {
|
|
20
|
+
const roles = user.roles;
|
|
21
|
+
if (!Array.isArray(roles)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const slugs = [];
|
|
25
|
+
for (const entry of roles){
|
|
26
|
+
if (typeof entry === 'string') {
|
|
27
|
+
slugs.push(entry.toLowerCase());
|
|
28
|
+
} else if (entry && typeof entry === 'object' && typeof entry.slug === 'string') {
|
|
29
|
+
slugs.push(entry.slug.toLowerCase());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return slugs;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns true when the authenticated user is allowed to view the backup dashboard based
|
|
36
|
+
* on the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var. Unauthenticated users always return false.
|
|
37
|
+
*
|
|
38
|
+
* Exported for unit testing and for advanced consumers that want to reuse the same check
|
|
39
|
+
* (e.g. wrap it inside their own `access` option).
|
|
40
|
+
*/ export function isUserAllowedByEnvRoles(user, envValue = process.env[BACKUP_ALLOWED_ROLES_ENV]) {
|
|
41
|
+
if (!user) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const allowed = parseAllowedRolesEnv(envValue);
|
|
45
|
+
const userRoles = extractRoleSlugs(user);
|
|
46
|
+
// Env var not set -> backwards-compatible default.
|
|
47
|
+
if (allowed.length === 0) {
|
|
48
|
+
if (userRoles === null || userRoles.length === 0) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return userRoles.includes('admin');
|
|
52
|
+
}
|
|
53
|
+
// Wildcard -> any authenticated user.
|
|
54
|
+
if (allowed.includes('*')) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
// Explicit allow-list requires the user to actually expose roles that match.
|
|
58
|
+
if (!userRoles || userRoles.length === 0) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return userRoles.some((slug)=>allowed.includes(slug));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//# sourceMappingURL=dashboardRoleAccess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/dashboardRoleAccess.ts"],"sourcesContent":["/**\n * Resolves which users may see/use the backup dashboard when no custom `access`\n * option is passed to the plugin. Driven by the `PAYLOAD_BACKUP_ALLOWED_ROLES`\n * environment variable.\n *\n * Syntax:\n * PAYLOAD_BACKUP_ALLOWED_ROLES=admin,editor // any user whose roles[].slug matches\n * PAYLOAD_BACKUP_ALLOWED_ROLES=* // any authenticated user\n * (unset / empty) // backwards-compatible default:\n * // - roles present -> require `admin`\n * // - no roles field -> allow\n */\n\nexport const BACKUP_ALLOWED_ROLES_ENV = 'PAYLOAD_BACKUP_ALLOWED_ROLES'\n\n/** Parses the env var value into a normalized list of role slugs (lower-cased, trimmed). */\nexport function parseAllowedRolesEnv(raw: string | undefined): string[] {\n if (!raw) {return []}\n return raw\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter((s) => s.length > 0)\n}\n\nfunction extractRoleSlugs(user: Record<string, unknown>): null | string[] {\n const roles = user.roles as Array<{ slug?: unknown } | string> | undefined\n if (!Array.isArray(roles)) {return null}\n const slugs: string[] = []\n for (const entry of roles) {\n if (typeof entry === 'string') {\n slugs.push(entry.toLowerCase())\n } else if (entry && typeof entry === 'object' && typeof entry.slug === 'string') {\n slugs.push(entry.slug.toLowerCase())\n }\n }\n return slugs\n}\n\n/**\n * Returns true when the authenticated user is allowed to view the backup dashboard based\n * on the `PAYLOAD_BACKUP_ALLOWED_ROLES` env var. Unauthenticated users always return false.\n *\n * Exported for unit testing and for advanced consumers that want to reuse the same check\n * (e.g. wrap it inside their own `access` option).\n */\nexport function isUserAllowedByEnvRoles(\n user: null | Record<string, unknown>,\n envValue: string | undefined = process.env[BACKUP_ALLOWED_ROLES_ENV],\n): boolean {\n if (!user) {return false}\n const allowed = parseAllowedRolesEnv(envValue)\n const userRoles = extractRoleSlugs(user)\n\n // Env var not set -> backwards-compatible default.\n if (allowed.length === 0) {\n if (userRoles === null || userRoles.length === 0) {return true}\n return userRoles.includes('admin')\n }\n\n // Wildcard -> any authenticated user.\n if (allowed.includes('*')) {return true}\n\n // Explicit allow-list requires the user to actually expose roles that match.\n if (!userRoles || userRoles.length === 0) {return false}\n return userRoles.some((slug) => allowed.includes(slug))\n}\n"],"names":["BACKUP_ALLOWED_ROLES_ENV","parseAllowedRolesEnv","raw","split","map","s","trim","toLowerCase","filter","length","extractRoleSlugs","user","roles","Array","isArray","slugs","entry","push","slug","isUserAllowedByEnvRoles","envValue","process","env","allowed","userRoles","includes","some"],"mappings":"AAAA;;;;;;;;;;;CAWC,GAED,OAAO,MAAMA,2BAA2B,+BAA8B;AAEtE,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,GAAuB;IAC1D,IAAI,CAACA,KAAK;QAAC,OAAO,EAAE;IAAA;IACpB,OAAOA,IACJC,KAAK,CAAC,KACNC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI,GAAGC,WAAW,IAC/BC,MAAM,CAAC,CAACH,IAAMA,EAAEI,MAAM,GAAG;AAC9B;AAEA,SAASC,iBAAiBC,IAA6B;IACrD,MAAMC,QAAQD,KAAKC,KAAK;IACxB,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QAAC,OAAO;IAAI;IACvC,MAAMG,QAAkB,EAAE;IAC1B,KAAK,MAAMC,SAASJ,MAAO;QACzB,IAAI,OAAOI,UAAU,UAAU;YAC7BD,MAAME,IAAI,CAACD,MAAMT,WAAW;QAC9B,OAAO,IAAIS,SAAS,OAAOA,UAAU,YAAY,OAAOA,MAAME,IAAI,KAAK,UAAU;YAC/EH,MAAME,IAAI,CAACD,MAAME,IAAI,CAACX,WAAW;QACnC;IACF;IACA,OAAOQ;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASI,wBACdR,IAAoC,EACpCS,WAA+BC,QAAQC,GAAG,CAACtB,yBAAyB;IAEpE,IAAI,CAACW,MAAM;QAAC,OAAO;IAAK;IACxB,MAAMY,UAAUtB,qBAAqBmB;IACrC,MAAMI,YAAYd,iBAAiBC;IAEnC,mDAAmD;IACnD,IAAIY,QAAQd,MAAM,KAAK,GAAG;QACxB,IAAIe,cAAc,QAAQA,UAAUf,MAAM,KAAK,GAAG;YAAC,OAAO;QAAI;QAC9D,OAAOe,UAAUC,QAAQ,CAAC;IAC5B;IAEA,sCAAsC;IACtC,IAAIF,QAAQE,QAAQ,CAAC,MAAM;QAAC,OAAO;IAAI;IAEvC,6EAA6E;IAC7E,IAAI,CAACD,aAAaA,UAAUf,MAAM,KAAK,GAAG;QAAC,OAAO;IAAK;IACvD,OAAOe,UAAUE,IAAI,CAAC,CAACR,OAASK,QAAQE,QAAQ,CAACP;AACnD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MouseEvent, PointerEvent, RefObject } from 'react';
|
|
2
|
+
/** Mouse or pointer event with viewport coordinates (native {@code <dialog>} backdrop close). */
|
|
3
|
+
export type DialogBackdropPointer = Pick<MouseEvent<HTMLDialogElement>, 'clientX' | 'clientY'> | Pick<PointerEvent<HTMLDialogElement>, 'clientX' | 'clientY'>;
|
|
4
|
+
/**
|
|
5
|
+
* Closes a native {@code <dialog>} when the pointer event is outside the dialog panel
|
|
6
|
+
* (on the dimmed backdrop). Modal dialogs only expose the panel in {@code getBoundingClientRect()},
|
|
7
|
+
* not the full viewport overlay.
|
|
8
|
+
*/
|
|
9
|
+
export declare function closeNativeDialogOnBackdropPointer(event: DialogBackdropPointer, dialogRef: RefObject<HTMLDialogElement | null>): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closes a native {@code <dialog>} when the pointer event is outside the dialog panel
|
|
3
|
+
* (on the dimmed backdrop). Modal dialogs only expose the panel in {@code getBoundingClientRect()},
|
|
4
|
+
* not the full viewport overlay.
|
|
5
|
+
*/ export function closeNativeDialogOnBackdropPointer(event, dialogRef) {
|
|
6
|
+
const el = dialogRef.current;
|
|
7
|
+
if (!el) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const r = el.getBoundingClientRect();
|
|
11
|
+
const { clientX: x, clientY: y } = event;
|
|
12
|
+
if (x < r.left || x > r.right || y < r.top || y > r.bottom) {
|
|
13
|
+
el.close();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=dialogBackdrop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/dialogBackdrop.ts"],"sourcesContent":["import type { MouseEvent, PointerEvent, RefObject } from 'react'\n\n/** Mouse or pointer event with viewport coordinates (native {@code <dialog>} backdrop close). */\nexport type DialogBackdropPointer =\n | Pick<MouseEvent<HTMLDialogElement>, 'clientX' | 'clientY'>\n | Pick<PointerEvent<HTMLDialogElement>, 'clientX' | 'clientY'>\n\n/**\n * Closes a native {@code <dialog>} when the pointer event is outside the dialog panel\n * (on the dimmed backdrop). Modal dialogs only expose the panel in {@code getBoundingClientRect()},\n * not the full viewport overlay.\n */\nexport function closeNativeDialogOnBackdropPointer(\n event: DialogBackdropPointer,\n dialogRef: RefObject<HTMLDialogElement | null>,\n): void {\n const el = dialogRef.current\n if (!el) {return}\n const r = el.getBoundingClientRect()\n const { clientX: x, clientY: y } = event\n if (x < r.left || x > r.right || y < r.top || y > r.bottom) {\n el.close()\n }\n}\n"],"names":["closeNativeDialogOnBackdropPointer","event","dialogRef","el","current","r","getBoundingClientRect","clientX","x","clientY","y","left","right","top","bottom","close"],"mappings":"AAOA;;;;CAIC,GACD,OAAO,SAASA,mCACdC,KAA4B,EAC5BC,SAA8C;IAE9C,MAAMC,KAAKD,UAAUE,OAAO;IAC5B,IAAI,CAACD,IAAI;QAAC;IAAM;IAChB,MAAME,IAAIF,GAAGG,qBAAqB;IAClC,MAAM,EAAEC,SAASC,CAAC,EAAEC,SAASC,CAAC,EAAE,GAAGT;IACnC,IAAIO,IAAIH,EAAEM,IAAI,IAAIH,IAAIH,EAAEO,KAAK,IAAIF,IAAIL,EAAEQ,GAAG,IAAIH,IAAIL,EAAES,MAAM,EAAE;QAC1DX,GAAGY,KAAK;IACV;AACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Human-readable file size (binary units: KiB, MiB, GiB — shown as KB/MB/GB for familiarity). */ export function formatBytes(bytes) {
|
|
2
|
+
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
3
|
+
return '—';
|
|
4
|
+
}
|
|
5
|
+
if (bytes < 1024) {
|
|
6
|
+
return `${Math.round(bytes)} B`;
|
|
7
|
+
}
|
|
8
|
+
const kb = bytes / 1024;
|
|
9
|
+
if (kb < 1024) {
|
|
10
|
+
return kb < 10 ? `${kb.toFixed(1)} KB` : `${Math.round(kb)} KB`;
|
|
11
|
+
}
|
|
12
|
+
const mb = kb / 1024;
|
|
13
|
+
if (mb < 1024) {
|
|
14
|
+
return mb < 10 ? `${mb.toFixed(1)} MB` : `${mb.toFixed(1)} MB`;
|
|
15
|
+
}
|
|
16
|
+
const gb = mb / 1024;
|
|
17
|
+
return gb < 10 ? `${gb.toFixed(2)} GB` : `${gb.toFixed(1)} GB`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=formatBytes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/formatBytes.ts"],"sourcesContent":["/** Human-readable file size (binary units: KiB, MiB, GiB — shown as KB/MB/GB for familiarity). */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) {\n return '—'\n }\n if (bytes < 1024) {\n return `${Math.round(bytes)} B`\n }\n const kb = bytes / 1024\n if (kb < 1024) {\n return kb < 10 ? `${kb.toFixed(1)} KB` : `${Math.round(kb)} KB`\n }\n const mb = kb / 1024\n if (mb < 1024) {\n return mb < 10 ? `${mb.toFixed(1)} MB` : `${mb.toFixed(1)} MB`\n }\n const gb = mb / 1024\n return gb < 10 ? `${gb.toFixed(2)} GB` : `${gb.toFixed(1)} GB`\n}\n"],"names":["formatBytes","bytes","Number","isFinite","Math","round","kb","toFixed","mb","gb"],"mappings":"AAAA,gGAAgG,GAChG,OAAO,SAASA,YAAYC,KAAa;IACvC,IAAI,CAACC,OAAOC,QAAQ,CAACF,UAAUA,QAAQ,GAAG;QACxC,OAAO;IACT;IACA,IAAIA,QAAQ,MAAM;QAChB,OAAO,GAAGG,KAAKC,KAAK,CAACJ,OAAO,EAAE,CAAC;IACjC;IACA,MAAMK,KAAKL,QAAQ;IACnB,IAAIK,KAAK,MAAM;QACb,OAAOA,KAAK,KAAK,GAAGA,GAAGC,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,GAAGH,KAAKC,KAAK,CAACC,IAAI,GAAG,CAAC;IACjE;IACA,MAAME,KAAKF,KAAK;IAChB,IAAIE,KAAK,MAAM;QACb,OAAOA,KAAK,KAAK,GAAGA,GAAGD,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,GAAGC,GAAGD,OAAO,CAAC,GAAG,GAAG,CAAC;IAChE;IACA,MAAME,KAAKD,KAAK;IAChB,OAAOC,KAAK,KAAK,GAAGA,GAAGF,OAAO,CAAC,GAAG,GAAG,CAAC,GAAG,GAAGE,GAAGF,OAAO,CAAC,GAAG,GAAG,CAAC;AAChE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function getCurrentDbName() {
|
|
2
|
+
try {
|
|
3
|
+
const { hostname, pathname } = new URL(process.env.MONGODB_URI);
|
|
4
|
+
return hostname + pathname;
|
|
5
|
+
} catch {
|
|
6
|
+
return 'none';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function getCurrentHostname() {
|
|
10
|
+
try {
|
|
11
|
+
return process.env.NEXT_PUBLIC_SERVER_URL ? new URL(process.env.NEXT_PUBLIC_SERVER_URL).hostname : process.env.VERCEL_URL ?? 'none';
|
|
12
|
+
} catch {
|
|
13
|
+
return process.env.VERCEL_URL || 'none';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=hostname.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/hostname.ts"],"sourcesContent":["export function getCurrentDbName(): string {\n try {\n const { hostname, pathname } = new URL(process.env.MONGODB_URI!)\n return hostname + pathname\n } catch {\n return 'none'\n }\n}\n\nexport function getCurrentHostname(): string {\n try {\n return process.env.NEXT_PUBLIC_SERVER_URL\n ? new URL(process.env.NEXT_PUBLIC_SERVER_URL).hostname\n : (process.env.VERCEL_URL ?? 'none')\n } catch {\n return process.env.VERCEL_URL! || 'none'\n }\n}\n"],"names":["getCurrentDbName","hostname","pathname","URL","process","env","MONGODB_URI","getCurrentHostname","NEXT_PUBLIC_SERVER_URL","VERCEL_URL"],"mappings":"AAAA,OAAO,SAASA;IACd,IAAI;QACF,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAIC,QAAQC,GAAG,CAACC,WAAW;QAC9D,OAAOL,WAAWC;IACpB,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,SAASK;IACd,IAAI;QACF,OAAOH,QAAQC,GAAG,CAACG,sBAAsB,GACrC,IAAIL,IAAIC,QAAQC,GAAG,CAACG,sBAAsB,EAAEP,QAAQ,GACnDG,QAAQC,GAAG,CAACI,UAAU,IAAI;IACjC,EAAE,OAAM;QACN,OAAOL,QAAQC,GAAG,CAACI,UAAU,IAAK;IACpC;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["export * from './blobName'\nexport * from './dashboardRoleAccess'\nexport * from './formatBytes'\nexport * from './hostname'\nexport * from './maskBlobToken'\n"],"names":[],"mappings":"AAAA,cAAc,aAAY;AAC1B,cAAc,wBAAuB;AACrC,cAAc,gBAAe;AAC7B,cAAc,aAAY;AAC1B,cAAc,kBAAiB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask a Vercel Blob read/write token for display in the admin UI (never send the full secret).
|
|
3
|
+
* Shape: prefix + fixed-length asterisk run + suffix (length of real token does not change star count).
|
|
4
|
+
* Example: vercel_blob_rw_yfn + 23×* + bsM7
|
|
5
|
+
*/
|
|
6
|
+
export declare function maskBlobReadWriteToken(raw: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* When a token is already stored, treat the client value as "do not change the stored token" if
|
|
9
|
+
* they left the masked placeholder or cleared the field while editing other settings.
|
|
10
|
+
*/
|
|
11
|
+
export declare function shouldPreserveBackupBlobTokenField(clientValue: string, hadStoredToken: boolean): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask a Vercel Blob read/write token for display in the admin UI (never send the full secret).
|
|
3
|
+
* Shape: prefix + fixed-length asterisk run + suffix (length of real token does not change star count).
|
|
4
|
+
* Example: vercel_blob_rw_yfn + 23×* + bsM7
|
|
5
|
+
*/ export function maskBlobReadWriteToken(raw) {
|
|
6
|
+
const t = raw.trim();
|
|
7
|
+
if (t.length === 0) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
/** Fixed number of asterisks between prefix and suffix (UI stays compact for any token length). */ const middleRun = 32;
|
|
11
|
+
const prefixLen = Math.min(18, Math.max(8, Math.floor(t.length * 0.42)));
|
|
12
|
+
const suffixLen = Math.min(4, Math.max(3, Math.floor(t.length * 0.12)));
|
|
13
|
+
if (t.length <= prefixLen + suffixLen + 6) {
|
|
14
|
+
const headLen = Math.min(6, t.length);
|
|
15
|
+
const head = t.slice(0, headLen);
|
|
16
|
+
const tailLen = Math.min(3, Math.max(0, t.length - headLen));
|
|
17
|
+
const tail = tailLen > 0 ? t.slice(-tailLen) : '';
|
|
18
|
+
const middle = '*'.repeat(middleRun);
|
|
19
|
+
return tail ? `${head}${middle}${tail}` : `${head}${middle}`;
|
|
20
|
+
}
|
|
21
|
+
const prefix = t.slice(0, prefixLen);
|
|
22
|
+
const suffix = t.slice(-suffixLen);
|
|
23
|
+
return `${prefix}${'*'.repeat(middleRun)}${suffix}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* When a token is already stored, treat the client value as "do not change the stored token" if
|
|
27
|
+
* they left the masked placeholder or cleared the field while editing other settings.
|
|
28
|
+
*/ export function shouldPreserveBackupBlobTokenField(clientValue, hadStoredToken) {
|
|
29
|
+
if (!hadStoredToken) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const v = clientValue.trim();
|
|
33
|
+
if (v.length === 0) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (/\*{2,}/.test(v)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=maskBlobToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/maskBlobToken.ts"],"sourcesContent":["/**\n * Mask a Vercel Blob read/write token for display in the admin UI (never send the full secret).\n * Shape: prefix + fixed-length asterisk run + suffix (length of real token does not change star count).\n * Example: vercel_blob_rw_yfn + 23×* + bsM7\n */\nexport function maskBlobReadWriteToken(raw: string): string {\n const t = raw.trim()\n if (t.length === 0) {return ''}\n\n /** Fixed number of asterisks between prefix and suffix (UI stays compact for any token length). */\n const middleRun = 32\n\n const prefixLen = Math.min(18, Math.max(8, Math.floor(t.length * 0.42)))\n const suffixLen = Math.min(4, Math.max(3, Math.floor(t.length * 0.12)))\n\n if (t.length <= prefixLen + suffixLen + 6) {\n const headLen = Math.min(6, t.length)\n const head = t.slice(0, headLen)\n const tailLen = Math.min(3, Math.max(0, t.length - headLen))\n const tail = tailLen > 0 ? t.slice(-tailLen) : ''\n const middle = '*'.repeat(middleRun)\n return tail ? `${head}${middle}${tail}` : `${head}${middle}`\n }\n\n const prefix = t.slice(0, prefixLen)\n const suffix = t.slice(-suffixLen)\n return `${prefix}${'*'.repeat(middleRun)}${suffix}`\n}\n\n/**\n * When a token is already stored, treat the client value as \"do not change the stored token\" if\n * they left the masked placeholder or cleared the field while editing other settings.\n */\nexport function shouldPreserveBackupBlobTokenField(\n clientValue: string,\n hadStoredToken: boolean,\n): boolean {\n if (!hadStoredToken) {return false}\n const v = clientValue.trim()\n if (v.length === 0) {return true}\n if (/\\*{2,}/.test(v)) {return true}\n return false\n}\n"],"names":["maskBlobReadWriteToken","raw","t","trim","length","middleRun","prefixLen","Math","min","max","floor","suffixLen","headLen","head","slice","tailLen","tail","middle","repeat","prefix","suffix","shouldPreserveBackupBlobTokenField","clientValue","hadStoredToken","v","test"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,SAASA,uBAAuBC,GAAW;IAChD,MAAMC,IAAID,IAAIE,IAAI;IAClB,IAAID,EAAEE,MAAM,KAAK,GAAG;QAAC,OAAO;IAAE;IAE9B,iGAAiG,GACjG,MAAMC,YAAY;IAElB,MAAMC,YAAYC,KAAKC,GAAG,CAAC,IAAID,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACR,EAAEE,MAAM,GAAG;IACjE,MAAMO,YAAYJ,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACR,EAAEE,MAAM,GAAG;IAEhE,IAAIF,EAAEE,MAAM,IAAIE,YAAYK,YAAY,GAAG;QACzC,MAAMC,UAAUL,KAAKC,GAAG,CAAC,GAAGN,EAAEE,MAAM;QACpC,MAAMS,OAAOX,EAAEY,KAAK,CAAC,GAAGF;QACxB,MAAMG,UAAUR,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGP,EAAEE,MAAM,GAAGQ;QACnD,MAAMI,OAAOD,UAAU,IAAIb,EAAEY,KAAK,CAAC,CAACC,WAAW;QAC/C,MAAME,SAAS,IAAIC,MAAM,CAACb;QAC1B,OAAOW,OAAO,GAAGH,OAAOI,SAASD,MAAM,GAAG,GAAGH,OAAOI,QAAQ;IAC9D;IAEA,MAAME,SAASjB,EAAEY,KAAK,CAAC,GAAGR;IAC1B,MAAMc,SAASlB,EAAEY,KAAK,CAAC,CAACH;IACxB,OAAO,GAAGQ,SAAS,IAAID,MAAM,CAACb,aAAae,QAAQ;AACrD;AAEA;;;CAGC,GACD,OAAO,SAASC,mCACdC,WAAmB,EACnBC,cAAuB;IAEvB,IAAI,CAACA,gBAAgB;QAAC,OAAO;IAAK;IAClC,MAAMC,IAAIF,YAAYnB,IAAI;IAC1B,IAAIqB,EAAEpB,MAAM,KAAK,GAAG;QAAC,OAAO;IAAI;IAChC,IAAI,SAASqB,IAAI,CAACD,IAAI;QAAC,OAAO;IAAI;IAClC,OAAO;AACT"}
|
package/package.json
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trieb.work/payload-plugin-backup-mongodb",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Payload CMS plugin for MongoDB backup and recovery via Vercel Blob Storage",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/trieb-work/payload-plugin-backup-mongodb.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./client": {
|
|
18
|
+
"import": "./dist/exports/client.js",
|
|
19
|
+
"types": "./dist/exports/client.d.ts",
|
|
20
|
+
"default": "./dist/exports/client.js"
|
|
21
|
+
},
|
|
22
|
+
"./rsc": {
|
|
23
|
+
"import": "./dist/exports/rsc.js",
|
|
24
|
+
"types": "./dist/exports/rsc.d.ts",
|
|
25
|
+
"default": "./dist/exports/rsc.js"
|
|
26
|
+
},
|
|
27
|
+
"./styles.css": "./dist/backup-dashboard.css"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"lint-staged": {
|
|
35
|
+
"*.{ts,tsx}": [
|
|
36
|
+
"eslint --fix --no-warn-ignored",
|
|
37
|
+
"prettier --write"
|
|
38
|
+
],
|
|
39
|
+
"*.{js,mjs,cjs,jsx,json,md,css,scss,yml,yaml}": [
|
|
40
|
+
"prettier --write --ignore-unknown"
|
|
41
|
+
],
|
|
42
|
+
"package.json": [
|
|
43
|
+
"sort-package-json"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@vercel/blob": "^2.3.3",
|
|
48
|
+
"bson": "^6.10.0",
|
|
49
|
+
"cronstrue": "^3.14.0",
|
|
50
|
+
"tar-stream": "^3.1.7"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@changesets/cli": "^2.29.8",
|
|
54
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
55
|
+
"@payloadcms/db-mongodb": "3.82.1",
|
|
56
|
+
"@payloadcms/eslint-config": "3.28.0",
|
|
57
|
+
"@payloadcms/next": "3.82.1",
|
|
58
|
+
"@payloadcms/richtext-lexical": "3.82.1",
|
|
59
|
+
"@payloadcms/translations": "3.82.1",
|
|
60
|
+
"@payloadcms/ui": "3.82.1",
|
|
61
|
+
"@playwright/test": "1.58.2",
|
|
62
|
+
"@swc-node/register": "1.10.9",
|
|
63
|
+
"@swc/cli": "0.6.0",
|
|
64
|
+
"@types/node": "22.19.9",
|
|
65
|
+
"@types/react": "19.2.14",
|
|
66
|
+
"@types/react-dom": "19.2.3",
|
|
67
|
+
"@types/tar-stream": "^3.1.4",
|
|
68
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
69
|
+
"copyfiles": "2.4.1",
|
|
70
|
+
"cross-env": "^7.0.3",
|
|
71
|
+
"dotenv": "^16.4.7",
|
|
72
|
+
"eslint": "^9.23.0",
|
|
73
|
+
"eslint-config-next": "16.2.3",
|
|
74
|
+
"graphql": "^16.8.1",
|
|
75
|
+
"husky": "^9.1.7",
|
|
76
|
+
"lint-staged": "^16.4.0",
|
|
77
|
+
"mongodb-memory-server": "10.1.4",
|
|
78
|
+
"next": "16.2.3",
|
|
79
|
+
"open": "^10.1.0",
|
|
80
|
+
"payload": "3.82.1",
|
|
81
|
+
"prettier": "^3.4.2",
|
|
82
|
+
"qs-esm": "8.0.1",
|
|
83
|
+
"react": "19.2.4",
|
|
84
|
+
"react-dom": "19.2.4",
|
|
85
|
+
"rimraf": "3.0.2",
|
|
86
|
+
"sass": "^1.85.0",
|
|
87
|
+
"sharp": "0.34.2",
|
|
88
|
+
"sort-package-json": "^2.10.0",
|
|
89
|
+
"typescript": "5.7.3",
|
|
90
|
+
"vite-tsconfig-paths": "6.0.5",
|
|
91
|
+
"vitest": "4.1.5"
|
|
92
|
+
},
|
|
93
|
+
"peerDependencies": {
|
|
94
|
+
"@payloadcms/db-mongodb": "^3.0.0",
|
|
95
|
+
"@payloadcms/ui": "^3.0.0",
|
|
96
|
+
"next": "^15.0.0",
|
|
97
|
+
"payload": "^3.0.0",
|
|
98
|
+
"react": "^19.0.0"
|
|
99
|
+
},
|
|
100
|
+
"engines": {
|
|
101
|
+
"node": "^18.20.2 || >=20.9.0",
|
|
102
|
+
"pnpm": "^9 || ^10"
|
|
103
|
+
},
|
|
104
|
+
"publishConfig": {
|
|
105
|
+
"access": "public"
|
|
106
|
+
},
|
|
107
|
+
"registry": "https://registry.npmjs.org/",
|
|
108
|
+
"scripts": {
|
|
109
|
+
"build": "pnpm copyfiles && pnpm build:css && pnpm build:embed-css && pnpm build:types && pnpm build:swc",
|
|
110
|
+
"build:css": "sass src/components/BackupDashboard/index.scss dist/backup-dashboard.css --no-source-map --pkg-importer=node --quiet-deps --silence-deprecation=import",
|
|
111
|
+
"build:dev": "cross-env NODE_OPTIONS=--no-deprecation PAYLOAD_CONFIG_PATH=./dev/payload.config.ts next build dev --webpack",
|
|
112
|
+
"build:embed-css": "node ./scripts/embed-backup-css.mjs",
|
|
113
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
114
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
115
|
+
"changeset": "changeset",
|
|
116
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
117
|
+
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
118
|
+
"predev": "pnpm build:css && pnpm build:embed-css",
|
|
119
|
+
"dev": "node ./scripts/dev.mjs",
|
|
120
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
121
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
122
|
+
"dev:next": "next dev dev --webpack",
|
|
123
|
+
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
124
|
+
"dev:turbo": "node ./scripts/dev.mjs --turbo",
|
|
125
|
+
"dev:watch-css": "node ./scripts/watch-backup-css.mjs",
|
|
126
|
+
"generate:importmap": "pnpm dev:generate-importmap",
|
|
127
|
+
"generate:types": "pnpm dev:generate-types",
|
|
128
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\" \"dev/**/*.{ts,tsx}\" \"tests/**/*.{ts,tsx}\"",
|
|
129
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" \"dev/**/*.{ts,tsx}\" \"tests/**/*.{ts,tsx}\" --fix",
|
|
130
|
+
"playwright:install": "playwright install chromium",
|
|
131
|
+
"release": "pnpm run build && changeset publish",
|
|
132
|
+
"start:dev": "cross-env NODE_OPTIONS=--no-deprecation PAYLOAD_CONFIG_PATH=./dev/payload.config.ts next start dev -p 3000",
|
|
133
|
+
"test": "pnpm test:int && pnpm test:e2e",
|
|
134
|
+
"test:e2e": "cross-env NODE_OPTIONS=--no-deprecation playwright test",
|
|
135
|
+
"test:int": "vitest run",
|
|
136
|
+
"test:int:cov": "vitest run --coverage",
|
|
137
|
+
"test:int:watch": "vitest",
|
|
138
|
+
"typecheck": "tsc --noEmit"
|
|
139
|
+
}
|
|
140
|
+
}
|