@trieb.work/payload-plugin-backup-mongodb 0.1.2 → 0.1.4

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 CHANGED
@@ -1,14 +1,16 @@
1
- > Experimental. Originated in the [Payblocks](https://github.com/trieb-work/payblocks) template, now maintained as a standalone package. Feedback and PRs welcome.
1
+ > Experimental. Originated in the [Payblocks](https://www.shadcnblocks.com/payload-cms) template, now maintained as a standalone package. Running in production at 20+ projects from TRWK agency. Feedback and PRs welcome.
2
2
 
3
3
  # @trieb.work/payload-plugin-backup-mongodb
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/@trieb.work/payload-plugin-backup-mongodb)](https://www.npmjs.com/package/@trieb.work/payload-plugin-backup-mongodb)
6
+
5
7
  A **Payload CMS v3** plugin for **MongoDB only** — not Postgres, SQLite, or other database adapters. It handles Mongo + media backup, restore, and scheduled retention with zero meta-database and a built-in admin UI. Backups live directly in Vercel Blob Storage, so a fresh install can list and restore any prior backup without bootstrapping a database first.
6
8
 
7
9
  ![Backup dashboard embedded in the Payload admin panel](./docs/screenshots/dashboard.png)
8
10
 
9
11
  A first-class **Backups** section lives right below the Payload dashboard: browse every archive
10
12
  sorted by creation time, see at a glance which host and database it belongs to, whether media is
11
- bundled and how big it is, then download, restore or delete any backup with one click.
13
+ bundled and how big it is, then download, restore or delete any backup with one click. Read more on how it originated at [TRWK> Case Study](https://trwk.de/case-studies/payload-plugin-mongodb-backup-restore).
12
14
 
13
15
  ---
14
16
 
@@ -299,7 +301,7 @@ The repo includes a `dev/` Payload + Next app (MongoDB Memory Server when no URI
299
301
 
300
302
  ## Publishing (npm)
301
303
 
302
- Versioning uses [Changesets](https://github.com/changesets/changesets), like [Payblocks](https://github.com/trieb-work/payblocks): add a file with `pnpm changeset`, open a PR to `main`, and merge. The **Release** workflow opens a “version packages” PR or runs `pnpm run release` (`build` + `changeset publish`) when the set of changesets is ready. PRs need a new `.changeset/*.md` unless you add the **`no-changeset`** label (e.g. docs-only).
304
+ Versioning uses [Changesets](https://github.com/changesets/changesets): add a file with `pnpm changeset`, open a PR to `main`, and merge. The **Release** workflow opens a “version packages” PR or runs `pnpm run release` (`build` + `changeset publish`) when the set of changesets is ready. PRs need a new `.changeset/*.md` unless you add the **`no-changeset`** label (e.g. docs-only).
303
305
 
304
306
  ---
305
307
 
@@ -309,11 +311,15 @@ Versioning uses [Changesets](https://github.com/changesets/changesets), like [Pa
309
311
  - Additional storage adapters (S3, R2, filesystem, etc.).
310
312
  - Scheduler-agnostic display when not using Vercel (`vercel.json` is currently used for the schedule summary where available).
311
313
  - Streaming restore for very large databases.
312
- - More E2E coverage around a demo project.
313
314
  - Configurable `backups/` prefix or bucket layout.
315
+ - add support for tenant plugin and partial backups per tenant
314
316
 
315
317
  ---
316
318
 
317
319
  ## License
318
320
 
319
321
  MIT
322
+
323
+ ---
324
+
325
+ Built and maintained by [TRWK>](https://trwk.de), formerly [trieb.work](https://trieb.work).
@@ -1,6 +1,13 @@
1
1
  import type { I18n } from '@payloadcms/translations';
2
+ import type { Payload } from 'payload';
2
3
  interface BackupDashboardProps {
3
4
  i18n: I18n;
5
+ /**
6
+ * Payload instance injected by Payload's admin `RenderServerComponent` as a server prop
7
+ * for `afterDashboard` components. Avoids importing `@payload-config` from the plugin,
8
+ * which does not resolve when the plugin runs from `node_modules`.
9
+ */
10
+ payload?: Payload;
4
11
  /** Optional on admin server props; omitted does not mean logged out (see `defaultIsHidden`). */
5
12
  user?: null | Record<string, unknown>;
6
13
  }
@@ -1,6 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import configPromise from '@payload-config';
3
- import { getPayload } from 'payload';
4
2
  import { listBackups, resolveBackupListToken } from '../../core/backup';
5
3
  import { getBackupSortTimeMs, getCurrentDbName, getCurrentHostname, isUserAllowedByEnvRoles, transformBlobName } from '../../utils/index';
6
4
  import { backupDashboardInlineCss } from './backupDashboardInlineCss';
@@ -22,7 +20,7 @@ function defaultIsHidden(user, access) {
22
20
  // historical default (admin role, or "visible" when the project has no roles field).
23
21
  return !isUserAllowedByEnvRoles(user);
24
22
  }
25
- export const BackupDashboard = async ({ i18n, user })=>{
23
+ export const BackupDashboard = async ({ i18n, payload, user })=>{
26
24
  if (defaultIsHidden(user)) {
27
25
  return null;
28
26
  }
@@ -70,9 +68,37 @@ export const BackupDashboard = async ({ i18n, user })=>{
70
68
  ]
71
69
  });
72
70
  }
73
- const payload = await getPayload({
74
- config: configPromise
75
- });
71
+ if (!payload) {
72
+ // Should not happen in a normal Payload admin render; guard for safety.
73
+ return /*#__PURE__*/ _jsxs(_Fragment, {
74
+ children: [
75
+ /*#__PURE__*/ _jsx("style", {
76
+ dangerouslySetInnerHTML: {
77
+ __html: backupDashboardInlineCss
78
+ }
79
+ }),
80
+ /*#__PURE__*/ _jsxs("div", {
81
+ className: "backup-dashboard",
82
+ children: [
83
+ /*#__PURE__*/ _jsxs("h2", {
84
+ children: [
85
+ "Backups ",
86
+ /*#__PURE__*/ _jsx("span", {
87
+ className: "experimental",
88
+ children: "(experimental)"
89
+ })
90
+ ]
91
+ }),
92
+ /*#__PURE__*/ _jsx("p", {
93
+ className: "backup-dashboard__setup-hint",
94
+ role: "status",
95
+ children: "Backup dashboard could not initialise: Payload instance was not provided by the admin server props."
96
+ })
97
+ ]
98
+ })
99
+ ]
100
+ });
101
+ }
76
102
  const backupBlobToken = await resolveBackupListToken(payload);
77
103
  const hasBlobToken = backupBlobToken.trim().length > 0;
78
104
  const blobs = hasBlobToken ? await listBackups(payload, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/BackupDashboard/index.tsx"],"sourcesContent":["import type { I18n } from '@payloadcms/translations'\n\nimport configPromise from '@payload-config'\nimport { getPayload } from 'payload'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { listBackups, resolveBackupListToken } from '../../core/backup'\nimport {\n getBackupSortTimeMs,\n getCurrentDbName,\n getCurrentHostname,\n isUserAllowedByEnvRoles,\n transformBlobName,\n} from '../../utils/index'\nimport { backupDashboardInlineCss } from './backupDashboardInlineCss'\nimport { BackupListCollapsible, BackupSettingsModal, ManualBackupDialog } from './index.client'\n\ninterface BackupDashboardProps {\n i18n: I18n\n /** Optional on admin server props; omitted does not mean logged out (see `defaultIsHidden`). */\n user?: null | Record<string, unknown>\n}\n\nfunction defaultIsHidden(\n user: null | Record<string, unknown> | undefined,\n access?: BackupPluginOptions['access'],\n): boolean {\n if (user === null) {\n return true\n }\n if (user === undefined) {\n if (access) {\n return true\n }\n return false\n }\n if (access) {\n return !access(user)\n }\n // Role allow-list via `PAYLOAD_BACKUP_ALLOWED_ROLES`. When unset, falls back to the\n // historical default (admin role, or \"visible\" when the project has no roles field).\n return !isUserAllowedByEnvRoles(user)\n}\n\nexport const BackupDashboard: React.FC<BackupDashboardProps> = async ({ i18n, user }) => {\n if (defaultIsHidden(user)) {\n return null\n }\n\n const hasMongoConnection =\n Boolean(process.env.MONGODB_URI?.trim()) || Boolean(process.env.DATABASE_URL?.trim())\n if (!hasMongoConnection) {\n return (\n <>\n <style dangerouslySetInnerHTML={{ __html: backupDashboardInlineCss }} />\n <div className=\"backup-dashboard\">\n <h2>\n Backups <span className=\"experimental\">(experimental)</span>\n </h2>\n <p className=\"backup-dashboard__setup-hint\" role=\"status\">\n Set <code className=\"backup-dashboard__setup-hint-code\">MONGODB_URI</code> or{' '}\n <code className=\"backup-dashboard__setup-hint-code\">DATABASE_URL</code> so Payload can\n connect to MongoDB. The in-memory dev database sets both automatically when neither is\n provided.\n </p>\n </div>\n </>\n )\n }\n\n const payload = await getPayload({ config: configPromise })\n const backupBlobToken = await resolveBackupListToken(payload)\n const hasBlobToken = backupBlobToken.trim().length > 0\n\n const blobs = hasBlobToken ? await listBackups(payload, { blobToken: backupBlobToken }) : []\n const sortedBlobs = [...blobs].sort((a, b) => {\n const ta = getBackupSortTimeMs(transformBlobName(a.pathname), new Date(a.uploadedAt))\n const tb = getBackupSortTimeMs(transformBlobName(b.pathname), new Date(b.uploadedAt))\n return tb - ta\n })\n const currentHostname = getCurrentHostname()\n const currentDbName = getCurrentDbName()\n\n const countOtherDb = sortedBlobs.filter((blob) => {\n const { dbName } = transformBlobName(blob.pathname)\n return currentDbName !== dbName\n }).length\n\n const countOtherHostname = sortedBlobs.filter((blob) => {\n const { hostname } = transformBlobName(blob.pathname)\n return currentHostname !== hostname\n }).length\n const lastBackup = sortedBlobs[0]\n\n return (\n <>\n <style\n dangerouslySetInnerHTML={{ __html: backupDashboardInlineCss }}\n data-payload-backup-mongodb=\"1\"\n />\n <div className=\"backup-dashboard\">\n <h2>\n Backups <span className=\"experimental\">(experimental)</span>\n </h2>\n\n {!hasBlobToken && (\n <p className=\"backup-dashboard__setup-hint\" role=\"status\">\n Add a Vercel Blob read/write token: set environment variable{' '}\n <code className=\"backup-dashboard__setup-hint-code\">BLOB_READ_WRITE_TOKEN</code>, or\n open <strong>Backup settings</strong> and paste a token. Until then, the list stays\n empty and cron or manual backups cannot run.\n </p>\n )}\n\n <div className=\"backup-dashboard__toolbar\">\n <div className=\"backup-dashboard__toolbar-meta\">\n <span className=\"backup-dashboard__toolbar-pill\">\n <span className=\"backup-dashboard__toolbar-key\">Total</span>\n {sortedBlobs.length} backup{sortedBlobs.length === 1 ? '' : 's'}\n </span>\n <span className=\"backup-dashboard__toolbar-pill\">\n <span className=\"backup-dashboard__toolbar-key\">Last backup</span>\n {lastBackup\n ? new Date(\n getBackupSortTimeMs(\n transformBlobName(lastBackup.pathname),\n new Date(lastBackup.uploadedAt),\n ),\n ).toLocaleString(i18n?.language || 'en')\n : 'No backups yet'}\n </span>\n </div>\n <div className=\"make-backup-actions\">\n <BackupSettingsModal />\n <ManualBackupDialog />\n </div>\n </div>\n\n <BackupListCollapsible\n blobs={sortedBlobs.map((b) => ({\n downloadUrl: b.downloadUrl,\n pathname: b.pathname,\n size: b.size,\n uploadedAt: new Date(b.uploadedAt).toISOString(),\n url: b.url,\n }))}\n countOtherDb={countOtherDb}\n countOtherHostname={countOtherHostname}\n currentDbName={currentDbName}\n currentHostname={currentHostname}\n i18nLanguage={i18n?.language || 'en'}\n />\n </div>\n </>\n )\n}\n\nexport default BackupDashboard\n"],"names":["configPromise","getPayload","listBackups","resolveBackupListToken","getBackupSortTimeMs","getCurrentDbName","getCurrentHostname","isUserAllowedByEnvRoles","transformBlobName","backupDashboardInlineCss","BackupListCollapsible","BackupSettingsModal","ManualBackupDialog","defaultIsHidden","user","access","undefined","BackupDashboard","i18n","hasMongoConnection","Boolean","process","env","MONGODB_URI","trim","DATABASE_URL","style","dangerouslySetInnerHTML","__html","div","className","h2","span","p","role","code","payload","config","backupBlobToken","hasBlobToken","length","blobs","blobToken","sortedBlobs","sort","a","b","ta","pathname","Date","uploadedAt","tb","currentHostname","currentDbName","countOtherDb","filter","blob","dbName","countOtherHostname","hostname","lastBackup","data-payload-backup-mongodb","strong","toLocaleString","language","map","downloadUrl","size","toISOString","url","i18nLanguage"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/BackupDashboard/index.tsx"],"sourcesContent":["import type { I18n } from '@payloadcms/translations'\nimport type { Payload } from 'payload'\n\nimport type { BackupPluginOptions } from '../../types'\n\nimport { listBackups, resolveBackupListToken } from '../../core/backup'\nimport {\n getBackupSortTimeMs,\n getCurrentDbName,\n getCurrentHostname,\n isUserAllowedByEnvRoles,\n transformBlobName,\n} from '../../utils/index'\nimport { backupDashboardInlineCss } from './backupDashboardInlineCss'\nimport { BackupListCollapsible, BackupSettingsModal, ManualBackupDialog } from './index.client'\n\ninterface BackupDashboardProps {\n i18n: I18n\n /**\n * Payload instance injected by Payload's admin `RenderServerComponent` as a server prop\n * for `afterDashboard` components. Avoids importing `@payload-config` from the plugin,\n * which does not resolve when the plugin runs from `node_modules`.\n */\n payload?: Payload\n /** Optional on admin server props; omitted does not mean logged out (see `defaultIsHidden`). */\n user?: null | Record<string, unknown>\n}\n\nfunction defaultIsHidden(\n user: null | Record<string, unknown> | undefined,\n access?: BackupPluginOptions['access'],\n): boolean {\n if (user === null) {\n return true\n }\n if (user === undefined) {\n if (access) {\n return true\n }\n return false\n }\n if (access) {\n return !access(user)\n }\n // Role allow-list via `PAYLOAD_BACKUP_ALLOWED_ROLES`. When unset, falls back to the\n // historical default (admin role, or \"visible\" when the project has no roles field).\n return !isUserAllowedByEnvRoles(user)\n}\n\nexport const BackupDashboard: React.FC<BackupDashboardProps> = async ({ i18n, payload, user }) => {\n if (defaultIsHidden(user)) {\n return null\n }\n\n const hasMongoConnection =\n Boolean(process.env.MONGODB_URI?.trim()) || Boolean(process.env.DATABASE_URL?.trim())\n if (!hasMongoConnection) {\n return (\n <>\n <style dangerouslySetInnerHTML={{ __html: backupDashboardInlineCss }} />\n <div className=\"backup-dashboard\">\n <h2>\n Backups <span className=\"experimental\">(experimental)</span>\n </h2>\n <p className=\"backup-dashboard__setup-hint\" role=\"status\">\n Set <code className=\"backup-dashboard__setup-hint-code\">MONGODB_URI</code> or{' '}\n <code className=\"backup-dashboard__setup-hint-code\">DATABASE_URL</code> so Payload can\n connect to MongoDB. The in-memory dev database sets both automatically when neither is\n provided.\n </p>\n </div>\n </>\n )\n }\n\n if (!payload) {\n // Should not happen in a normal Payload admin render; guard for safety.\n return (\n <>\n <style dangerouslySetInnerHTML={{ __html: backupDashboardInlineCss }} />\n <div className=\"backup-dashboard\">\n <h2>\n Backups <span className=\"experimental\">(experimental)</span>\n </h2>\n <p className=\"backup-dashboard__setup-hint\" role=\"status\">\n Backup dashboard could not initialise: Payload instance was not provided by the admin\n server props.\n </p>\n </div>\n </>\n )\n }\n\n const backupBlobToken = await resolveBackupListToken(payload)\n const hasBlobToken = backupBlobToken.trim().length > 0\n\n const blobs = hasBlobToken ? await listBackups(payload, { blobToken: backupBlobToken }) : []\n const sortedBlobs = [...blobs].sort((a, b) => {\n const ta = getBackupSortTimeMs(transformBlobName(a.pathname), new Date(a.uploadedAt))\n const tb = getBackupSortTimeMs(transformBlobName(b.pathname), new Date(b.uploadedAt))\n return tb - ta\n })\n const currentHostname = getCurrentHostname()\n const currentDbName = getCurrentDbName()\n\n const countOtherDb = sortedBlobs.filter((blob) => {\n const { dbName } = transformBlobName(blob.pathname)\n return currentDbName !== dbName\n }).length\n\n const countOtherHostname = sortedBlobs.filter((blob) => {\n const { hostname } = transformBlobName(blob.pathname)\n return currentHostname !== hostname\n }).length\n const lastBackup = sortedBlobs[0]\n\n return (\n <>\n <style\n dangerouslySetInnerHTML={{ __html: backupDashboardInlineCss }}\n data-payload-backup-mongodb=\"1\"\n />\n <div className=\"backup-dashboard\">\n <h2>\n Backups <span className=\"experimental\">(experimental)</span>\n </h2>\n\n {!hasBlobToken && (\n <p className=\"backup-dashboard__setup-hint\" role=\"status\">\n Add a Vercel Blob read/write token: set environment variable{' '}\n <code className=\"backup-dashboard__setup-hint-code\">BLOB_READ_WRITE_TOKEN</code>, or\n open <strong>Backup settings</strong> and paste a token. Until then, the list stays\n empty and cron or manual backups cannot run.\n </p>\n )}\n\n <div className=\"backup-dashboard__toolbar\">\n <div className=\"backup-dashboard__toolbar-meta\">\n <span className=\"backup-dashboard__toolbar-pill\">\n <span className=\"backup-dashboard__toolbar-key\">Total</span>\n {sortedBlobs.length} backup{sortedBlobs.length === 1 ? '' : 's'}\n </span>\n <span className=\"backup-dashboard__toolbar-pill\">\n <span className=\"backup-dashboard__toolbar-key\">Last backup</span>\n {lastBackup\n ? new Date(\n getBackupSortTimeMs(\n transformBlobName(lastBackup.pathname),\n new Date(lastBackup.uploadedAt),\n ),\n ).toLocaleString(i18n?.language || 'en')\n : 'No backups yet'}\n </span>\n </div>\n <div className=\"make-backup-actions\">\n <BackupSettingsModal />\n <ManualBackupDialog />\n </div>\n </div>\n\n <BackupListCollapsible\n blobs={sortedBlobs.map((b) => ({\n downloadUrl: b.downloadUrl,\n pathname: b.pathname,\n size: b.size,\n uploadedAt: new Date(b.uploadedAt).toISOString(),\n url: b.url,\n }))}\n countOtherDb={countOtherDb}\n countOtherHostname={countOtherHostname}\n currentDbName={currentDbName}\n currentHostname={currentHostname}\n i18nLanguage={i18n?.language || 'en'}\n />\n </div>\n </>\n )\n}\n\nexport default BackupDashboard\n"],"names":["listBackups","resolveBackupListToken","getBackupSortTimeMs","getCurrentDbName","getCurrentHostname","isUserAllowedByEnvRoles","transformBlobName","backupDashboardInlineCss","BackupListCollapsible","BackupSettingsModal","ManualBackupDialog","defaultIsHidden","user","access","undefined","BackupDashboard","i18n","payload","hasMongoConnection","Boolean","process","env","MONGODB_URI","trim","DATABASE_URL","style","dangerouslySetInnerHTML","__html","div","className","h2","span","p","role","code","backupBlobToken","hasBlobToken","length","blobs","blobToken","sortedBlobs","sort","a","b","ta","pathname","Date","uploadedAt","tb","currentHostname","currentDbName","countOtherDb","filter","blob","dbName","countOtherHostname","hostname","lastBackup","data-payload-backup-mongodb","strong","toLocaleString","language","map","downloadUrl","size","toISOString","url","i18nLanguage"],"mappings":";AAKA,SAASA,WAAW,EAAEC,sBAAsB,QAAQ,oBAAmB;AACvE,SACEC,mBAAmB,EACnBC,gBAAgB,EAChBC,kBAAkB,EAClBC,uBAAuB,EACvBC,iBAAiB,QACZ,oBAAmB;AAC1B,SAASC,wBAAwB,QAAQ,6BAA4B;AACrE,SAASC,qBAAqB,EAAEC,mBAAmB,EAAEC,kBAAkB,QAAQ,iBAAgB;AAc/F,SAASC,gBACPC,IAAgD,EAChDC,MAAsC;IAEtC,IAAID,SAAS,MAAM;QACjB,OAAO;IACT;IACA,IAAIA,SAASE,WAAW;QACtB,IAAID,QAAQ;YACV,OAAO;QACT;QACA,OAAO;IACT;IACA,IAAIA,QAAQ;QACV,OAAO,CAACA,OAAOD;IACjB;IACA,oFAAoF;IACpF,qFAAqF;IACrF,OAAO,CAACP,wBAAwBO;AAClC;AAEA,OAAO,MAAMG,kBAAkD,OAAO,EAAEC,IAAI,EAAEC,OAAO,EAAEL,IAAI,EAAE;IAC3F,IAAID,gBAAgBC,OAAO;QACzB,OAAO;IACT;IAEA,MAAMM,qBACJC,QAAQC,QAAQC,GAAG,CAACC,WAAW,EAAEC,WAAWJ,QAAQC,QAAQC,GAAG,CAACG,YAAY,EAAED;IAChF,IAAI,CAACL,oBAAoB;QACvB,qBACE;;8BACE,KAACO;oBAAMC,yBAAyB;wBAAEC,QAAQpB;oBAAyB;;8BACnE,MAACqB;oBAAIC,WAAU;;sCACb,MAACC;;gCAAG;8CACM,KAACC;oCAAKF,WAAU;8CAAe;;;;sCAEzC,MAACG;4BAAEH,WAAU;4BAA+BI,MAAK;;gCAAS;8CACpD,KAACC;oCAAKL,WAAU;8CAAoC;;gCAAkB;gCAAI;8CAC9E,KAACK;oCAAKL,WAAU;8CAAoC;;gCAAmB;;;;;;;IAOjF;IAEA,IAAI,CAACZ,SAAS;QACZ,wEAAwE;QACxE,qBACE;;8BACE,KAACQ;oBAAMC,yBAAyB;wBAAEC,QAAQpB;oBAAyB;;8BACnE,MAACqB;oBAAIC,WAAU;;sCACb,MAACC;;gCAAG;8CACM,KAACC;oCAAKF,WAAU;8CAAe;;;;sCAEzC,KAACG;4BAAEH,WAAU;4BAA+BI,MAAK;sCAAS;;;;;;IAOlE;IAEA,MAAME,kBAAkB,MAAMlC,uBAAuBgB;IACrD,MAAMmB,eAAeD,gBAAgBZ,IAAI,GAAGc,MAAM,GAAG;IAErD,MAAMC,QAAQF,eAAe,MAAMpC,YAAYiB,SAAS;QAAEsB,WAAWJ;IAAgB,KAAK,EAAE;IAC5F,MAAMK,cAAc;WAAIF;KAAM,CAACG,IAAI,CAAC,CAACC,GAAGC;QACtC,MAAMC,KAAK1C,oBAAoBI,kBAAkBoC,EAAEG,QAAQ,GAAG,IAAIC,KAAKJ,EAAEK,UAAU;QACnF,MAAMC,KAAK9C,oBAAoBI,kBAAkBqC,EAAEE,QAAQ,GAAG,IAAIC,KAAKH,EAAEI,UAAU;QACnF,OAAOC,KAAKJ;IACd;IACA,MAAMK,kBAAkB7C;IACxB,MAAM8C,gBAAgB/C;IAEtB,MAAMgD,eAAeX,YAAYY,MAAM,CAAC,CAACC;QACvC,MAAM,EAAEC,MAAM,EAAE,GAAGhD,kBAAkB+C,KAAKR,QAAQ;QAClD,OAAOK,kBAAkBI;IAC3B,GAAGjB,MAAM;IAET,MAAMkB,qBAAqBf,YAAYY,MAAM,CAAC,CAACC;QAC7C,MAAM,EAAEG,QAAQ,EAAE,GAAGlD,kBAAkB+C,KAAKR,QAAQ;QACpD,OAAOI,oBAAoBO;IAC7B,GAAGnB,MAAM;IACT,MAAMoB,aAAajB,WAAW,CAAC,EAAE;IAEjC,qBACE;;0BACE,KAACf;gBACCC,yBAAyB;oBAAEC,QAAQpB;gBAAyB;gBAC5DmD,+BAA4B;;0BAE9B,MAAC9B;gBAAIC,WAAU;;kCACb,MAACC;;4BAAG;0CACM,KAACC;gCAAKF,WAAU;0CAAe;;;;oBAGxC,CAACO,8BACA,MAACJ;wBAAEH,WAAU;wBAA+BI,MAAK;;4BAAS;4BACK;0CAC7D,KAACC;gCAAKL,WAAU;0CAAoC;;4BAA4B;0CAC3E,KAAC8B;0CAAO;;4BAAwB;;;kCAKzC,MAAC/B;wBAAIC,WAAU;;0CACb,MAACD;gCAAIC,WAAU;;kDACb,MAACE;wCAAKF,WAAU;;0DACd,KAACE;gDAAKF,WAAU;0DAAgC;;4CAC/CW,YAAYH,MAAM;4CAAC;4CAAQG,YAAYH,MAAM,KAAK,IAAI,KAAK;;;kDAE9D,MAACN;wCAAKF,WAAU;;0DACd,KAACE;gDAAKF,WAAU;0DAAgC;;4CAC/C4B,aACG,IAAIX,KACF5C,oBACEI,kBAAkBmD,WAAWZ,QAAQ,GACrC,IAAIC,KAAKW,WAAWV,UAAU,IAEhCa,cAAc,CAAC5C,MAAM6C,YAAY,QACnC;;;;;0CAGR,MAACjC;gCAAIC,WAAU;;kDACb,KAACpB;kDACD,KAACC;;;;;kCAIL,KAACF;wBACC8B,OAAOE,YAAYsB,GAAG,CAAC,CAACnB,IAAO,CAAA;gCAC7BoB,aAAapB,EAAEoB,WAAW;gCAC1BlB,UAAUF,EAAEE,QAAQ;gCACpBmB,MAAMrB,EAAEqB,IAAI;gCACZjB,YAAY,IAAID,KAAKH,EAAEI,UAAU,EAAEkB,WAAW;gCAC9CC,KAAKvB,EAAEuB,GAAG;4BACZ,CAAA;wBACAf,cAAcA;wBACdI,oBAAoBA;wBACpBL,eAAeA;wBACfD,iBAAiBA;wBACjBkB,cAAcnD,MAAM6C,YAAY;;;;;;AAK1C,EAAC;AAED,eAAe9C,gBAAe"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@trieb.work/payload-plugin-backup-mongodb",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Payload CMS plugin for MongoDB backup and recovery via Vercel Blob Storage",
5
+ "homepage": "https://trwk.de/case-studies/payload-plugin-mongodb-backup-restore",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "https://github.com/trieb-work/payload-plugin-backup-mongodb.git"