From 0d1072fea88ba047e23ee5fa0185938341548b10 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Sat, 17 Feb 2024 03:38:24 +0700
Subject: [PATCH 01/42] basic isolated media server setup

---
 desci-media-isolated/.dockerignore          |    6 +
 desci-media-isolated/.eslintrc.json         |   20 +
 desci-media-isolated/.example.env           |    0
 desci-media-isolated/.gitignore             |    3 +
 desci-media-isolated/.nvmrc                 |    1 +
 desci-media-isolated/.prettierignore        |    1 +
 desci-media-isolated/.prettierrc            |    6 +
 desci-media-isolated/package-lock.json      | 2968 +++++++++++++++++++
 desci-media-isolated/package.json           |   26 +
 desci-media-isolated/src/config/index.ts    |    0
 desci-media-isolated/src/index.ts           |   12 +
 desci-media-isolated/src/routes/v1/index.ts |    0
 desci-media-isolated/tsconfig.json          |  109 +
 13 files changed, 3152 insertions(+)
 create mode 100755 desci-media-isolated/.dockerignore
 create mode 100644 desci-media-isolated/.eslintrc.json
 create mode 100644 desci-media-isolated/.example.env
 create mode 100644 desci-media-isolated/.gitignore
 create mode 100644 desci-media-isolated/.nvmrc
 create mode 100755 desci-media-isolated/.prettierignore
 create mode 100755 desci-media-isolated/.prettierrc
 create mode 100644 desci-media-isolated/package-lock.json
 create mode 100644 desci-media-isolated/package.json
 create mode 100644 desci-media-isolated/src/config/index.ts
 create mode 100644 desci-media-isolated/src/index.ts
 create mode 100644 desci-media-isolated/src/routes/v1/index.ts
 create mode 100644 desci-media-isolated/tsconfig.json

diff --git a/desci-media-isolated/.dockerignore b/desci-media-isolated/.dockerignore
new file mode 100755
index 000000000..5e3971c4b
--- /dev/null
+++ b/desci-media-isolated/.dockerignore
@@ -0,0 +1,6 @@
+config
+database
+dist
+node_modules
+.git
+.env
\ No newline at end of file
diff --git a/desci-media-isolated/.eslintrc.json b/desci-media-isolated/.eslintrc.json
new file mode 100644
index 000000000..6751428a9
--- /dev/null
+++ b/desci-media-isolated/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+    "env": {
+        "browser": true,
+        "es2021": true
+    },
+    "extends": [
+        "eslint:recommended",
+        "plugin:@typescript-eslint/recommended"
+    ],
+    "parser": "@typescript-eslint/parser",
+    "parserOptions": {
+        "ecmaVersion": "latest",
+        "sourceType": "module"
+    },
+    "plugins": [
+        "@typescript-eslint"
+    ],
+    "rules": {
+    }
+}
diff --git a/desci-media-isolated/.example.env b/desci-media-isolated/.example.env
new file mode 100644
index 000000000..e69de29bb
diff --git a/desci-media-isolated/.gitignore b/desci-media-isolated/.gitignore
new file mode 100644
index 000000000..bdfa6b815
--- /dev/null
+++ b/desci-media-isolated/.gitignore
@@ -0,0 +1,3 @@
+.env
+node_modules
+dist
\ No newline at end of file
diff --git a/desci-media-isolated/.nvmrc b/desci-media-isolated/.nvmrc
new file mode 100644
index 000000000..43bff1f8c
--- /dev/null
+++ b/desci-media-isolated/.nvmrc
@@ -0,0 +1 @@
+20.9.0
\ No newline at end of file
diff --git a/desci-media-isolated/.prettierignore b/desci-media-isolated/.prettierignore
new file mode 100755
index 000000000..53c37a166
--- /dev/null
+++ b/desci-media-isolated/.prettierignore
@@ -0,0 +1 @@
+dist
\ No newline at end of file
diff --git a/desci-media-isolated/.prettierrc b/desci-media-isolated/.prettierrc
new file mode 100755
index 000000000..6599520f8
--- /dev/null
+++ b/desci-media-isolated/.prettierrc
@@ -0,0 +1,6 @@
+{
+  "printWidth": 120,
+  "semi": true,
+  "singleQuote": true,
+  "trailingComma": "all"
+}
diff --git a/desci-media-isolated/package-lock.json b/desci-media-isolated/package-lock.json
new file mode 100644
index 000000000..c86eecbc4
--- /dev/null
+++ b/desci-media-isolated/package-lock.json
@@ -0,0 +1,2968 @@
+{
+  "name": "desci-media-isolated",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "desci-media-isolated",
+      "version": "1.0.0",
+      "license": "ISC",
+      "dependencies": {
+        "express": "^4.18.2",
+        "filepreview-soffice": "^1.1.1"
+      },
+      "devDependencies": {
+        "@types/express": "^4.17.21",
+        "@typescript-eslint/eslint-plugin": "^7.0.1",
+        "@typescript-eslint/parser": "^7.0.1",
+        "eslint": "^8.56.0",
+        "ts-node-dev": "^2.0.0",
+        "typescript": "^5.3.3"
+      }
+    },
+    "node_modules/@aashutoshrathi/word-wrap": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+      "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+      "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+      "dev": true,
+      "dependencies": {
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.10.0",
+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
+      "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/eslintrc": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.6.0",
+        "globals": "^13.19.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@eslint/eslintrc/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "8.56.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
+      "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@humanwhocodes/config-array": {
+      "version": "0.11.14",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+      "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+      "dev": true,
+      "dependencies": {
+        "@humanwhocodes/object-schema": "^2.0.2",
+        "debug": "^4.3.1",
+        "minimatch": "^3.0.5"
+      },
+      "engines": {
+        "node": ">=10.10.0"
+      }
+    },
+    "node_modules/@humanwhocodes/config-array/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@humanwhocodes/config-array/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/object-schema": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
+      "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+      "dev": true
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.4.15",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+      "dev": true
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+      "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+      "dev": true
+    },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.5",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+      "dev": true,
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+      "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "4.17.43",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
+      "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+      "dev": true
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "20.11.19",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
+      "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
+      "dev": true,
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/qs": {
+      "version": "6.9.11",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz",
+      "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==",
+      "dev": true
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "dev": true
+    },
+    "node_modules/@types/semver": {
+      "version": "7.5.7",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
+      "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
+      "dev": true
+    },
+    "node_modules/@types/send": {
+      "version": "0.17.4",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+      "dev": true,
+      "dependencies": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.5",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
+      "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/mime": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
+      "dev": true
+    },
+    "node_modules/@types/strip-json-comments": {
+      "version": "0.0.30",
+      "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz",
+      "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.1.tgz",
+      "integrity": "sha512-OLvgeBv3vXlnnJGIAgCLYKjgMEU+wBGj07MQ/nxAaON+3mLzX7mJbhRYrVGiVvFiXtwFlkcBa/TtmglHy0UbzQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.5.1",
+        "@typescript-eslint/scope-manager": "7.0.1",
+        "@typescript-eslint/type-utils": "7.0.1",
+        "@typescript-eslint/utils": "7.0.1",
+        "@typescript-eslint/visitor-keys": "7.0.1",
+        "debug": "^4.3.4",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.4",
+        "natural-compare": "^1.4.0",
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^7.0.0",
+        "eslint": "^8.56.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.0.1.tgz",
+      "integrity": "sha512-8GcRRZNzaHxKzBPU3tKtFNing571/GwPBeCvmAUw0yBtfE2XVd0zFKJIMSWkHJcPQi0ekxjIts6L/rrZq5cxGQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "7.0.1",
+        "@typescript-eslint/types": "7.0.1",
+        "@typescript-eslint/typescript-estree": "7.0.1",
+        "@typescript-eslint/visitor-keys": "7.0.1",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.56.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/parser/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/parser/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.1.tgz",
+      "integrity": "sha512-v7/T7As10g3bcWOOPAcbnMDuvctHzCFYCG/8R4bK4iYzdFqsZTbXGln0cZNVcwQcwewsYU2BJLay8j0/4zOk4w==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "7.0.1",
+        "@typescript-eslint/visitor-keys": "7.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.1.tgz",
+      "integrity": "sha512-YtT9UcstTG5Yqy4xtLiClm1ZpM/pWVGFnkAa90UfdkkZsR1eP2mR/1jbHeYp8Ay1l1JHPyGvoUYR6o3On5Nhmw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/typescript-estree": "7.0.1",
+        "@typescript-eslint/utils": "7.0.1",
+        "debug": "^4.3.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.56.0"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.1.tgz",
+      "integrity": "sha512-uJDfmirz4FHib6ENju/7cz9SdMSkeVvJDK3VcMFvf/hAShg8C74FW+06MaQPODHfDJp/z/zHfgawIJRjlu0RLg==",
+      "dev": true,
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.1.tgz",
+      "integrity": "sha512-SO9wHb6ph0/FN5OJxH4MiPscGah5wjOd0RRpaLvuBv9g8565Fgu0uMySFEPqwPHiQU90yzJ2FjRYKGrAhS1xig==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "7.0.1",
+        "@typescript-eslint/visitor-keys": "7.0.1",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "minimatch": "9.0.3",
+        "semver": "^7.5.4",
+        "ts-api-utils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+      "version": "9.0.3",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+      "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/utils": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.1.tgz",
+      "integrity": "sha512-oe4his30JgPbnv+9Vef1h48jm0S6ft4mNwi9wj7bX10joGn07QRfqIqFHoMiajrtoU88cIhXf8ahwgrcbNLgPA==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@types/json-schema": "^7.0.12",
+        "@types/semver": "^7.5.0",
+        "@typescript-eslint/scope-manager": "7.0.1",
+        "@typescript-eslint/types": "7.0.1",
+        "@typescript-eslint/typescript-estree": "7.0.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.56.0"
+      }
+    },
+    "node_modules/@typescript-eslint/visitor-keys": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.1.tgz",
+      "integrity": "sha512-hwAgrOyk++RTXrP4KzCg7zB2U0xt7RUU0ZdMSCsqF3eKUwkdXUMyTb0qdCuji7VIbcpG62kKTU9M1J1c9UpFBw==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "7.0.1",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^16.0.0 || >=18.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@ungap/structured-clone": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+      "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+      "dev": true
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/acorn": {
+      "version": "8.11.3",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+      "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true,
+      "peerDependencies": {
+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+      }
+    },
+    "node_modules/acorn-walk": {
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
+      "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "dependencies": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
+    "node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+    },
+    "node_modules/array-union": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+      "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.1",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+      "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.11.0",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "dependencies": {
+        "fill-range": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+      "dev": true
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+    },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
+    "node_modules/dir-glob": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+      "dev": true,
+      "dependencies": {
+        "path-type": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "dependencies": {
+        "esutils": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/dynamic-dedupe": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
+      "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
+      "dev": true,
+      "dependencies": {
+        "xtend": "^4.0.0"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint": {
+      "version": "8.56.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
+      "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.6.1",
+        "@eslint/eslintrc": "^2.1.4",
+        "@eslint/js": "8.56.0",
+        "@humanwhocodes/config-array": "^0.11.13",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@nodelib/fs.walk": "^1.2.8",
+        "@ungap/structured-clone": "^1.2.0",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.2.2",
+        "eslint-visitor-keys": "^3.4.3",
+        "espree": "^9.6.1",
+        "esquery": "^1.4.2",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "globals": "^13.19.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "is-path-inside": "^3.0.3",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "strip-ansi": "^6.0.1",
+        "text-table": "^0.2.0"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-scope": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/eslint/node_modules/debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "dependencies": {
+        "ms": "2.1.2"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/eslint/node_modules/glob-parent": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.3"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/eslint/node_modules/ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "node_modules/espree": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.9.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/esquery": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+      "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.1.0"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "dependencies": {
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/estraverse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
+    "node_modules/esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.18.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.1",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.5.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.2.0",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.11.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+      "dev": true,
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "node_modules/fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "node_modules/fastq": {
+      "version": "1.17.1",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+      "dev": true,
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^3.0.4"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/filepreview-soffice": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/filepreview-soffice/-/filepreview-soffice-1.1.1.tgz",
+      "integrity": "sha512-DbmjeHsAgRYpgor7lwracD6NHyHJIBVTd4mk6xtc0+SIIqSG6JC2orLLvP9fSz7BIvLRgYvdCH6mtMW5y1u0HQ=="
+    },
+    "node_modules/fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/flat-cache": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+      "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.3",
+        "rimraf": "^3.0.2"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/flat-cache/node_modules/rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/flatted": {
+      "version": "3.2.9",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
+      "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+      "dev": true
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "has-proto": "^1.0.1",
+        "has-symbols": "^1.0.3",
+        "hasown": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/globals": {
+      "version": "13.24.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.20.2"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/globby": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+      "dev": true,
+      "dependencies": {
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.2.9",
+        "ignore": "^5.2.0",
+        "merge2": "^1.4.1",
+        "slash": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "dependencies": {
+        "get-intrinsic": "^1.1.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graphemer": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+      "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+      "dev": true
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
+      "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ignore": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+      "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "dependencies": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.19"
+      }
+    },
+    "node_modules/inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "dependencies": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-core-module": {
+      "version": "2.13.1",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+      "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+      "dev": true,
+      "dependencies": {
+        "hasown": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-path-inside": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "node_modules/js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "dependencies": {
+        "argparse": "^2.0.1"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "dev": true
+    },
+    "node_modules/json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "node_modules/json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "dev": true,
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+      "dev": true,
+      "bin": {
+        "mkdirp": "bin/cmd.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+    },
+    "node_modules/natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+      "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/optionator": {
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+      "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+      "dev": true,
+      "dependencies": {
+        "@aashutoshrathi/word-wrap": "^1.2.3",
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "dependencies": {
+        "callsites": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+    },
+    "node_modules/path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+      "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+      "dependencies": {
+        "side-channel": "^1.0.4"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.8",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+      "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+      "dev": true,
+      "dependencies": {
+        "is-core-module": "^2.13.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true,
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "node_modules/semver": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/send": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node_modules/serve-static": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+      "dependencies": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.18.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/set-function-length": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
+      "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
+      "dependencies": {
+        "define-data-property": "^1.1.2",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.3",
+        "gopd": "^1.0.1",
+        "has-property-descriptors": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
+      "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
+      "dependencies": {
+        "call-bind": "^1.0.6",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-support": {
+      "version": "0.5.21",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+      "dev": true,
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/tree-kill": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+      "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+      "dev": true,
+      "bin": {
+        "tree-kill": "cli.js"
+      }
+    },
+    "node_modules/ts-api-utils": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
+      "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
+      "dev": true,
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.2.0"
+      }
+    },
+    "node_modules/ts-node": {
+      "version": "10.9.2",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+      "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+      "dev": true,
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/ts-node-dev": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
+      "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.5.1",
+        "dynamic-dedupe": "^0.3.0",
+        "minimist": "^1.2.6",
+        "mkdirp": "^1.0.4",
+        "resolve": "^1.0.0",
+        "rimraf": "^2.6.1",
+        "source-map-support": "^0.5.12",
+        "tree-kill": "^1.2.2",
+        "ts-node": "^10.4.0",
+        "tsconfig": "^7.0.0"
+      },
+      "bin": {
+        "ts-node-dev": "lib/bin.js",
+        "tsnd": "lib/bin.js"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      },
+      "peerDependencies": {
+        "node-notifier": "*",
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "node-notifier": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tsconfig": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
+      "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
+      "dev": true,
+      "dependencies": {
+        "@types/strip-bom": "^3.0.0",
+        "@types/strip-json-comments": "0.0.30",
+        "strip-bom": "^3.0.0",
+        "strip-json-comments": "^2.0.0"
+      }
+    },
+    "node_modules/type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "dependencies": {
+        "prelude-ls": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.3.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+      "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "dev": true
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "dependencies": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    }
+  }
+}
diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
new file mode 100644
index 000000000..7883dab31
--- /dev/null
+++ b/desci-media-isolated/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "desci-media-isolated",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "node dist/index.js",
+    "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
+    "build": "tsc"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "express": "^4.18.2",
+    "filepreview-soffice": "^1.1.1"
+  },
+  "devDependencies": {
+    "@types/express": "^4.17.21",
+    "@typescript-eslint/eslint-plugin": "^7.0.1",
+    "@typescript-eslint/parser": "^7.0.1",
+    "eslint": "^8.56.0",
+    "ts-node-dev": "^2.0.0",
+    "typescript": "^5.3.3"
+  }
+}
diff --git a/desci-media-isolated/src/config/index.ts b/desci-media-isolated/src/config/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/desci-media-isolated/src/index.ts b/desci-media-isolated/src/index.ts
new file mode 100644
index 000000000..9e9f09de7
--- /dev/null
+++ b/desci-media-isolated/src/index.ts
@@ -0,0 +1,12 @@
+import express from 'express';
+
+const app = express();
+const PORT = process.env.PORT || 7777;
+
+app.get('/health', (req, res) => {
+  return res.send('healthy');
+});
+
+app.listen(PORT, () => {
+  console.log(`[Media Server Isolated]Server is listening on port: ${PORT}`);
+});
diff --git a/desci-media-isolated/src/routes/v1/index.ts b/desci-media-isolated/src/routes/v1/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/desci-media-isolated/tsconfig.json b/desci-media-isolated/tsconfig.json
new file mode 100644
index 000000000..e075f973c
--- /dev/null
+++ b/desci-media-isolated/tsconfig.json
@@ -0,0 +1,109 @@
+{
+  "compilerOptions": {
+    /* Visit https://aka.ms/tsconfig to read more about this file */
+
+    /* Projects */
+    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
+    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
+    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
+    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
+    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
+
+    /* Language and Environment */
+    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
+    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
+    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
+    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
+    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
+    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
+
+    /* Modules */
+    "module": "commonjs",                                /* Specify what module code is generated. */
+    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
+    // "moduleResolution": "node10",                     /* Specify how TypeScript looks up a file from a given module specifier. */
+    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
+    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
+    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
+    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
+    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
+    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
+    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
+    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
+    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
+    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+    // "resolveJsonModule": true,                        /* Enable importing .json files. */
+    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
+    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+
+    /* JavaScript Support */
+    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
+    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+    /* Emit */
+    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
+    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
+    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
+    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
+    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
+    // "removeComments": true,                           /* Disable emitting comments. */
+    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
+    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
+    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
+    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
+    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
+    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
+    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
+    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
+    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
+    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
+    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+    /* Interop Constraints */
+    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
+    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
+    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
+
+    /* Type Checking */
+    "strict": true,                                      /* Enable all strict type-checking options. */
+    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
+    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
+    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
+    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
+    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
+    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
+    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
+    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
+    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
+    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
+    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
+    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
+    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
+    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
+    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
+
+    /* Completeness */
+    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
+    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
+  }
+}

From d0767132309d731e0d5b51d2c6fd07817b420113 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Tue, 20 Feb 2024 04:05:03 +0700
Subject: [PATCH 02/42] config/setup, add Dockerfile

---
 desci-media-isolated/.eslintrc.json      | 33 ++++-----
 desci-media-isolated/.example.env        |  1 +
 desci-media-isolated/.gitignore          |  4 +-
 desci-media-isolated/Dockerfile          | 65 +++++++++++++++++
 desci-media-isolated/package-lock.json   | 91 ++++++++++++++++++++++--
 desci-media-isolated/package.json        |  4 +-
 desci-media-isolated/src/config/index.ts |  4 ++
 desci-media-isolated/tsconfig.json       | 13 ++--
 8 files changed, 184 insertions(+), 31 deletions(-)
 create mode 100644 desci-media-isolated/Dockerfile

diff --git a/desci-media-isolated/.eslintrc.json b/desci-media-isolated/.eslintrc.json
index 6751428a9..e9101e7fc 100644
--- a/desci-media-isolated/.eslintrc.json
+++ b/desci-media-isolated/.eslintrc.json
@@ -1,20 +1,17 @@
 {
-    "env": {
-        "browser": true,
-        "es2021": true
-    },
-    "extends": [
-        "eslint:recommended",
-        "plugin:@typescript-eslint/recommended"
-    ],
-    "parser": "@typescript-eslint/parser",
-    "parserOptions": {
-        "ecmaVersion": "latest",
-        "sourceType": "module"
-    },
-    "plugins": [
-        "@typescript-eslint"
-    ],
-    "rules": {
-    }
+  "env": {
+    "browser": true,
+    "es2021": true
+  },
+  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "ecmaVersion": "latest",
+    "sourceType": "module"
+  },
+  "plugins": ["@typescript-eslint"],
+  "rules": {
+    "@typescript-eslint/no-explicit-any": 0,
+    "@typescript-eslint/no-unused-vars": 0
+  }
 }
diff --git a/desci-media-isolated/.example.env b/desci-media-isolated/.example.env
index e69de29bb..621fc17e8 100644
--- a/desci-media-isolated/.example.env
+++ b/desci-media-isolated/.example.env
@@ -0,0 +1 @@
+IPFS_GATEWAY=https://ipfs.desci.com/ipfs
\ No newline at end of file
diff --git a/desci-media-isolated/.gitignore b/desci-media-isolated/.gitignore
index bdfa6b815..ec3b56bda 100644
--- a/desci-media-isolated/.gitignore
+++ b/desci-media-isolated/.gitignore
@@ -1,3 +1,5 @@
 .env
 node_modules
-dist
\ No newline at end of file
+dist
+.npm
+.temp
\ No newline at end of file
diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
new file mode 100644
index 000000000..5a46e9c03
--- /dev/null
+++ b/desci-media-isolated/Dockerfile
@@ -0,0 +1,65 @@
+
+FROM docker.io/node:20.9.0-alpine as base
+
+# Install dumb-init so we can use it as PID 1
+RUN apk update && apk add --no-cache dumb-init
+
+# No longer using libre office file preview remove soon
+# ARG APP_ROOT=/opt/app-root/src
+# ENV NO_UPDATE_NOTIFIER=true \
+#   PATH="/usr/lib/libreoffice/program:${PATH}" \
+#   PYTHONUNBUFFERED=1
+# WORKDIR ${APP_ROOT}
+
+# # Install LibreOffice & Common Fonts
+# RUN apk --no-cache add bash libreoffice util-linux \
+#   font-droid-nonlatin font-droid ttf-dejavu ttf-freefont ttf-liberation && \
+#   rm -rf /var/cache/apk/*
+
+# # Install Microsoft Core Fonts
+# RUN apk --no-cache add msttcorefonts-installer fontconfig && \
+#   update-ms-fonts && \
+#   fc-cache -f && \
+#   rm -rf /var/cache/apk/*
+
+# # Fix Python/LibreOffice Integration
+# COPY container/libreOffice ${APP_ROOT}/container/libreOffice
+# RUN chmod a+rx ${APP_ROOT}/container/libreOffice/bindPython.sh \
+#   && ${APP_ROOT}/container/libreOffice/bindPython.sh
+
+# NPM Permission Fix
+RUN mkdir -p /.npm
+RUN chown -R 1001:0 /.npm
+
+# App Setup
+WORKDIR /usr/src/app
+
+COPY package*.json ./
+
+FROM base as dev
+
+RUN --mount=type=cache,target=/usr/src/app/.npm \
+  npm set cache /usr/src/app/.npm && \
+  npm install
+
+COPY . .
+
+CMD ["dumb-init", "npx", "ts-node-dev", "--respawn", "--transpile-only", "src/index.ts"]
+
+FROM base as production
+
+ENV NODE_ENV production
+
+# Cache mounts for faster builds, prod env for better express perf
+RUN --mount=type=cache,target=/usr/src/app/.npm \
+  npm set cache /usr/src/app/.npm && \
+  npm ci --only=production
+
+
+# 'node' user is created by the node image, prevent perm issues, run with reduced privs
+USER node
+COPY --chown=node:node ./src/ .
+RUN npm run build
+
+
+CMD ["dumb-init", "node", "dist/index.js"]
diff --git a/desci-media-isolated/package-lock.json b/desci-media-isolated/package-lock.json
index c86eecbc4..77e762c38 100644
--- a/desci-media-isolated/package-lock.json
+++ b/desci-media-isolated/package-lock.json
@@ -9,8 +9,10 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
+        "axios": "^1.6.7",
         "express": "^4.18.2",
-        "filepreview-soffice": "^1.1.1"
+        "filepreview_ts": "^1.0.0",
+        "helmet": "^7.1.0"
       },
       "devDependencies": {
         "@types/express": "^4.17.21",
@@ -827,6 +829,21 @@
         "node": ">=8"
       }
     },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+    },
+    "node_modules/axios": {
+      "version": "1.6.7",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+      "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+      "dependencies": {
+        "follow-redirects": "^1.15.4",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -986,6 +1003,17 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "dev": true
     },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1074,6 +1102,14 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/depd": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -1463,10 +1499,10 @@
         "node": "^10.12.0 || >=12.0.0"
       }
     },
-    "node_modules/filepreview-soffice": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/filepreview-soffice/-/filepreview-soffice-1.1.1.tgz",
-      "integrity": "sha512-DbmjeHsAgRYpgor7lwracD6NHyHJIBVTd4mk6xtc0+SIIqSG6JC2orLLvP9fSz7BIvLRgYvdCH6mtMW5y1u0HQ=="
+    "node_modules/filepreview_ts": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/filepreview_ts/-/filepreview_ts-1.0.0.tgz",
+      "integrity": "sha512-BhdiTLhW0Uzg672Zg9HhcD/YvLWJMNSKzzi2zsQAaA6SqVZfH+pefUaRdXKW5aVeZetUQHw0mkHm2LBfvTFf/g=="
     },
     "node_modules/fill-range": {
       "version": "7.0.1",
@@ -1548,6 +1584,38 @@
       "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
       "dev": true
     },
+    "node_modules/follow-redirects": {
+      "version": "1.15.5",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
+      "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1747,6 +1815,14 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/helmet": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
+      "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==",
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
     "node_modules/http-errors": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -2299,6 +2375,11 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    },
     "node_modules/punycode": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
index 7883dab31..6dd984c5e 100644
--- a/desci-media-isolated/package.json
+++ b/desci-media-isolated/package.json
@@ -12,8 +12,10 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "axios": "^1.6.7",
     "express": "^4.18.2",
-    "filepreview-soffice": "^1.1.1"
+    "filepreview_ts": "^1.0.0",
+    "helmet": "^7.1.0"
   },
   "devDependencies": {
     "@types/express": "^4.17.21",
diff --git a/desci-media-isolated/src/config/index.ts b/desci-media-isolated/src/config/index.ts
index e69de29bb..94ff1ff43 100644
--- a/desci-media-isolated/src/config/index.ts
+++ b/desci-media-isolated/src/config/index.ts
@@ -0,0 +1,4 @@
+export const IPFS_GATEWAY = process.env.IPFS_GATEWAY;
+export const TEMP_DIR = '.temp';
+export const THUMBNAIL_FILES_DIR = `${TEMP_DIR}/files`;
+export const THUMBNAIL_OUTPUT_DIR = `${TEMP_DIR}/thumbnails`;
diff --git a/desci-media-isolated/tsconfig.json b/desci-media-isolated/tsconfig.json
index e075f973c..1fbd165a5 100644
--- a/desci-media-isolated/tsconfig.json
+++ b/desci-media-isolated/tsconfig.json
@@ -11,7 +11,8 @@
     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 
     /* Language and Environment */
-    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+    "outDir": "./dist",
+    "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
@@ -25,7 +26,7 @@
     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 
     /* Modules */
-    "module": "commonjs",                                /* Specify what module code is generated. */
+    "module": "commonjs" /* Specify what module code is generated. */,
     // "rootDir": "./",                                  /* Specify the root folder within your source files. */
     // "moduleResolution": "node10",                     /* Specify how TypeScript looks up a file from a given module specifier. */
     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
@@ -77,12 +78,12 @@
     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
     // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
-    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
-    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
+    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
 
     /* Type Checking */
-    "strict": true,                                      /* Enable all strict type-checking options. */
+    "strict": true /* Enable all strict type-checking options. */,
     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
@@ -104,6 +105,6 @@
 
     /* Completeness */
     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
-    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
+    "skipLibCheck": true /* Skip type checking all .d.ts files. */
   }
 }

From 99b9dd6570c0a62b52c09eb669bf37f430c73653 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Tue, 20 Feb 2024 04:05:33 +0700
Subject: [PATCH 03/42] create thumbnail service/routes, create ipfs service,
 add err handling

---
 .../src/controllers/thumbnails/create.ts      | 41 ++++++++++++++++
 desci-media-isolated/src/index.ts             | 13 ++++-
 .../src/middleware/errorHandler.ts            | 16 ++++++
 desci-media-isolated/src/routes/index.ts      |  8 +++
 desci-media-isolated/src/routes/v1/index.ts   |  8 +++
 .../src/routes/v1/thumbnails.ts               |  8 +++
 desci-media-isolated/src/services/ipfs.ts     | 30 ++++++++++++
 .../src/services/thumbnails.ts                | 46 +++++++++++++++++
 .../src/utils/customErrors.ts                 | 49 +++++++++++++++++++
 9 files changed, 218 insertions(+), 1 deletion(-)
 create mode 100644 desci-media-isolated/src/controllers/thumbnails/create.ts
 create mode 100644 desci-media-isolated/src/middleware/errorHandler.ts
 create mode 100644 desci-media-isolated/src/routes/index.ts
 create mode 100644 desci-media-isolated/src/routes/v1/thumbnails.ts
 create mode 100644 desci-media-isolated/src/services/ipfs.ts
 create mode 100644 desci-media-isolated/src/services/thumbnails.ts
 create mode 100644 desci-media-isolated/src/utils/customErrors.ts

diff --git a/desci-media-isolated/src/controllers/thumbnails/create.ts b/desci-media-isolated/src/controllers/thumbnails/create.ts
new file mode 100644
index 000000000..ab9ecf2f2
--- /dev/null
+++ b/desci-media-isolated/src/controllers/thumbnails/create.ts
@@ -0,0 +1,41 @@
+import { Request, Response } from 'express';
+import { ThumbnailsService } from '../../services/thumbnails';
+import path from 'path';
+import fs from 'fs';
+import { TEMP_DIR } from '../../config';
+import { BadRequestError, NotFoundError } from '../../utils/customErrors';
+
+export type GenerateThumbnailRequestBody = {
+  cid: string;
+  fileName: string;
+};
+
+const BASE_TEMP_DIR = path.resolve(__dirname, '../../..', TEMP_DIR);
+
+export const generateThumbnail = async (req: Request<any, any, GenerateThumbnailRequestBody>, res: Response) => {
+  const { cid, fileName } = req.body;
+  if (!cid) throw new BadRequestError('Missing cid in request body');
+  if (!fileName) throw new BadRequestError('Missing fileName in request body');
+
+  try {
+    const thumbnailPath = await ThumbnailsService.generateThumbnail(cid);
+    const fullThumbnailPath = path.join(BASE_TEMP_DIR, thumbnailPath);
+
+    // Check if the file exists before attempting to stream it
+    fs.access(fullThumbnailPath, fs.constants.F_OK, (err) => {
+      if (err) {
+        throw new NotFoundError(`Thumbnail not found for file with cid: ${cid}`);
+      }
+
+      res.setHeader('Content-Type', 'image/png');
+      const readStream = fs.createReadStream(fullThumbnailPath);
+
+      readStream.pipe(res);
+    });
+
+    // Send the thumbnail as a response
+    res.status(200);
+  } catch (err: any) {
+    res.status(500).json({ message: err.message });
+  }
+};
diff --git a/desci-media-isolated/src/index.ts b/desci-media-isolated/src/index.ts
index 9e9f09de7..c68b0d7e1 100644
--- a/desci-media-isolated/src/index.ts
+++ b/desci-media-isolated/src/index.ts
@@ -1,12 +1,23 @@
 import express from 'express';
+import helmet from 'helmet';
+import { errorHandler } from './middleware/errorHandler';
+import routes from './routes';
 
 const app = express();
-const PORT = process.env.PORT || 7777;
+const PORT = process.env.PORT || 7771;
+
+app.use(helmet());
+app.use(express.json());
 
 app.get('/health', (req, res) => {
   return res.send('healthy');
 });
 
+app.use('/', routes);
+
+// Keep after all routes
+app.use(errorHandler);
+
 app.listen(PORT, () => {
   console.log(`[Media Server Isolated]Server is listening on port: ${PORT}`);
 });
diff --git a/desci-media-isolated/src/middleware/errorHandler.ts b/desci-media-isolated/src/middleware/errorHandler.ts
new file mode 100644
index 000000000..91135f88a
--- /dev/null
+++ b/desci-media-isolated/src/middleware/errorHandler.ts
@@ -0,0 +1,16 @@
+import { Request, Response, NextFunction } from 'express';
+import { BaseError } from '../utils/customErrors';
+
+export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
+  console.error(err.stack);
+
+  let statusCode = 500;
+  if (err instanceof BaseError && typeof err.statusCode === 'number') {
+    statusCode = err.statusCode;
+  }
+
+  res.status(statusCode).json({
+    message: err.message || 'Something went wrong',
+    stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
+  });
+};
diff --git a/desci-media-isolated/src/routes/index.ts b/desci-media-isolated/src/routes/index.ts
new file mode 100644
index 000000000..d7c2b6204
--- /dev/null
+++ b/desci-media-isolated/src/routes/index.ts
@@ -0,0 +1,8 @@
+import { Router } from 'express';
+import v1 from './v1/index';
+
+const router = Router();
+
+router.use(`/v1`, v1);
+
+export default router;
diff --git a/desci-media-isolated/src/routes/v1/index.ts b/desci-media-isolated/src/routes/v1/index.ts
index e69de29bb..e631244d4 100644
--- a/desci-media-isolated/src/routes/v1/index.ts
+++ b/desci-media-isolated/src/routes/v1/index.ts
@@ -0,0 +1,8 @@
+import { Router } from 'express';
+import thumbnails from './thumbnails';
+
+const router = Router();
+
+router.use('/thumbnails', thumbnails);
+
+export default router;
diff --git a/desci-media-isolated/src/routes/v1/thumbnails.ts b/desci-media-isolated/src/routes/v1/thumbnails.ts
new file mode 100644
index 000000000..df4a339d1
--- /dev/null
+++ b/desci-media-isolated/src/routes/v1/thumbnails.ts
@@ -0,0 +1,8 @@
+import { Router } from 'express';
+import { generateThumbnail } from '../../controllers/thumbnails/create';
+
+const router = Router();
+
+router.post('/', generateThumbnail);
+
+export default router;
diff --git a/desci-media-isolated/src/services/ipfs.ts b/desci-media-isolated/src/services/ipfs.ts
new file mode 100644
index 000000000..7e6abd92f
--- /dev/null
+++ b/desci-media-isolated/src/services/ipfs.ts
@@ -0,0 +1,30 @@
+import axios from 'axios';
+import fs from 'fs';
+import { pipeline } from 'stream/promises';
+import { IPFS_GATEWAY } from '../config';
+import { IpfsConfigurationError } from '../utils/customErrors';
+
+export class IpfsService {
+  static async saveFile(cid: string, outputPath: string) {
+    if (!IPFS_GATEWAY) {
+      throw new IpfsConfigurationError('process.env.IPFS_GATEWAY is not defined in environment variables');
+    }
+
+    const url = `${IPFS_GATEWAY}/${cid}`;
+
+    try {
+      const response = await axios({
+        method: 'get',
+        url: url,
+        responseType: 'stream',
+      });
+
+      await pipeline(response.data, fs.createWriteStream(outputPath));
+
+      console.log(`File downloaded and saved to ${outputPath}`);
+    } catch (error) {
+      console.error('Error downloading or saving the file:', error);
+      throw error;
+    }
+  }
+}
diff --git a/desci-media-isolated/src/services/thumbnails.ts b/desci-media-isolated/src/services/thumbnails.ts
new file mode 100644
index 000000000..95e49770c
--- /dev/null
+++ b/desci-media-isolated/src/services/thumbnails.ts
@@ -0,0 +1,46 @@
+import { generateAsync } from 'filepreview_ts';
+import { TEMP_DIR, THUMBNAIL_FILES_DIR, THUMBNAIL_OUTPUT_DIR } from '../config';
+import { IpfsService } from './ipfs';
+import { UnhandledError } from '../utils/customErrors';
+import path from 'path';
+import fs from 'fs';
+
+const THUMBNAIL_DIMENSIONS = {
+  width: 220,
+  height: 300,
+};
+const BASE_TEMP_DIR = path.resolve(__dirname, '..', TEMP_DIR);
+
+export class ThumbnailsService {
+  static async generateThumbnail(cid: string) {
+    const tempFilePath = path.join(BASE_TEMP_DIR, THUMBNAIL_FILES_DIR, `${cid}`);
+    const thumbnailPath = this.getThumbnailPath(cid);
+    const exportPath = path.join(BASE_TEMP_DIR, THUMBNAIL_OUTPUT_DIR, thumbnailPath);
+
+    await IpfsService.saveFile(cid, tempFilePath);
+    try {
+      await generateAsync(tempFilePath, exportPath, THUMBNAIL_DIMENSIONS);
+      return thumbnailPath;
+    } catch (e) {
+      console.error(e);
+      throw new UnhandledError(`Failed generating thumbnail for file with cid: ${cid}`);
+    } finally {
+      // The initially saved file is removed, however the thumbnail remains. Further cleanup can be done for the thumbnail.
+      try {
+        await fs.unlink(tempFilePath, (err) => {
+          if (err) {
+            console.error(err, `Failed to cleanup temporary file: ${tempFilePath}`);
+            return;
+          }
+          console.log(`Temporary file ${tempFilePath} deleted successfully.`);
+        });
+      } catch (cleanupError) {
+        console.error(`Failed to delete temporary file ${tempFilePath}:`, cleanupError);
+      }
+    }
+  }
+
+  static getThumbnailPath(cid: string) {
+    return `${THUMBNAIL_DIMENSIONS.width}x${THUMBNAIL_DIMENSIONS.height}_${cid}`;
+  }
+}
diff --git a/desci-media-isolated/src/utils/customErrors.ts b/desci-media-isolated/src/utils/customErrors.ts
new file mode 100644
index 000000000..ec577effe
--- /dev/null
+++ b/desci-media-isolated/src/utils/customErrors.ts
@@ -0,0 +1,49 @@
+import crypto from 'crypto';
+
+export class BaseError extends Error {
+  statusCode: number;
+
+  constructor(message: string, statusCode: number) {
+    super(message);
+    this.statusCode = statusCode;
+
+    // Restores the prototype chain
+    Object.setPrototypeOf(this, new.target.prototype);
+  }
+}
+
+export class NotFoundError extends BaseError {
+  constructor(message = 'Resource not found') {
+    super(message, 404);
+  }
+}
+
+export class BadRequestError extends BaseError {
+  constructor(message = 'Bad request') {
+    super(message, 400);
+  }
+}
+
+export class UnhandledError extends BaseError {
+  constructor(message = `Unhandled error occured, error reference: ${crypto.randomUUID()}`) {
+    super(message, 500);
+  }
+}
+
+export class InternalServerError extends BaseError {
+  constructor(message = 'Internal server error') {
+    super(message, 500);
+  }
+}
+
+export class IpfsConfigurationError extends BaseError {
+  constructor(message = 'IPFS Misconfigured') {
+    super(message, 500);
+  }
+}
+
+export class IpfsFetchError extends BaseError {
+  constructor(message = 'Failed to retrieve file from IPFS') {
+    super(message, 502);
+  }
+}

From ca483826ddc2395e58219b242ef444931d27741c Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Tue, 20 Feb 2024 23:41:20 +0700
Subject: [PATCH 04/42] switch to a debian based image for package support

---
 desci-media-isolated/Dockerfile | 40 +++++++++------------------------
 1 file changed, 11 insertions(+), 29 deletions(-)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 5a46e9c03..1704d2786 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -1,31 +1,12 @@
 
-FROM docker.io/node:20.9.0-alpine as base
+FROM docker.io/node:20.9.0 as base
 
 # Install dumb-init so we can use it as PID 1
-RUN apk update && apk add --no-cache dumb-init
-
-# No longer using libre office file preview remove soon
-# ARG APP_ROOT=/opt/app-root/src
-# ENV NO_UPDATE_NOTIFIER=true \
-#   PATH="/usr/lib/libreoffice/program:${PATH}" \
-#   PYTHONUNBUFFERED=1
-# WORKDIR ${APP_ROOT}
-
-# # Install LibreOffice & Common Fonts
-# RUN apk --no-cache add bash libreoffice util-linux \
-#   font-droid-nonlatin font-droid ttf-dejavu ttf-freefont ttf-liberation && \
-#   rm -rf /var/cache/apk/*
-
-# # Install Microsoft Core Fonts
-# RUN apk --no-cache add msttcorefonts-installer fontconfig && \
-#   update-ms-fonts && \
-#   fc-cache -f && \
-#   rm -rf /var/cache/apk/*
-
-# # Fix Python/LibreOffice Integration
-# COPY container/libreOffice ${APP_ROOT}/container/libreOffice
-# RUN chmod a+rx ${APP_ROOT}/container/libreOffice/bindPython.sh \
-#   && ${APP_ROOT}/container/libreOffice/bindPython.sh
+# RUN apk update && apk add --no-cache dumb-init
+# RUN apk add --no-cache unoconv ffmpeg imagemagick curl
+
+RUN apt-get update && apt-get install -y dumb-init unoconv ffmpeg imagemagick curl && \
+    rm -rf /var/lib/apt/lists/*
 
 # NPM Permission Fix
 RUN mkdir -p /.npm
@@ -44,13 +25,14 @@ RUN --mount=type=cache,target=/usr/src/app/.npm \
 
 COPY . .
 
-CMD ["dumb-init", "npx", "ts-node-dev", "--respawn", "--transpile-only", "src/index.ts"]
-
-FROM base as production
+# Expose debugger port
+EXPOSE 9277
 
-ENV NODE_ENV production
+CMD ["dumb-init", "npx", "tsx","watch", "--inspect=0.0.0.0:9277", "src/index.ts"]
 
+FROM base as production
 # Cache mounts for faster builds, prod env for better express perf
+ENV NODE_ENV production
 RUN --mount=type=cache,target=/usr/src/app/.npm \
   npm set cache /usr/src/app/.npm && \
   npm ci --only=production

From a3965649e562aa2d33c5371b8ffa751d8b8dd1ed Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Tue, 20 Feb 2024 23:42:40 +0700
Subject: [PATCH 05/42] configure support for es modules, add debugging

---
 .vscode/launch.json                    |  16 +
 desci-media-isolated/.dockerignore     |   2 +-
 desci-media-isolated/package-lock.json | 435 ++++++++++++++++++++++++-
 desci-media-isolated/package.json      |   7 +-
 desci-media-isolated/tsconfig.json     |  18 +-
 5 files changed, 466 insertions(+), 12 deletions(-)

diff --git a/.vscode/launch.json b/.vscode/launch.json
index 069982987..e0d58f439 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -43,6 +43,22 @@
       "address": "localhost",
       "localRoot": "${workspaceFolder}",
       "remoteRoot": "/app"
+    },
+    {
+      "name": "media-isolated tsx",
+      "type": "node",
+      "request": "attach",
+      "restart": true,
+      "localRoot": "${workspaceFolder}/desci-media-isolated",
+      "remoteRoot": "/usr/src/app",
+      "port": 9277,
+      "skipFiles": [
+        // Node.js internal core modules
+        "<node_internals>/**",
+
+        // Ignore all dependencies (optional)
+        "${workspaceFolder}/node_modules/**"
+      ]
     }
   ]
 }
diff --git a/desci-media-isolated/.dockerignore b/desci-media-isolated/.dockerignore
index 5e3971c4b..0af6aafe9 100755
--- a/desci-media-isolated/.dockerignore
+++ b/desci-media-isolated/.dockerignore
@@ -3,4 +3,4 @@ database
 dist
 node_modules
 .git
-.env
\ No newline at end of file
+# .env
\ No newline at end of file
diff --git a/desci-media-isolated/package-lock.json b/desci-media-isolated/package-lock.json
index 77e762c38..32d011c98 100644
--- a/desci-media-isolated/package-lock.json
+++ b/desci-media-isolated/package-lock.json
@@ -10,9 +10,11 @@
       "license": "ISC",
       "dependencies": {
         "axios": "^1.6.7",
+        "dotenv": "^16.4.5",
         "express": "^4.18.2",
         "filepreview_ts": "^1.0.0",
-        "helmet": "^7.1.0"
+        "helmet": "^7.1.0",
+        "tsx": "^4.7.1"
       },
       "devDependencies": {
         "@types/express": "^4.17.21",
@@ -44,6 +46,351 @@
         "node": ">=12"
       }
     },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+      "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+      "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+      "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+      "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+      "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+      "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+      "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+      "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+      "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+      "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+      "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+      "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+      "cpu": [
+        "loong64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+      "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+      "cpu": [
+        "mips64el"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+      "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+      "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+      "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+      "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+      "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+      "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+      "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+      "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+      "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+      "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1160,6 +1507,17 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/dotenv": {
+      "version": "16.4.5",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+      "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
     "node_modules/dynamic-dedupe": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
@@ -1201,6 +1559,43 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/esbuild": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+      "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.19.12",
+        "@esbuild/android-arm": "0.19.12",
+        "@esbuild/android-arm64": "0.19.12",
+        "@esbuild/android-x64": "0.19.12",
+        "@esbuild/darwin-arm64": "0.19.12",
+        "@esbuild/darwin-x64": "0.19.12",
+        "@esbuild/freebsd-arm64": "0.19.12",
+        "@esbuild/freebsd-x64": "0.19.12",
+        "@esbuild/linux-arm": "0.19.12",
+        "@esbuild/linux-arm64": "0.19.12",
+        "@esbuild/linux-ia32": "0.19.12",
+        "@esbuild/linux-loong64": "0.19.12",
+        "@esbuild/linux-mips64el": "0.19.12",
+        "@esbuild/linux-ppc64": "0.19.12",
+        "@esbuild/linux-riscv64": "0.19.12",
+        "@esbuild/linux-s390x": "0.19.12",
+        "@esbuild/linux-x64": "0.19.12",
+        "@esbuild/netbsd-x64": "0.19.12",
+        "@esbuild/openbsd-x64": "0.19.12",
+        "@esbuild/sunos-x64": "0.19.12",
+        "@esbuild/win32-arm64": "0.19.12",
+        "@esbuild/win32-ia32": "0.19.12",
+        "@esbuild/win32-x64": "0.19.12"
+      }
+    },
     "node_modules/escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -1642,7 +2037,6 @@
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
       "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "dev": true,
       "hasInstallScript": true,
       "optional": true,
       "os": [
@@ -1678,6 +2072,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-tsconfig": {
+      "version": "4.7.2",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz",
+      "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==",
+      "dependencies": {
+        "resolve-pkg-maps": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+      }
+    },
     "node_modules/glob": {
       "version": "7.2.3",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -2483,6 +2888,14 @@
         "node": ">=4"
       }
     },
+    "node_modules/resolve-pkg-maps": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+      "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+      "funding": {
+        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+      }
+    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -2894,6 +3307,24 @@
         "strip-json-comments": "^2.0.0"
       }
     },
+    "node_modules/tsx": {
+      "version": "4.7.1",
+      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.1.tgz",
+      "integrity": "sha512-8d6VuibXHtlN5E3zFkgY8u4DX7Y3Z27zvvPKVmLon/D4AjuKzarkUBTLDBgj9iTQ0hg5xM7c/mYiRVM+HETf0g==",
+      "dependencies": {
+        "esbuild": "~0.19.10",
+        "get-tsconfig": "^4.7.2"
+      },
+      "bin": {
+        "tsx": "dist/cli.mjs"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      }
+    },
     "node_modules/type-check": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
index 6dd984c5e..8104c1fc7 100644
--- a/desci-media-isolated/package.json
+++ b/desci-media-isolated/package.json
@@ -3,9 +3,10 @@
   "version": "1.0.0",
   "description": "",
   "main": "index.js",
+  "type": "module",
   "scripts": {
     "start": "node dist/index.js",
-    "dev": "ts-node-dev --respawn --transpile-only src/index.ts",
+    "dev": "tsx watch src/index.ts",
     "build": "tsc"
   },
   "keywords": [],
@@ -13,9 +14,11 @@
   "license": "ISC",
   "dependencies": {
     "axios": "^1.6.7",
+    "dotenv": "^16.4.5",
     "express": "^4.18.2",
     "filepreview_ts": "^1.0.0",
-    "helmet": "^7.1.0"
+    "helmet": "^7.1.0",
+    "tsx": "^4.7.1"
   },
   "devDependencies": {
     "@types/express": "^4.17.21",
diff --git a/desci-media-isolated/tsconfig.json b/desci-media-isolated/tsconfig.json
index 1fbd165a5..b663cb032 100644
--- a/desci-media-isolated/tsconfig.json
+++ b/desci-media-isolated/tsconfig.json
@@ -12,7 +12,7 @@
 
     /* Language and Environment */
     "outDir": "./dist",
-    "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+    "target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
@@ -26,7 +26,7 @@
     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 
     /* Modules */
-    "module": "commonjs" /* Specify what module code is generated. */,
+    "module": "NodeNext" /* Specify what module code is generated. */,
     // "rootDir": "./",                                  /* Specify the root folder within your source files. */
     // "moduleResolution": "node10",                     /* Specify how TypeScript looks up a file from a given module specifier. */
     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
@@ -53,8 +53,8 @@
     // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
     // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
     // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
-    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
-    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
+    // "sourceMap": true /* Create source map files for emitted JavaScript files. */,
+    "inlineSourceMap": true /* Include sourcemap files inside the emitted JavaScript. */,
     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
     // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
     // "removeComments": true,                           /* Disable emitting comments. */
@@ -64,7 +64,7 @@
     // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
     // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
     // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
-    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
+    "inlineSources": true /* Include source code in the sourcemaps inside the emitted JavaScript. */,
     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
     // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
@@ -76,9 +76,9 @@
 
     /* Interop Constraints */
     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
-    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+    "verbatimModuleSyntax": true /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */,
     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
-    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
+    // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
     "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
 
@@ -106,5 +106,9 @@
     /* Completeness */
     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
     "skipLibCheck": true /* Skip type checking all .d.ts files. */
+  },
+  "ts-node": {
+    // Tell ts-node CLI to install the --loader automatically, explained below
+    "esm": true
   }
 }

From 8c2b8a7fd89e75935c211612a1a3ae6ab323049b Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Tue, 20 Feb 2024 23:43:41 +0700
Subject: [PATCH 06/42] get thumbnail generation working

---
 desci-media-isolated/src/config/index.ts      |  4 ++--
 .../src/controllers/thumbnails/create.ts      | 20 ++++++++++--------
 desci-media-isolated/src/index.ts             |  6 ++++--
 .../src/middleware/errorHandler.ts            |  6 +++---
 desci-media-isolated/src/routes/index.ts      |  2 +-
 desci-media-isolated/src/routes/v1/index.ts   |  2 +-
 .../src/routes/v1/thumbnails.ts               |  2 +-
 desci-media-isolated/src/services/ipfs.ts     |  4 ++--
 .../src/services/thumbnails.ts                | 21 ++++++++++++-------
 9 files changed, 39 insertions(+), 28 deletions(-)

diff --git a/desci-media-isolated/src/config/index.ts b/desci-media-isolated/src/config/index.ts
index 94ff1ff43..46ac0834c 100644
--- a/desci-media-isolated/src/config/index.ts
+++ b/desci-media-isolated/src/config/index.ts
@@ -1,4 +1,4 @@
 export const IPFS_GATEWAY = process.env.IPFS_GATEWAY;
 export const TEMP_DIR = '.temp';
-export const THUMBNAIL_FILES_DIR = `${TEMP_DIR}/files`;
-export const THUMBNAIL_OUTPUT_DIR = `${TEMP_DIR}/thumbnails`;
+export const THUMBNAIL_FILES_DIR = `/files`;
+export const THUMBNAIL_OUTPUT_DIR = `/thumbnails`;
diff --git a/desci-media-isolated/src/controllers/thumbnails/create.ts b/desci-media-isolated/src/controllers/thumbnails/create.ts
index ab9ecf2f2..0e23350b1 100644
--- a/desci-media-isolated/src/controllers/thumbnails/create.ts
+++ b/desci-media-isolated/src/controllers/thumbnails/create.ts
@@ -1,15 +1,18 @@
-import { Request, Response } from 'express';
-import { ThumbnailsService } from '../../services/thumbnails';
+import type { Request, Response } from 'express';
+import { ThumbnailsService } from '../../services/thumbnails.js';
 import path from 'path';
+import { fileURLToPath } from 'url';
 import fs from 'fs';
-import { TEMP_DIR } from '../../config';
-import { BadRequestError, NotFoundError } from '../../utils/customErrors';
+import { TEMP_DIR, THUMBNAIL_OUTPUT_DIR } from '../../config/index.js';
+import { BadRequestError, NotFoundError } from '../../utils/customErrors.js';
 
 export type GenerateThumbnailRequestBody = {
   cid: string;
   fileName: string;
 };
 
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
 const BASE_TEMP_DIR = path.resolve(__dirname, '../../..', TEMP_DIR);
 
 export const generateThumbnail = async (req: Request<any, any, GenerateThumbnailRequestBody>, res: Response) => {
@@ -18,8 +21,8 @@ export const generateThumbnail = async (req: Request<any, any, GenerateThumbnail
   if (!fileName) throw new BadRequestError('Missing fileName in request body');
 
   try {
-    const thumbnailPath = await ThumbnailsService.generateThumbnail(cid);
-    const fullThumbnailPath = path.join(BASE_TEMP_DIR, thumbnailPath);
+    const thumbnailPath = await ThumbnailsService.generateThumbnail(cid, fileName);
+    const fullThumbnailPath = path.join(BASE_TEMP_DIR, THUMBNAIL_OUTPUT_DIR, thumbnailPath);
 
     // Check if the file exists before attempting to stream it
     fs.access(fullThumbnailPath, fs.constants.F_OK, (err) => {
@@ -33,9 +36,8 @@ export const generateThumbnail = async (req: Request<any, any, GenerateThumbnail
       readStream.pipe(res);
     });
 
-    // Send the thumbnail as a response
-    res.status(200);
+    return res.status(200);
   } catch (err: any) {
-    res.status(500).json({ message: err.message });
+    return res.status(500).json({ message: err.message });
   }
 };
diff --git a/desci-media-isolated/src/index.ts b/desci-media-isolated/src/index.ts
index c68b0d7e1..abbed5727 100644
--- a/desci-media-isolated/src/index.ts
+++ b/desci-media-isolated/src/index.ts
@@ -1,9 +1,11 @@
+import 'dotenv/config';
 import express from 'express';
 import helmet from 'helmet';
-import { errorHandler } from './middleware/errorHandler';
-import routes from './routes';
+import { errorHandler } from './middleware/errorHandler.js';
+import routes from './routes/index.js';
 
 const app = express();
+console.log('process.env.PORT:', process.env.PORT);
 const PORT = process.env.PORT || 7771;
 
 app.use(helmet());
diff --git a/desci-media-isolated/src/middleware/errorHandler.ts b/desci-media-isolated/src/middleware/errorHandler.ts
index 91135f88a..e755da4f7 100644
--- a/desci-media-isolated/src/middleware/errorHandler.ts
+++ b/desci-media-isolated/src/middleware/errorHandler.ts
@@ -1,5 +1,5 @@
-import { Request, Response, NextFunction } from 'express';
-import { BaseError } from '../utils/customErrors';
+import type { Request, Response, NextFunction } from 'express';
+import { BaseError } from '../utils/customErrors.js';
 
 export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
   console.error(err.stack);
@@ -9,7 +9,7 @@ export const errorHandler = (err: Error, req: Request, res: Response, next: Next
     statusCode = err.statusCode;
   }
 
-  res.status(statusCode).json({
+  return res.status(statusCode).json({
     message: err.message || 'Something went wrong',
     stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
   });
diff --git a/desci-media-isolated/src/routes/index.ts b/desci-media-isolated/src/routes/index.ts
index d7c2b6204..bafe8f18a 100644
--- a/desci-media-isolated/src/routes/index.ts
+++ b/desci-media-isolated/src/routes/index.ts
@@ -1,5 +1,5 @@
 import { Router } from 'express';
-import v1 from './v1/index';
+import v1 from './v1/index.js';
 
 const router = Router();
 
diff --git a/desci-media-isolated/src/routes/v1/index.ts b/desci-media-isolated/src/routes/v1/index.ts
index e631244d4..f07481a44 100644
--- a/desci-media-isolated/src/routes/v1/index.ts
+++ b/desci-media-isolated/src/routes/v1/index.ts
@@ -1,5 +1,5 @@
 import { Router } from 'express';
-import thumbnails from './thumbnails';
+import thumbnails from './thumbnails.js';
 
 const router = Router();
 
diff --git a/desci-media-isolated/src/routes/v1/thumbnails.ts b/desci-media-isolated/src/routes/v1/thumbnails.ts
index df4a339d1..0ca747e60 100644
--- a/desci-media-isolated/src/routes/v1/thumbnails.ts
+++ b/desci-media-isolated/src/routes/v1/thumbnails.ts
@@ -1,5 +1,5 @@
 import { Router } from 'express';
-import { generateThumbnail } from '../../controllers/thumbnails/create';
+import { generateThumbnail } from '../../controllers/thumbnails/create.js';
 
 const router = Router();
 
diff --git a/desci-media-isolated/src/services/ipfs.ts b/desci-media-isolated/src/services/ipfs.ts
index 7e6abd92f..cd4a96c11 100644
--- a/desci-media-isolated/src/services/ipfs.ts
+++ b/desci-media-isolated/src/services/ipfs.ts
@@ -1,8 +1,8 @@
 import axios from 'axios';
 import fs from 'fs';
 import { pipeline } from 'stream/promises';
-import { IPFS_GATEWAY } from '../config';
-import { IpfsConfigurationError } from '../utils/customErrors';
+import { IPFS_GATEWAY } from '../config/index.js';
+import { IpfsConfigurationError } from '../utils/customErrors.js';
 
 export class IpfsService {
   static async saveFile(cid: string, outputPath: string) {
diff --git a/desci-media-isolated/src/services/thumbnails.ts b/desci-media-isolated/src/services/thumbnails.ts
index 95e49770c..b3084fbd3 100644
--- a/desci-media-isolated/src/services/thumbnails.ts
+++ b/desci-media-isolated/src/services/thumbnails.ts
@@ -1,25 +1,32 @@
 import { generateAsync } from 'filepreview_ts';
-import { TEMP_DIR, THUMBNAIL_FILES_DIR, THUMBNAIL_OUTPUT_DIR } from '../config';
-import { IpfsService } from './ipfs';
-import { UnhandledError } from '../utils/customErrors';
+import { TEMP_DIR, THUMBNAIL_FILES_DIR, THUMBNAIL_OUTPUT_DIR } from '../config/index.js';
+import { IpfsService } from './ipfs.js';
+import { BadRequestError, UnhandledError } from '../utils/customErrors.js';
 import path from 'path';
 import fs from 'fs';
+import { fileURLToPath } from 'url';
 
 const THUMBNAIL_DIMENSIONS = {
   width: 220,
   height: 300,
 };
-const BASE_TEMP_DIR = path.resolve(__dirname, '..', TEMP_DIR);
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const BASE_TEMP_DIR = path.resolve(__dirname, '../..', TEMP_DIR);
 
 export class ThumbnailsService {
-  static async generateThumbnail(cid: string) {
-    const tempFilePath = path.join(BASE_TEMP_DIR, THUMBNAIL_FILES_DIR, `${cid}`);
+  static async generateThumbnail(cid: string, fileName: string) {
+    const extension = '.' + fileName.split('.').pop();
+    if (!extension) throw new BadRequestError('Invalid file name, requires extension');
+    const tempFilePath = path.join(BASE_TEMP_DIR, THUMBNAIL_FILES_DIR, `${cid + extension}`);
     const thumbnailPath = this.getThumbnailPath(cid);
     const exportPath = path.join(BASE_TEMP_DIR, THUMBNAIL_OUTPUT_DIR, thumbnailPath);
 
     await IpfsService.saveFile(cid, tempFilePath);
     try {
       await generateAsync(tempFilePath, exportPath, THUMBNAIL_DIMENSIONS);
+      console.log('Thumbnail generated successfully:', exportPath);
       return thumbnailPath;
     } catch (e) {
       console.error(e);
@@ -41,6 +48,6 @@ export class ThumbnailsService {
   }
 
   static getThumbnailPath(cid: string) {
-    return `${THUMBNAIL_DIMENSIONS.width}x${THUMBNAIL_DIMENSIONS.height}_${cid}`;
+    return `${THUMBNAIL_DIMENSIONS.width}x${THUMBNAIL_DIMENSIONS.height}_${cid}.jpg`;
   }
 }

From c5548361e05fc4ab7757ee2f82757f1330e7b8ad Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Wed, 21 Feb 2024 02:43:43 +0700
Subject: [PATCH 07/42] fix thumbnail gen for pdfs

---
 desci-media-isolated/Dockerfile                 | 5 ++++-
 desci-media-isolated/src/services/thumbnails.ts | 8 +++++---
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 1704d2786..80102bf10 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -5,9 +5,12 @@ FROM docker.io/node:20.9.0 as base
 # RUN apk update && apk add --no-cache dumb-init
 # RUN apk add --no-cache unoconv ffmpeg imagemagick curl
 
-RUN apt-get update && apt-get install -y dumb-init unoconv ffmpeg imagemagick curl && \
+RUN apt-get update && apt-get install -y dumb-init ghostscript unoconv ffmpeg imagemagick curl && \
     rm -rf /var/lib/apt/lists/*
 
+# Modify ImageMagick policy to allow PDF processing
+RUN sed -i '/<policy domain="coder" rights="none" pattern="PDF" \/>/c\<policy domain="Undefined" rights="read|write" pattern="PDF" \/>' /etc/ImageMagick-6/policy.xml
+
 # NPM Permission Fix
 RUN mkdir -p /.npm
 RUN chown -R 1001:0 /.npm
diff --git a/desci-media-isolated/src/services/thumbnails.ts b/desci-media-isolated/src/services/thumbnails.ts
index b3084fbd3..b9adbc352 100644
--- a/desci-media-isolated/src/services/thumbnails.ts
+++ b/desci-media-isolated/src/services/thumbnails.ts
@@ -7,8 +7,9 @@ import fs from 'fs';
 import { fileURLToPath } from 'url';
 
 const THUMBNAIL_DIMENSIONS = {
-  width: 220,
+  // width: 220,
   height: 300,
+  keepAspect: true,
 };
 
 const __filename = fileURLToPath(import.meta.url);
@@ -25,7 +26,7 @@ export class ThumbnailsService {
 
     await IpfsService.saveFile(cid, tempFilePath);
     try {
-      await generateAsync(tempFilePath, exportPath, THUMBNAIL_DIMENSIONS);
+      await generateAsync(tempFilePath, exportPath, { ...THUMBNAIL_DIMENSIONS });
       console.log('Thumbnail generated successfully:', exportPath);
       return thumbnailPath;
     } catch (e) {
@@ -48,6 +49,7 @@ export class ThumbnailsService {
   }
 
   static getThumbnailPath(cid: string) {
-    return `${THUMBNAIL_DIMENSIONS.width}x${THUMBNAIL_DIMENSIONS.height}_${cid}.jpg`;
+    return `h-${THUMBNAIL_DIMENSIONS.height}px_${cid}.jpg`;
+    // return `${THUMBNAIL_DIMENSIONS.width}x${THUMBNAIL_DIMENSIONS.height}_${cid}.jpg`;
   }
 }

From ad5b88d3f358f224b6776c7daeb3f7326a8c952e Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Wed, 21 Feb 2024 03:56:53 +0700
Subject: [PATCH 08/42] add media-isolated service to docker-compose-dev,
 isolate communcations

---
 .vscode/launch.json             |  2 +-
 desci-media-isolated/Dockerfile |  4 +--
 docker-compose.dev.yml          | 54 +++++++++++++++++++--------------
 3 files changed, 35 insertions(+), 25 deletions(-)

diff --git a/.vscode/launch.json b/.vscode/launch.json
index e0d58f439..99647a2ac 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -51,7 +51,7 @@
       "restart": true,
       "localRoot": "${workspaceFolder}/desci-media-isolated",
       "remoteRoot": "/usr/src/app",
-      "port": 9277,
+      "port": 9777,
       "skipFiles": [
         // Node.js internal core modules
         "<node_internals>/**",
diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 80102bf10..9a07b49a8 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -29,9 +29,9 @@ RUN --mount=type=cache,target=/usr/src/app/.npm \
 COPY . .
 
 # Expose debugger port
-EXPOSE 9277
+EXPOSE 9777
 
-CMD ["dumb-init", "npx", "tsx","watch", "--inspect=0.0.0.0:9277", "src/index.ts"]
+CMD ["dumb-init", "npx", "tsx","watch", "--inspect=0.0.0.0:9777", "src/index.ts"]
 
 FROM base as production
 # Cache mounts for faster builds, prod env for better express perf
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index f30ce58c7..da2433ba8 100755
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -18,8 +18,8 @@ services:
     volumes:
       - ./local-data/database/boilerplate:/var/lib/postgresql/data/
     environment:
-      POSTGRES_INITDB_ARGS: '--encoding=UTF-8 --lc-collate=C --lc-ctype=C'
-      POSTGRES_PASSWORD: 'white'
+      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
+      POSTGRES_PASSWORD: "white"
 
   desci_blockchain_ganache:
     container_name: "desci_blockchain_ganache"
@@ -41,11 +41,9 @@ services:
       - ./desci-contracts/.openzeppelin:/app/.openzeppelin
     depends_on:
       graph_node:
-        condition:
-          service_started
+        condition: service_started
       db_postgres:
-        condition:
-          service_healthy
+        condition: service_healthy
 
   desci_nodes_backend:
     container_name: "desci_nodes_backend"
@@ -61,23 +59,22 @@ services:
       - host.docker.internal:host-gateway
     depends_on:
       db_postgres:
-        condition:
-          service_healthy
+        condition: service_healthy
       desci_blockchain_ganache:
-        condition:
-          service_healthy
+        condition: service_healthy
       graph_node:
-        condition:
-          service_started
+        condition: service_started
       redis:
-        condition:
-          service_started
+        condition: service_started
       # - nodes_media # UNCOMMENT FOR LOCAL DEV OF nodes-media
     links:
       - db_postgres
     volumes:
       - ./local-data/yarn_cache:/root/.yarn
     # mem_limit: 2g #uncomment to test large data with limited memory
+    networks:
+      - default
+      - isolated
 
   block_explorer_dev:
     image: sinaiman/expedition-dev:latest
@@ -104,11 +101,9 @@ services:
       - host.docker.internal:host-gateway
     depends_on:
       ipfs:
-        condition:
-          service_healthy
+        condition: service_healthy
       db_postgres:
-        condition:
-          service_healthy
+        condition: service_healthy
     environment:
       # https://github.com/graphprotocol/graph-node/blob/master/docs/environment-variables.md
       postgres_host: db_postgres
@@ -178,11 +173,22 @@ services:
       - ./.ceramicDev.config.json:/root/.ceramic/daemon.config.json
     depends_on:
       ipfs:
-        condition:
-          service_healthy
+        condition: service_healthy
       db_postgres:
-        condition:
-          service_healthy
+        condition: service_healthy
+
+  desci-media-isolated:
+    build:
+      context: ./desci-media-isolated
+      target: dev
+    container_name: "media_isolated"
+    volumes:
+      - ./desci-media-isolated:/usr/src/app
+    ports:
+      - "9777:9777" # debugger
+      # - "7771:7771" # Uncomment if you want to test the media server from the host machine
+    networks:
+      - isolated
 
   # desci_nodes_backend_test:
   #   container_name: 'be_test_boilerplate'
@@ -199,3 +205,7 @@ services:
   #   volumes:
   #     - .:/app/
   #     - /app/node_modules
+
+networks:
+  isolated:
+    driver: bridge

From 0b6e6dfd84687e61e022517a73c7f050ebc76f4a Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Wed, 21 Feb 2024 04:15:26 +0700
Subject: [PATCH 09/42] update example envs

---
 desci-media-isolated/.example.env | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/desci-media-isolated/.example.env b/desci-media-isolated/.example.env
index 621fc17e8..f2fe1d153 100644
--- a/desci-media-isolated/.example.env
+++ b/desci-media-isolated/.example.env
@@ -1 +1,5 @@
+NODE_ENV=development
+
+PORT=7771
+
 IPFS_GATEWAY=https://ipfs.desci.com/ipfs
\ No newline at end of file

From 2ba70b4d0f73ca77d5a455a1a2d4db911aa489b8 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Wed, 21 Feb 2024 04:33:15 +0700
Subject: [PATCH 10/42] add k8s deployment configs and network policies

---
 .../kubernetes/deployment_dev.yaml            | 36 +++++++++++++++++++
 .../kubernetes/network_policies.yaml          | 28 +++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 desci-media-isolated/kubernetes/deployment_dev.yaml
 create mode 100644 desci-media-isolated/kubernetes/network_policies.yaml

diff --git a/desci-media-isolated/kubernetes/deployment_dev.yaml b/desci-media-isolated/kubernetes/deployment_dev.yaml
new file mode 100644
index 000000000..7887ea5b0
--- /dev/null
+++ b/desci-media-isolated/kubernetes/deployment_dev.yaml
@@ -0,0 +1,36 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nodes-isolated-media-server-dev
+  labels:
+    App: NodesIsolatedMediaServerDev
+spec:
+  replicas: 1
+  revisionHistoryLimit: 2
+  selector:
+    matchLabels:
+      App: NodesIsolatedMediaServerDev
+  strategy:
+    rollingUpdate:
+      maxSurge: 25%
+      maxUnavailable: 25%
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        App: NodesIsolatedMediaServerDev
+    spec:
+      containers:
+        - image: {CHANGE}.dkr.ecr.us-east-2.amazonaws.com/nodes-isolated-media-server-dev:production
+          name: nodes-isolated-media-server-dev
+          ports:
+            - containerPort: 7771
+              name: media_isolated
+          resources:
+            limits:
+              cpu: '0.5'
+              memory: 2Gi
+            requests:
+              cpu: 250m
+              memory: 1Gi
+      serviceAccountName: 'default'
diff --git a/desci-media-isolated/kubernetes/network_policies.yaml b/desci-media-isolated/kubernetes/network_policies.yaml
new file mode 100644
index 000000000..8b2819127
--- /dev/null
+++ b/desci-media-isolated/kubernetes/network_policies.yaml
@@ -0,0 +1,28 @@
+kind: NetworkPolicy
+apiVersion: networking.k8s.io/v1
+metadata:
+  name: allow-backend-pod-to-isolated-media-server-comms
+spec:
+  podSelector:
+    matchLabels:
+      App: NodesIsolatedMediaServerDev
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              allow-from: 'DesciServerDev'
+  policyTypes:
+    - Ingress
+
+
+kind: NetworkPolicy
+apiVersion: networking.k8s.io/v1
+metadata:
+  name: deny-egress-from-isolated-media-server
+spec:
+  podSelector:
+    matchLabels:
+      App: NodesIsolatedMediaServerDev
+  egress: []
+  policyTypes:
+    - Egress

From 46abfb6ed90740acab17633204e328bef5560058 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 02:44:56 +0700
Subject: [PATCH 11/42] fix attachUser middleware

---
 desci-server/src/middleware/attachUser.ts | 38 +++++++++++++++++++++++
 desci-server/src/middleware/ensureUser.ts | 24 ++++++++++++--
 2 files changed, 59 insertions(+), 3 deletions(-)
 create mode 100755 desci-server/src/middleware/attachUser.ts

diff --git a/desci-server/src/middleware/attachUser.ts b/desci-server/src/middleware/attachUser.ts
new file mode 100755
index 000000000..c9fb62a9d
--- /dev/null
+++ b/desci-server/src/middleware/attachUser.ts
@@ -0,0 +1,38 @@
+import { User } from '@prisma/client';
+import { Request, Response, NextFunction } from 'express';
+
+import {
+  AuthMethods,
+  extractApiKey,
+  extractAuthToken,
+  extractUserFromApiKey,
+  extractUserFromToken,
+} from './permissions.js';
+
+/**
+ * Attaches the user to the request (req.user), the difference between this middleware and ensureUser is that this is optional
+ * and won't reject with a 401 if not logged in.
+ */
+export const attachUser = async (req: Request, res: Response, next: NextFunction) => {
+  const authHeader = req.headers['authorization'];
+  const apiKeyHeader = req.headers['api-key'];
+
+  const token = authHeader ? await extractAuthToken(req) : undefined;
+  const apiKey = apiKeyHeader ? await await extractApiKey(req) : undefined;
+  const authTokenRetrieval = authHeader ? await extractUserFromToken(token) : undefined;
+  const apiKeyRetrieval = apiKeyHeader ? await extractUserFromApiKey(apiKey, req.ip) : undefined;
+
+  const retrievedUser = authTokenRetrieval || apiKeyRetrieval;
+
+  if (retrievedUser) {
+    (req as any).user = retrievedUser;
+    (req as any).authMethod = authTokenRetrieval ? AuthMethods.AUTH_TOKEN : AuthMethods.API_KEY;
+  }
+  next();
+};
+
+export const retrieveUser = async (req: Request): Promise<User | null> => {
+  const token = await extractAuthToken(req);
+  const retrievedUser = await extractUserFromToken(token);
+  return retrievedUser;
+};
diff --git a/desci-server/src/middleware/ensureUser.ts b/desci-server/src/middleware/ensureUser.ts
index 77ebde032..6da4f3fcb 100755
--- a/desci-server/src/middleware/ensureUser.ts
+++ b/desci-server/src/middleware/ensureUser.ts
@@ -1,7 +1,13 @@
 import { User } from '@prisma/client';
 import { Request, Response, NextFunction } from 'express';
 
-import { extractAuthToken, extractUserFromToken } from './permissions.js';
+import {
+  AuthMethods,
+  extractApiKey,
+  extractAuthToken,
+  extractUserFromApiKey,
+  extractUserFromToken,
+} from './permissions.js';
 
 // export const ensureUser = async (req: Request, res: Response, next: NextFunction) => {
 //   const retrievedUser = await retrieveUser(req);
@@ -18,8 +24,20 @@ import { extractAuthToken, extractUserFromToken } from './permissions.js';
  * and won't reject with a 401 if not logged in.
  */
 export const attachUser = async (req: Request, res: Response, next: NextFunction) => {
-  const retrievedUser = await retrieveUser(req);
-  (req as any).user = retrievedUser;
+  const authHeader = req.headers['authorization'];
+  const apiKeyHeader = req.headers['api-key'];
+
+  const token = authHeader ? await extractAuthToken(req) : undefined;
+  const apiKey = apiKeyHeader ? await await extractApiKey(req) : undefined;
+  const authTokenRetrieval = authHeader ? await extractUserFromToken(token) : undefined;
+  const apiKeyRetrieval = apiKeyHeader ? await extractUserFromApiKey(apiKey, req.ip) : undefined;
+
+  const retrievedUser = authTokenRetrieval || apiKeyRetrieval;
+
+  if (retrievedUser) {
+    (req as any).user = retrievedUser;
+    (req as any).authMethod = authTokenRetrieval ? AuthMethods.AUTH_TOKEN : AuthMethods.API_KEY;
+  }
   next();
 };
 

From d20f3c7cf6325e145ba4d04065978e4e6920b4cb Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 03:08:57 +0700
Subject: [PATCH 12/42] add thumbnails table/migration

---
 .../migration.sql                                 | 15 +++++++++++++++
 desci-server/prisma/schema.prisma                 | 11 +++++++++++
 2 files changed, 26 insertions(+)
 create mode 100644 desci-server/prisma/migrations/20240221145731_add_thumbnails_table/migration.sql

diff --git a/desci-server/prisma/migrations/20240221145731_add_thumbnails_table/migration.sql b/desci-server/prisma/migrations/20240221145731_add_thumbnails_table/migration.sql
new file mode 100644
index 000000000..ba2e3be50
--- /dev/null
+++ b/desci-server/prisma/migrations/20240221145731_add_thumbnails_table/migration.sql
@@ -0,0 +1,15 @@
+-- CreateTable
+CREATE TABLE "NodeThumbnails" (
+    "id" SERIAL NOT NULL,
+    "componentCid" TEXT NOT NULL,
+    "nodeUuid" TEXT NOT NULL,
+    "thumbnails" JSONB NOT NULL,
+
+    CONSTRAINT "NodeThumbnails_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "NodeThumbnails_nodeUuid_componentCid_key" ON "NodeThumbnails"("nodeUuid", "componentCid");
+
+-- AddForeignKey
+ALTER TABLE "NodeThumbnails" ADD CONSTRAINT "NodeThumbnails_nodeUuid_fkey" FOREIGN KEY ("nodeUuid") REFERENCES "Node"("uuid") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/desci-server/prisma/schema.prisma b/desci-server/prisma/schema.prisma
index 0c3c67e5b..2a882b36c 100755
--- a/desci-server/prisma/schema.prisma
+++ b/desci-server/prisma/schema.prisma
@@ -39,6 +39,7 @@ model Node {
   DraftNodeTree       DraftNodeTree[]
   ceramicStream       String?
   NodeAttestation     NodeAttestation[]
+  NodeThumbnails      NodeThumbnails[]
 
   @@index([ownerId])
   @@index([uuid])
@@ -457,6 +458,16 @@ model NodeCover {
   @@unique([nodeUuid, version])
 }
 
+model NodeThumbnails {
+  id           Int    @id @default(autoincrement())
+  componentCid String
+  nodeUuid     String
+  thumbnails   Json
+  node         Node   @relation(fields: [nodeUuid], references: [uuid])
+
+  @@unique([nodeUuid, componentCid])
+}
+
 model FriendReferral {
   id                     Int                  @id @default(autoincrement())
   uuid                   String               @unique @default(uuid())

From 351e9ebe84718634d5f4ad45db497a966d05c02a Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 03:09:55 +0700
Subject: [PATCH 13/42] add desci-server routes/services/controllers for
 communicating with isolated media server

---
 .../src/controllers/nodes/thumbnails.ts       |  59 ++++++++
 desci-server/src/middleware/ensureUser.ts     |  48 -------
 .../src/middleware/ensureUserIfPresent.ts     |   2 +-
 desci-server/src/middleware/index.ts          |   2 +-
 desci-server/src/middleware/permissions.ts    |  11 +-
 desci-server/src/routes/v1/data.ts            |   2 +-
 desci-server/src/routes/v1/nodes.ts           |   3 +
 desci-server/src/services/Thumbnails.ts       | 131 ++++++++++++++++++
 desci-server/src/services/data/processing.ts  |  10 ++
 desci-server/src/services/ipfs.ts             |   8 ++
 desci-server/src/services/repoService.ts      |   2 +
 11 files changed, 223 insertions(+), 55 deletions(-)
 create mode 100644 desci-server/src/controllers/nodes/thumbnails.ts
 delete mode 100755 desci-server/src/middleware/ensureUser.ts
 create mode 100644 desci-server/src/services/Thumbnails.ts

diff --git a/desci-server/src/controllers/nodes/thumbnails.ts b/desci-server/src/controllers/nodes/thumbnails.ts
new file mode 100644
index 000000000..6ae6384b8
--- /dev/null
+++ b/desci-server/src/controllers/nodes/thumbnails.ts
@@ -0,0 +1,59 @@
+import type { Request, Response, NextFunction } from 'express';
+
+import { prisma } from '../../client.js';
+import { NodeUuid } from '../../internal.js';
+import { type ThumbnailMap, thumbnailsService } from '../../services/Thumbnails.js';
+import { ensureUuidEndsWithDot } from '../../utils.js';
+
+type ThumbnailsReqBodyParams = {
+  uuid: string;
+  manifestCid?: string;
+};
+
+type ThumbnailsResponse = {
+  ok: true;
+  thumbnailMap: ThumbnailMap;
+};
+
+type ThumbnailsErrorResponse = {
+  ok: false;
+  error: string;
+  status?: number;
+};
+
+/**
+ * Generates and retrieves preview thumbnails of pinned components for a node.
+ * @param req.params.uuid required for both drafts and published nodes
+ * @param req.params.manifestCid only required for published nodes (to get a specific version), without the latest draft will be used.
+ * @return {ThumbnailMap} ThumbnailMap = Record<ComponentCidString, Record<HeightPx, ThumbnailCidString>>
+ */
+export const thumbnails = async (
+  req: Request<any, any, ThumbnailsReqBodyParams>,
+  res: Response<ThumbnailsResponse | ThumbnailsErrorResponse>,
+) => {
+  const user = (req as any).user;
+  const { uuid, manifestCid } = req.params;
+
+  if (!uuid) return res.status(400).json({ ok: false, error: 'UUID is required.' });
+
+  if (!user && !manifestCid) {
+    // If there's no manifestCid passed in, we're looking at a draft node, and it requires auth.
+    return res.status(401).json({ ok: false, error: 'Unauthorized' });
+  }
+
+  if (user && !manifestCid) {
+    // Check if user owns node, if requesting draft thumbnails
+    const node = await prisma.node.findFirst({
+      where: {
+        ownerId: user.id,
+        uuid: ensureUuidEndsWithDot(uuid),
+      },
+    });
+
+    if (!node) return res.status(401).json({ ok: false, error: 'Unauthorized' });
+  }
+
+  const thumbnailMap = await thumbnailsService.getThumbnailsForNode({ uuid: uuid as NodeUuid, manifestCid });
+
+  return res.status(200).json({ ok: true, thumbnailMap });
+};
diff --git a/desci-server/src/middleware/ensureUser.ts b/desci-server/src/middleware/ensureUser.ts
deleted file mode 100755
index 6da4f3fcb..000000000
--- a/desci-server/src/middleware/ensureUser.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { User } from '@prisma/client';
-import { Request, Response, NextFunction } from 'express';
-
-import {
-  AuthMethods,
-  extractApiKey,
-  extractAuthToken,
-  extractUserFromApiKey,
-  extractUserFromToken,
-} from './permissions.js';
-
-// export const ensureUser = async (req: Request, res: Response, next: NextFunction) => {
-//   const retrievedUser = await retrieveUser(req);
-//   if (!retrievedUser) {
-//     res.status(401).send({ ok: false, message: 'Unauthorized' });
-//     return;
-//   }
-//   (req as any).user = retrievedUser;
-//   next();
-// };
-
-/**
- * Attaches the user to the request (req.user), the difference between this middleware and ensureUser is that this is optional
- * and won't reject with a 401 if not logged in.
- */
-export const attachUser = async (req: Request, res: Response, next: NextFunction) => {
-  const authHeader = req.headers['authorization'];
-  const apiKeyHeader = req.headers['api-key'];
-
-  const token = authHeader ? await extractAuthToken(req) : undefined;
-  const apiKey = apiKeyHeader ? await await extractApiKey(req) : undefined;
-  const authTokenRetrieval = authHeader ? await extractUserFromToken(token) : undefined;
-  const apiKeyRetrieval = apiKeyHeader ? await extractUserFromApiKey(apiKey, req.ip) : undefined;
-
-  const retrievedUser = authTokenRetrieval || apiKeyRetrieval;
-
-  if (retrievedUser) {
-    (req as any).user = retrievedUser;
-    (req as any).authMethod = authTokenRetrieval ? AuthMethods.AUTH_TOKEN : AuthMethods.API_KEY;
-  }
-  next();
-};
-
-export const retrieveUser = async (req: Request): Promise<User | null> => {
-  const token = await extractAuthToken(req);
-  const retrievedUser = await extractUserFromToken(token);
-  return retrievedUser;
-};
diff --git a/desci-server/src/middleware/ensureUserIfPresent.ts b/desci-server/src/middleware/ensureUserIfPresent.ts
index c772924c3..d18198f76 100644
--- a/desci-server/src/middleware/ensureUserIfPresent.ts
+++ b/desci-server/src/middleware/ensureUserIfPresent.ts
@@ -1,6 +1,6 @@
 import { Request, Response, NextFunction } from 'express';
 
-import { retrieveUser } from './ensureUser.js';
+import { retrieveUser } from './attachUser.js';
 
 export const ensureUserIfPresent = async (req: Request, res: Response, next: NextFunction) => {
   const retrievedUser = await retrieveUser(req);
diff --git a/desci-server/src/middleware/index.ts b/desci-server/src/middleware/index.ts
index a2af0b418..4ca90616f 100644
--- a/desci-server/src/middleware/index.ts
+++ b/desci-server/src/middleware/index.ts
@@ -1,7 +1,7 @@
 export * from './authorisation.js';
 export * from './checkJwt.js';
 export * from './ensureAdmin.js';
-export * from './ensureUser.js';
+export * from './attachUser.js';
 export * from './ensureUserIfPresent.js';
 export * from './errorHandler.js';
 export * from './permissions.js';
diff --git a/desci-server/src/middleware/permissions.ts b/desci-server/src/middleware/permissions.ts
index 8f006516a..4ad0c7608 100644
--- a/desci-server/src/middleware/permissions.ts
+++ b/desci-server/src/middleware/permissions.ts
@@ -13,10 +13,13 @@ export enum AuthMethods {
 }
 
 export const ensureUser = async (req: ExpressRequest, res: Response, next: NextFunction) => {
-  const token = await extractAuthToken(req);
-  const apiKey = await extractApiKey(req);
-  const authTokenRetrieval = await extractUserFromToken(token);
-  const apiKeyRetrieval = await extractUserFromApiKey(apiKey, req.ip);
+  const authHeader = req.headers['authorization'];
+  const apiKeyHeader = req.headers['api-key'];
+
+  const token = authHeader ? await extractAuthToken(req) : undefined;
+  const apiKey = apiKeyHeader ? await await extractApiKey(req) : undefined;
+  const authTokenRetrieval = authHeader ? await extractUserFromToken(token) : undefined;
+  const apiKeyRetrieval = apiKeyHeader ? await extractUserFromApiKey(apiKey, req.ip) : undefined;
 
   const retrievedUser = authTokenRetrieval || apiKeyRetrieval;
 
diff --git a/desci-server/src/routes/v1/data.ts b/desci-server/src/routes/v1/data.ts
index 54ff342ee..c4057ed32 100644
--- a/desci-server/src/routes/v1/data.ts
+++ b/desci-server/src/routes/v1/data.ts
@@ -9,7 +9,7 @@ import { moveData } from '../../controllers/data/move.js';
 import { updateExternalCid } from '../../controllers/data/updateExternalCid.js';
 import { logger } from '../../logger.js';
 import { ensureNodeAccess, ensureWriteAccessCheck } from '../../middleware/authorisation.js';
-import { attachUser } from '../../middleware/ensureUser.js';
+import { attachUser } from '../../middleware/attachUser.js';
 import { ensureUser } from '../../middleware/permissions.js';
 import { isS3Configured, s3Client } from '../../services/s3.js';
 
diff --git a/desci-server/src/routes/v1/nodes.ts b/desci-server/src/routes/v1/nodes.ts
index ec420cfe0..555f8f3c7 100755
--- a/desci-server/src/routes/v1/nodes.ts
+++ b/desci-server/src/routes/v1/nodes.ts
@@ -22,7 +22,9 @@ import {
 } from '../../controllers/nodes/index.js';
 import { retrieveTitle } from '../../controllers/nodes/legacyManifestApi.js';
 import { prepublish } from '../../controllers/nodes/prepublish.js';
+import { thumbnails } from '../../controllers/nodes/thumbnails.js';
 import { versionDetails } from '../../controllers/nodes/versionDetails.js';
+import { attachUser } from '../../internal.js';
 import { ensureNodeAccess } from '../../middleware/authorisation.js';
 import { ensureUser } from '../../middleware/permissions.js';
 
@@ -48,6 +50,7 @@ router.get('/cover/:uuid', [], getCoverImage);
 router.get('/cover/:uuid/:version', [], getCoverImage);
 router.get('/documents/:uuid', [ensureUser, ensureNodeAccess], getNodeDocument);
 router.post('/documents/:uuid/actions', [ensureUser, ensureNodeAccess], dispatchDocumentChange);
+router.get('/thumbnails/:uuid/:manifestCid?', [attachUser], thumbnails);
 
 router.delete('/:uuid', [ensureUser], deleteNode);
 
diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
new file mode 100644
index 000000000..8668ce2f0
--- /dev/null
+++ b/desci-server/src/services/Thumbnails.ts
@@ -0,0 +1,131 @@
+import axios from 'axios';
+
+import { prisma } from '../client.js';
+import { logger as parentLogger } from '../logger.js';
+
+import { getManifestByCid, getManifestFromNode, pinNewFiles } from './data/processing.js';
+import { pinFile } from './ipfs.js';
+import { NodeUuid } from './manifestRepo.js';
+import repoService from './repoService.js';
+
+const logger = parentLogger.child({
+  module: 'Services::Thumbnails',
+});
+
+export type HeightPx = number;
+export type CidString = string;
+export type FileName = string;
+
+export type Thumbnail = Record<HeightPx, CidString>;
+export type ThumbnailMap = Record<CidString, Thumbnail>;
+
+type GenerateThumbnailResult = {
+  componentCid: CidString;
+  height: HeightPx;
+  thumbnailCid: CidString;
+} | null;
+
+// Hardcoded for the time being, we can modify this logic if the need more size variants
+const HEIGHT_PX = 300;
+
+export class ThumbnailsService {
+  async getThumbnailsForNode({
+    uuid,
+    manifestCid,
+    // heightPx,
+  }: {
+    uuid: NodeUuid;
+    manifestCid?: string;
+    // heightPx: HeightPx;
+  }): Promise<ThumbnailMap> {
+    debugger;
+    const manifest = manifestCid
+      ? await getManifestByCid(manifestCid)
+      : await repoService.getDraftManifest(uuid as NodeUuid);
+
+    const pinnedComponents = manifest?.components?.filter((c) => c.starred);
+
+    // Determined by the file extension (can't generate thumbnails for files without extensions)
+    const fileComponents = pinnedComponents?.filter((c) => c.payload.path.split('/').pop().includes('.'));
+    const fileComponentCids = fileComponents?.map((c) => c.payload.cid || c.payload.url);
+    // const fileComponentCidMap = fileComponents.reduce((map, comp) => {
+    //     const key = comp.payload.cid || comp.payload.url;
+    //     map[key] = comp;
+    //     return map;
+    //   }, {});
+
+    const thumbnailsToGenerate: Record<CidString, FileName> = fileComponents?.reduce((map, comp) => {
+      const fileName = comp.payload.path.split('/').pop();
+      const cid = comp.payload.cid || comp.payload.url;
+      map[cid] = fileName;
+      return map;
+    }, {});
+
+    const thumbnailMap: ThumbnailMap = {};
+    // Check which thumbnails already exist
+    const existingThumbnailsFound = await prisma.nodeThumbnails.findMany({
+      where: { componentCid: { in: fileComponentCids } },
+    });
+    // Check if the desired sizes exist, otherwise add to the generation array
+    for (const thumbnail of existingThumbnailsFound) {
+      const desiredSizeThumbnail = thumbnail.thumbnails[HEIGHT_PX];
+      if (desiredSizeThumbnail) {
+        // If exists, add it to the returned thumbnail map.
+        thumbnailMap[thumbnail.componentCid] = thumbnail.thumbnails;
+        // Remove it from the generation map
+        delete thumbnailsToGenerate[thumbnail.componentCid];
+      }
+    }
+
+    // Generate thumbnails for the ones that don't exist
+    const generatedThumbnails = await Promise.all(
+      Object.entries(thumbnailsToGenerate).map(([cid, fileName]) => this.generateThumbnail(cid, fileName, HEIGHT_PX)),
+    );
+
+    // Add the newly generated ones to the thumbnail map
+    generatedThumbnails.forEach((newThumb) => {
+      thumbnailMap[newThumb.componentCid] = { [HEIGHT_PX]: newThumb.thumbnailCid };
+    });
+
+    return thumbnailMap;
+  }
+
+  private async generateThumbnail(
+    cid: CidString,
+    componentFileName: string,
+    heightPx: HeightPx,
+  ): Promise<GenerateThumbnailResult> {
+    if (process.env.ISOLATED_MEDIA_SERVER_URL === undefined) {
+      logger.error('process.env.ISOLATED_MEDIA_SERVER_URL is not defined');
+      return null;
+    }
+    // Generate the thumbnail
+    const thumbnailStream = await axios.post(
+      `${process.env.ISOLATED_MEDIA_SERVER_URL}/v1/thumbnails?height${heightPx}`,
+      { cid: cid, fileName: componentFileName },
+      {
+        responseType: 'stream',
+      },
+    );
+
+    // Save it on IPFS
+    const pinned = await pinFile(thumbnailStream.data);
+    // Save it to the database
+    const existingThumbnail = await prisma.nodeThumbnails.findFirst({
+      where: { componentCid: cid },
+    });
+    (await existingThumbnail)
+      ? prisma.nodeThumbnails.update({
+          where: { componentCid: cid },
+          data: { thumbnails: { ...existingThumbnail.thumbnails, [heightPx]: pinned.cid } },
+        })
+      : prisma.nodeThumbnails.create({ data: { componentCid: cid, thumbnails: { [heightPx]: pinned.cid } } });
+
+    // LATER: Add data ref
+
+    // Return the CID
+    return { componentCid: cid, height: heightPx, thumbnailCid: pinned.cid };
+  }
+}
+
+export const thumbnailsService = new ThumbnailsService();
diff --git a/desci-server/src/services/data/processing.ts b/desci-server/src/services/data/processing.ts
index d1f8dcddb..bcb7c3b44 100644
--- a/desci-server/src/services/data/processing.ts
+++ b/desci-server/src/services/data/processing.ts
@@ -341,6 +341,16 @@ export async function getManifestFromNode(
   }
 }
 
+export async function getManifestByCid(manifestCid: string, queryString?: string): Promise<ResearchObjectV1> {
+  const manifestUrlEntry = manifestCid ? cleanupManifestUrl(manifestCid, queryString as string) : null;
+  try {
+    const fetchedManifest = manifestUrlEntry ? await (await axios.get(manifestUrlEntry)).data : null;
+    return fetchedManifest;
+  } catch (e) {
+    throw createIpfsUnresolvableError(`Error fetching manifest from IPFS, manifestCid: ${manifestCid}`);
+  }
+}
+
 export function pathContainsExternalCids(flatTreeMap: Record<DrivePath, RecursiveLsResult>, contextPath: string) {
   // Check if update path contains externals, disable adding to external DAGs
   const pathMatch = flatTreeMap[contextPath];
diff --git a/desci-server/src/services/ipfs.ts b/desci-server/src/services/ipfs.ts
index c25478bf3..9f6df529d 100644
--- a/desci-server/src/services/ipfs.ts
+++ b/desci-server/src/services/ipfs.ts
@@ -298,6 +298,14 @@ export async function pinExternalDags(cids: string[]): Promise<string[]> {
   return result;
 }
 
+export const pinFile = async (file: Buffer | Readable | ReadableStream): Promise<IpfsPinnedResult> => {
+  const isOnline = await client.isOnline();
+  logger.debug({ fn: 'pinFile' }, `isOnline: ${isOnline}`);
+
+  const uploaded = await client.add(file, { cidVersion: 1, pin: true });
+  return { ...uploaded, cid: uploaded.cid.toString() };
+};
+
 export interface RecursiveLsResult extends IpfsPinnedResult {
   name: string;
   contains?: RecursiveLsResult[];
diff --git a/desci-server/src/services/repoService.ts b/desci-server/src/services/repoService.ts
index 0cec60b0f..6fc3cddd7 100644
--- a/desci-server/src/services/repoService.ts
+++ b/desci-server/src/services/repoService.ts
@@ -4,6 +4,7 @@ import axios, { AxiosInstance } from 'axios';
 
 import { logger as parentLogger } from '../logger.js';
 import { ResearchObjectDocument } from '../types/documents.js';
+import { ensureUuidEndsWithDot } from '../utils.js';
 
 import { ManifestActions, NodeUuid } from './manifestRepo.js';
 
@@ -103,6 +104,7 @@ class RepoService {
   async getDraftManifest(uuid: NodeUuid) {
     logger.info({ uuid }, 'Retrieve Draft Document');
     // try {} catch (err) {}
+    // uuid = ensureUuidEndsWithDot(uuid) as NodeUuid;
     try {
       const response = await this.getDraftDocument({ uuid });
       return response ? response.manifest : null;

From 575aadccc17b4e932dd62b561774f13bfed6f5b3 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 03:10:16 +0700
Subject: [PATCH 14/42] slight fixes, update .env.example

---
 .env.example                                              | 3 +++
 desci-media-isolated/src/controllers/thumbnails/create.ts | 8 ++++++--
 desci-media-isolated/src/services/thumbnails.ts           | 4 ++--
 3 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/.env.example b/.env.example
index d6b37f9be..2a0b0c88e 100755
--- a/.env.example
+++ b/.env.example
@@ -5,6 +5,7 @@ PORT=5420
 IPFS_NODE_URL=http://host.docker.internal:5001
 PUBLIC_IPFS_RESOLVER=https://ipfs.io
 
+
 # IPFS_RESOLVER_OVERRIDE=http://host.docker.internal:8089/ipfs
 
 ### Database - Postgres
@@ -96,3 +97,5 @@ REPO_SERVICE_SECRET_KEY=secretrepo
 TOGGLE_CERAMIC=
 # If above is set, clone `@desci-labs/desci-codex` and put the path to it here
 CODEX_REPO_PATH=
+
+ISOLATED_MEDIA_SERVER_URL=http://host.docker.internal:7771
diff --git a/desci-media-isolated/src/controllers/thumbnails/create.ts b/desci-media-isolated/src/controllers/thumbnails/create.ts
index 0e23350b1..912bdd257 100644
--- a/desci-media-isolated/src/controllers/thumbnails/create.ts
+++ b/desci-media-isolated/src/controllers/thumbnails/create.ts
@@ -15,13 +15,17 @@ const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
 const BASE_TEMP_DIR = path.resolve(__dirname, '../../..', TEMP_DIR);
 
-export const generateThumbnail = async (req: Request<any, any, GenerateThumbnailRequestBody>, res: Response) => {
+export const generateThumbnail = async (
+  req: Request<any, any, GenerateThumbnailRequestBody, { height: number }>,
+  res: Response,
+) => {
   const { cid, fileName } = req.body;
+  const { height = 300 } = req.query;
   if (!cid) throw new BadRequestError('Missing cid in request body');
   if (!fileName) throw new BadRequestError('Missing fileName in request body');
 
   try {
-    const thumbnailPath = await ThumbnailsService.generateThumbnail(cid, fileName);
+    const thumbnailPath = await ThumbnailsService.generateThumbnail(cid, fileName, height);
     const fullThumbnailPath = path.join(BASE_TEMP_DIR, THUMBNAIL_OUTPUT_DIR, thumbnailPath);
 
     // Check if the file exists before attempting to stream it
diff --git a/desci-media-isolated/src/services/thumbnails.ts b/desci-media-isolated/src/services/thumbnails.ts
index b9adbc352..a3b9fff07 100644
--- a/desci-media-isolated/src/services/thumbnails.ts
+++ b/desci-media-isolated/src/services/thumbnails.ts
@@ -17,7 +17,7 @@ const __dirname = path.dirname(__filename);
 const BASE_TEMP_DIR = path.resolve(__dirname, '../..', TEMP_DIR);
 
 export class ThumbnailsService {
-  static async generateThumbnail(cid: string, fileName: string) {
+  static async generateThumbnail(cid: string, fileName: string, heightPx: number) {
     const extension = '.' + fileName.split('.').pop();
     if (!extension) throw new BadRequestError('Invalid file name, requires extension');
     const tempFilePath = path.join(BASE_TEMP_DIR, THUMBNAIL_FILES_DIR, `${cid + extension}`);
@@ -26,7 +26,7 @@ export class ThumbnailsService {
 
     await IpfsService.saveFile(cid, tempFilePath);
     try {
-      await generateAsync(tempFilePath, exportPath, { ...THUMBNAIL_DIMENSIONS });
+      await generateAsync(tempFilePath, exportPath, { ...THUMBNAIL_DIMENSIONS, height: heightPx });
       console.log('Thumbnail generated successfully:', exportPath);
       return thumbnailPath;
     } catch (e) {

From e7245debef37177950be78220962b59faeab966e Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 04:56:57 +0700
Subject: [PATCH 15/42] fix

---
 desci-server/src/middleware/permissions.ts | 12 +++++-------
 desci-server/src/services/Thumbnails.ts    |  3 ++-
 2 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/desci-server/src/middleware/permissions.ts b/desci-server/src/middleware/permissions.ts
index 4ad0c7608..db26115f1 100644
--- a/desci-server/src/middleware/permissions.ts
+++ b/desci-server/src/middleware/permissions.ts
@@ -13,13 +13,11 @@ export enum AuthMethods {
 }
 
 export const ensureUser = async (req: ExpressRequest, res: Response, next: NextFunction) => {
-  const authHeader = req.headers['authorization'];
-  const apiKeyHeader = req.headers['api-key'];
-
-  const token = authHeader ? await extractAuthToken(req) : undefined;
-  const apiKey = apiKeyHeader ? await await extractApiKey(req) : undefined;
-  const authTokenRetrieval = authHeader ? await extractUserFromToken(token) : undefined;
-  const apiKeyRetrieval = apiKeyHeader ? await extractUserFromApiKey(apiKey, req.ip) : undefined;
+  debugger;
+  const token = await extractAuthToken(req);
+  const apiKey = await extractApiKey(req);
+  const authTokenRetrieval = await extractUserFromToken(token);
+  const apiKeyRetrieval = await extractUserFromApiKey(apiKey, req.ip);
 
   const retrievedUser = authTokenRetrieval || apiKeyRetrieval;
 
diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
index 1916c287d..d1244314e 100644
--- a/desci-server/src/services/Thumbnails.ts
+++ b/desci-server/src/services/Thumbnails.ts
@@ -54,6 +54,7 @@ export class ThumbnailsService {
     //     map[key] = comp;
     //     return map;
     //   }, {});
+    if (!fileComponents) return {};
 
     const thumbnailsToGenerate: Record<CidString, FileName> = fileComponents?.reduce((map, comp) => {
       const fileName = comp.payload.path.split('/').pop();
@@ -65,7 +66,7 @@ export class ThumbnailsService {
     const thumbnailMap: ThumbnailMap = {};
     // Check which thumbnails already exist
     const existingThumbnailsFound = await prisma.nodeThumbnails.findMany({
-      where: { componentCid: { in: fileComponentCids } },
+      where: { componentCid: { in: fileComponentCids }, nodeUuid: ensureUuidEndsWithDot(uuid) },
     });
     // Check if the desired sizes exist, otherwise add to the generation array
     for (const thumbnail of existingThumbnailsFound) {

From 955e089d4e46b49a97ec98d2879fcfda7991338b Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 05:08:52 +0700
Subject: [PATCH 16/42] update example env

---
 .env.example                            | 2 +-
 desci-server/src/services/Thumbnails.ts | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.env.example b/.env.example
index 7f880a5d6..a8156e369 100755
--- a/.env.example
+++ b/.env.example
@@ -98,6 +98,6 @@ TOGGLE_CERAMIC=
 # If above is set, clone `@desci-labs/desci-codex` and put the path to it here
 CODEX_REPO_PATH=
 
-ISOLATED_MEDIA_SERVER_URL=http://host.docker.internal:7771
+ISOLATED_MEDIA_SERVER_URL=http://media_isolated:7771
 # SET TO 1 to run communities seed script
 RUN=1
diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
index d1244314e..a9bcc445b 100644
--- a/desci-server/src/services/Thumbnails.ts
+++ b/desci-server/src/services/Thumbnails.ts
@@ -6,7 +6,7 @@ import { ensureUuidEndsWithDot } from '../utils.js';
 
 import { getManifestByCid, getManifestFromNode, pinNewFiles } from './data/processing.js';
 import { pinFile } from './ipfs.js';
-import { NodeUuid } from './manifestRepo.js';
+import { NodeUuid, getLatestManifestFromNode } from './manifestRepo.js';
 import repoService from './repoService.js';
 
 const logger = parentLogger.child({
@@ -40,9 +40,9 @@ export class ThumbnailsService {
     // heightPx: HeightPx;
   }): Promise<ThumbnailMap> {
     debugger;
-    const manifest = manifestCid
-      ? await getManifestByCid(manifestCid)
-      : await repoService.getDraftManifest(uuid as NodeUuid);
+    const node = await prisma.node.findFirst({ where: { uuid: ensureUuidEndsWithDot(uuid) } });
+
+    const manifest = manifestCid ? await getManifestByCid(manifestCid) : await getLatestManifestFromNode(node);
 
     const pinnedComponents = manifest?.components?.filter((c) => c.starred);
 

From 744f9658656ce45ffd4cb8c37988484ff4456f52 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Thu, 22 Feb 2024 05:16:43 +0700
Subject: [PATCH 17/42] remove debuggers

---
 desci-server/src/middleware/permissions.ts | 2 +-
 desci-server/src/services/Thumbnails.ts    | 2 +-
 desci-server/src/services/user.ts          | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/desci-server/src/middleware/permissions.ts b/desci-server/src/middleware/permissions.ts
index db26115f1..0872a52a4 100644
--- a/desci-server/src/middleware/permissions.ts
+++ b/desci-server/src/middleware/permissions.ts
@@ -13,7 +13,7 @@ export enum AuthMethods {
 }
 
 export const ensureUser = async (req: ExpressRequest, res: Response, next: NextFunction) => {
-  debugger;
+  // debugger;
   const token = await extractAuthToken(req);
   const apiKey = await extractApiKey(req);
   const authTokenRetrieval = await extractUserFromToken(token);
diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
index a9bcc445b..d1f3314a9 100644
--- a/desci-server/src/services/Thumbnails.ts
+++ b/desci-server/src/services/Thumbnails.ts
@@ -39,7 +39,7 @@ export class ThumbnailsService {
     manifestCid?: string;
     // heightPx: HeightPx;
   }): Promise<ThumbnailMap> {
-    debugger;
+    // debugger;
     const node = await prisma.node.findFirst({ where: { uuid: ensureUuidEndsWithDot(uuid) } });
 
     const manifest = manifestCid ? await getManifestByCid(manifestCid) : await getLatestManifestFromNode(node);
diff --git a/desci-server/src/services/user.ts b/desci-server/src/services/user.ts
index c94405b3c..1f651ac08 100644
--- a/desci-server/src/services/user.ts
+++ b/desci-server/src/services/user.ts
@@ -98,7 +98,7 @@ export async function writeExternalIdToOrcidProfile(userId: number, didAddress:
       console.log('External ID already added');
       return;
     }
-    debugger;
+    // debugger;
   } catch (error) {
     console.error('Error getting external IDs:', error.response?.data || error.message);
   }

From 2d0c4ba639544ee7c68b4e9b47a3798ed2357f96 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Fri, 23 Feb 2024 04:05:01 +0700
Subject: [PATCH 18/42] fixes for local dev

---
 .env.example                                  |  5 +++
 desci-media-isolated/.env.example             |  7 ++++
 desci-media-isolated/.example.env             |  5 ---
 desci-media-isolated/src/services/ipfs.ts     |  4 ++-
 .../src/services/thumbnails.ts                |  2 ++
 .../src/controllers/proxy/ipfsReadGateway.ts  | 36 +++++++++++++++++++
 desci-server/src/routes/v1/data.ts            |  2 +-
 desci-server/src/routes/v1/index.ts           |  2 ++
 8 files changed, 56 insertions(+), 7 deletions(-)
 create mode 100644 desci-media-isolated/.env.example
 delete mode 100644 desci-media-isolated/.example.env
 create mode 100644 desci-server/src/controllers/proxy/ipfsReadGateway.ts

diff --git a/.env.example b/.env.example
index a8156e369..5c03f0e8d 100755
--- a/.env.example
+++ b/.env.example
@@ -98,6 +98,11 @@ TOGGLE_CERAMIC=
 # If above is set, clone `@desci-labs/desci-codex` and put the path to it here
 CODEX_REPO_PATH=
 
+
+
+# ISOLATED MEDIA SERVER
 ISOLATED_MEDIA_SERVER_URL=http://media_isolated:7771
+IPFS_READ_ONLY_GATEWAY_SERVER=http://host.docker.internal:8089/ipfs # Used to proxy ipfs requests for ISOLATED_MEDIA_SERVER
+
 # SET TO 1 to run communities seed script
 RUN=1
diff --git a/desci-media-isolated/.env.example b/desci-media-isolated/.env.example
new file mode 100644
index 000000000..01693de22
--- /dev/null
+++ b/desci-media-isolated/.env.example
@@ -0,0 +1,7 @@
+NODE_ENV=development
+
+PORT=7771
+
+# IPFS_GATEWAY=https://ipfs.desci.com/ipfs
+
+IPFS_GATEWAY=http://host.docker.internal:5420/v1/ipfs
\ No newline at end of file
diff --git a/desci-media-isolated/.example.env b/desci-media-isolated/.example.env
deleted file mode 100644
index f2fe1d153..000000000
--- a/desci-media-isolated/.example.env
+++ /dev/null
@@ -1,5 +0,0 @@
-NODE_ENV=development
-
-PORT=7771
-
-IPFS_GATEWAY=https://ipfs.desci.com/ipfs
\ No newline at end of file
diff --git a/desci-media-isolated/src/services/ipfs.ts b/desci-media-isolated/src/services/ipfs.ts
index cd4a96c11..bbf4413ce 100644
--- a/desci-media-isolated/src/services/ipfs.ts
+++ b/desci-media-isolated/src/services/ipfs.ts
@@ -7,9 +7,10 @@ import { IpfsConfigurationError } from '../utils/customErrors.js';
 export class IpfsService {
   static async saveFile(cid: string, outputPath: string) {
     if (!IPFS_GATEWAY) {
+      console.log('IPFS_GATEWAY:', IPFS_GATEWAY);
       throw new IpfsConfigurationError('process.env.IPFS_GATEWAY is not defined in environment variables');
     }
-
+    debugger;
     const url = `${IPFS_GATEWAY}/${cid}`;
 
     try {
@@ -17,6 +18,7 @@ export class IpfsService {
         method: 'get',
         url: url,
         responseType: 'stream',
+        timeout: 60000,
       });
 
       await pipeline(response.data, fs.createWriteStream(outputPath));
diff --git a/desci-media-isolated/src/services/thumbnails.ts b/desci-media-isolated/src/services/thumbnails.ts
index a3b9fff07..91f5bc92f 100644
--- a/desci-media-isolated/src/services/thumbnails.ts
+++ b/desci-media-isolated/src/services/thumbnails.ts
@@ -10,6 +10,8 @@ const THUMBNAIL_DIMENSIONS = {
   // width: 220,
   height: 300,
   keepAspect: true,
+  quality: '100',
+  background: 'white',
 };
 
 const __filename = fileURLToPath(import.meta.url);
diff --git a/desci-server/src/controllers/proxy/ipfsReadGateway.ts b/desci-server/src/controllers/proxy/ipfsReadGateway.ts
new file mode 100644
index 000000000..a0766c2cc
--- /dev/null
+++ b/desci-server/src/controllers/proxy/ipfsReadGateway.ts
@@ -0,0 +1,36 @@
+import axios from 'axios';
+import { Request, Response } from 'express';
+
+import { logger as parentLogger } from '../../logger.js';
+
+/**
+ * Proxy for the read only IPFS gateway, to allow the isolated media server to access IPFS content, without writability.
+ */
+export const ipfsReadGatewayProxy = async (req: Request, res: Response) => {
+  debugger;
+  try {
+    const logger = parentLogger.child({
+      module: 'PROXY::ipfsReadGatewayProxyController',
+      cid: req.params.cid,
+    });
+    const { cid } = req.params;
+    if (!process.env.IPFS_READ_ONLY_GATEWAY_SERVER) {
+      logger.error('IPFS_READ_ONLY_GATEWAY_SERVER is not defined in environment variables');
+      return res.status(500).send('Unable to connect to IPFS gateway');
+    }
+    const url = `${process.env.IPFS_READ_ONLY_GATEWAY_SERVER}/${cid}`;
+
+    // Forward the request to the IPFS gateway
+    const response = await axios.get(url, { responseType: 'stream' });
+
+    // Forward headers
+    res.set('Content-Type', response.headers['content-type']);
+
+    // Stream the response back to the client
+    response.data.pipe(res);
+  } catch (error) {
+    console.error('Error forwarding IPFS request:', error);
+    return res.status(500).send('Error forwarding IPFS request');
+  }
+  return res.status(200);
+};
diff --git a/desci-server/src/routes/v1/data.ts b/desci-server/src/routes/v1/data.ts
index c4057ed32..5ab9f4a68 100644
--- a/desci-server/src/routes/v1/data.ts
+++ b/desci-server/src/routes/v1/data.ts
@@ -8,8 +8,8 @@ import { pubTree, retrieveTree, deleteData, update, renameData } from '../../con
 import { moveData } from '../../controllers/data/move.js';
 import { updateExternalCid } from '../../controllers/data/updateExternalCid.js';
 import { logger } from '../../logger.js';
-import { ensureNodeAccess, ensureWriteAccessCheck } from '../../middleware/authorisation.js';
 import { attachUser } from '../../middleware/attachUser.js';
+import { ensureNodeAccess, ensureWriteAccessCheck } from '../../middleware/authorisation.js';
 import { ensureUser } from '../../middleware/permissions.js';
 import { isS3Configured, s3Client } from '../../services/s3.js';
 
diff --git a/desci-server/src/routes/v1/index.ts b/desci-server/src/routes/v1/index.ts
index 57caba27d..89fc2c568 100755
--- a/desci-server/src/routes/v1/index.ts
+++ b/desci-server/src/routes/v1/index.ts
@@ -4,6 +4,7 @@ import { generateNonce } from 'siwe';
 import { prisma } from '../../client.js';
 import { queryResearchFields } from '../../controllers/data/index.js';
 import { queryRor } from '../../controllers/proxy/index.js';
+import { ipfsReadGatewayProxy } from '../../controllers/proxy/ipfsReadGateway.js';
 import { nft } from '../../controllers/raw/nft.js';
 import { ensureUser } from '../../middleware/permissions.js';
 
@@ -53,5 +54,6 @@ router.get('/nft/:id', nft);
 router.use('/referral', referral);
 router.get('/researchFields', [ensureUser], queryResearchFields);
 router.get('/ror', [ensureUser], queryRor);
+router.get('/ipfs/:cid', ipfsReadGatewayProxy);
 
 export default router;

From ff689c8719727fdb119c8d11d01b24838247d233 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Sat, 24 Feb 2024 04:02:32 +0700
Subject: [PATCH 19/42] remove links from thumbnail gen list

---
 desci-server/src/controllers/data/delete.ts           |  1 -
 desci-server/src/controllers/nodes/thumbnails.ts      | 11 ++++++++++-
 desci-server/src/controllers/proxy/ipfsReadGateway.ts |  1 -
 desci-server/src/services/Thumbnails.ts               | 11 +++++------
 4 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/desci-server/src/controllers/data/delete.ts b/desci-server/src/controllers/data/delete.ts
index e65adadad..0bfc8a1d1 100644
--- a/desci-server/src/controllers/data/delete.ts
+++ b/desci-server/src/controllers/data/delete.ts
@@ -50,7 +50,6 @@ export const deleteData = async (req: Request, res: Response<DeleteResponse | Er
     /**
      * Remove draft node tree entries, add them to the cid prune list
      */
-    // debugger;
     const entriesToDelete = await prisma.draftNodeTree.findMany({
       where: {
         nodeId: node.id,
diff --git a/desci-server/src/controllers/nodes/thumbnails.ts b/desci-server/src/controllers/nodes/thumbnails.ts
index 6ae6384b8..09180e36c 100644
--- a/desci-server/src/controllers/nodes/thumbnails.ts
+++ b/desci-server/src/controllers/nodes/thumbnails.ts
@@ -2,6 +2,7 @@ import type { Request, Response, NextFunction } from 'express';
 
 import { prisma } from '../../client.js';
 import { NodeUuid } from '../../internal.js';
+import { logger as parentLogger } from '../../logger.js';
 import { type ThumbnailMap, thumbnailsService } from '../../services/Thumbnails.js';
 import { ensureUuidEndsWithDot } from '../../utils.js';
 
@@ -34,6 +35,14 @@ export const thumbnails = async (
   const user = (req as any).user;
   const { uuid, manifestCid } = req.params;
 
+  const logger = parentLogger.child({
+    module: 'NODES::Thumbnails',
+    uuid,
+    manifestCid,
+    userId: user.id,
+  });
+  logger.trace({ fn: 'Retrieving thumbnails' });
+
   if (!uuid) return res.status(400).json({ ok: false, error: 'UUID is required.' });
 
   if (!user && !manifestCid) {
@@ -52,7 +61,7 @@ export const thumbnails = async (
 
     if (!node) return res.status(401).json({ ok: false, error: 'Unauthorized' });
   }
-
+  // debugger;
   const thumbnailMap = await thumbnailsService.getThumbnailsForNode({ uuid: uuid as NodeUuid, manifestCid });
 
   return res.status(200).json({ ok: true, thumbnailMap });
diff --git a/desci-server/src/controllers/proxy/ipfsReadGateway.ts b/desci-server/src/controllers/proxy/ipfsReadGateway.ts
index a0766c2cc..350fc8610 100644
--- a/desci-server/src/controllers/proxy/ipfsReadGateway.ts
+++ b/desci-server/src/controllers/proxy/ipfsReadGateway.ts
@@ -7,7 +7,6 @@ import { logger as parentLogger } from '../../logger.js';
  * Proxy for the read only IPFS gateway, to allow the isolated media server to access IPFS content, without writability.
  */
 export const ipfsReadGatewayProxy = async (req: Request, res: Response) => {
-  debugger;
   try {
     const logger = parentLogger.child({
       module: 'PROXY::ipfsReadGatewayProxyController',
diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
index d1f3314a9..18042cb42 100644
--- a/desci-server/src/services/Thumbnails.ts
+++ b/desci-server/src/services/Thumbnails.ts
@@ -8,6 +8,7 @@ import { getManifestByCid, getManifestFromNode, pinNewFiles } from './data/proce
 import { pinFile } from './ipfs.js';
 import { NodeUuid, getLatestManifestFromNode } from './manifestRepo.js';
 import repoService from './repoService.js';
+import { ResearchObjectComponentType } from '@desci-labs/desci-models';
 
 const logger = parentLogger.child({
   module: 'Services::Thumbnails',
@@ -47,13 +48,11 @@ export class ThumbnailsService {
     const pinnedComponents = manifest?.components?.filter((c) => c.starred);
 
     // Determined by the file extension (can't generate thumbnails for files without extensions)
-    const fileComponents = pinnedComponents?.filter((c) => c.payload.path.split('/').pop().includes('.'));
+    const fileComponents = pinnedComponents?.filter(
+      (c) => c.payload.path.split('/').pop().includes('.') && c.type !== ResearchObjectComponentType.LINK,
+    );
     const fileComponentCids = fileComponents?.map((c) => c.payload.cid || c.payload.url);
-    // const fileComponentCidMap = fileComponents.reduce((map, comp) => {
-    //     const key = comp.payload.cid || comp.payload.url;
-    //     map[key] = comp;
-    //     return map;
-    //   }, {});
+
     if (!fileComponents) return {};
 
     const thumbnailsToGenerate: Record<CidString, FileName> = fileComponents?.reduce((map, comp) => {

From 740e31e497084c96cec65efcc364664a9df07314 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 03:53:05 +0700
Subject: [PATCH 20/42] improve thumbnail gen logic to not fail on a single odd
 extension

---
 desci-server/src/services/Thumbnails.ts | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
index 18042cb42..f868e2229 100644
--- a/desci-server/src/services/Thumbnails.ts
+++ b/desci-server/src/services/Thumbnails.ts
@@ -1,3 +1,4 @@
+import { ResearchObjectComponentType } from '@desci-labs/desci-models';
 import axios from 'axios';
 
 import { prisma } from '../client.js';
@@ -7,8 +8,6 @@ import { ensureUuidEndsWithDot } from '../utils.js';
 import { getManifestByCid, getManifestFromNode, pinNewFiles } from './data/processing.js';
 import { pinFile } from './ipfs.js';
 import { NodeUuid, getLatestManifestFromNode } from './manifestRepo.js';
-import repoService from './repoService.js';
-import { ResearchObjectComponentType } from '@desci-labs/desci-models';
 
 const logger = parentLogger.child({
   module: 'Services::Thumbnails',
@@ -79,7 +78,7 @@ export class ThumbnailsService {
     }
 
     // Generate thumbnails for the ones that don't exist
-    const generatedThumbnails = await Promise.all(
+    const generatedThumbnails = await Promise.allSettled(
       Object.entries(thumbnailsToGenerate).map(([cid, fileName]) =>
         this.generateThumbnail(uuid, cid, fileName, HEIGHT_PX),
       ),
@@ -87,7 +86,9 @@ export class ThumbnailsService {
 
     // Add the newly generated ones to the thumbnail map
     generatedThumbnails.forEach((newThumb) => {
-      thumbnailMap[newThumb.componentCid] = { [HEIGHT_PX]: newThumb.thumbnailCid };
+      if (newThumb.status === 'fulfilled' && 'componentCid' in newThumb.value) {
+        thumbnailMap[newThumb.value.componentCid] = { [HEIGHT_PX]: newThumb.value.thumbnailCid };
+      }
     });
 
     return thumbnailMap;

From f11140ba2d664536c867d16da1a5304fc4528827 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 04:31:52 +0700
Subject: [PATCH 21/42] add gh actions image building config, adjust k8s
 configs for isolated media server

---
 .../build-isolated-media-server.yaml          | 119 ++++++++++++++++++
 .../kubernetes/deployment.yaml                |  36 ++++++
 .../kubernetes/deployment_dev.yaml            |  14 +--
 3 files changed, 162 insertions(+), 7 deletions(-)
 create mode 100644 .github/workflows/build-isolated-media-server.yaml
 create mode 100644 desci-media-isolated/kubernetes/deployment.yaml

diff --git a/.github/workflows/build-isolated-media-server.yaml b/.github/workflows/build-isolated-media-server.yaml
new file mode 100644
index 000000000..76e49f398
--- /dev/null
+++ b/.github/workflows/build-isolated-media-server.yaml
@@ -0,0 +1,119 @@
+# build.yml
+on:
+  push:
+    paths:
+      - .github/workflows/**
+      - desci-media-isolated/**
+      - Dockerfile
+    branches: # array of glob patterns matching against refs/heads. Optional; defaults to all
+      - main # triggers on pushes that contain changes
+      - develop
+
+      # TODO: add demo env
+
+name: Build desci-media-isolated
+
+# https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
+env:
+  AWS_DEFAULT_REGION: us-east-2
+  AWS_DEFAULT_OUTPUT: json
+  AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
+  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+  CONTAINER_IMAGE: desci-media-isolated
+  DOCKER_BUILDKIT: 1
+
+jobs:
+  build-and-push:
+    name: Build and deploy
+    runs-on: ubuntu-latest
+    steps:
+      - uses: hashicorp/setup-terraform@v1
+      - name: Checkout
+        uses: actions/checkout@master
+
+      # Add steps here like linting, testing, minification, etc.
+      - id: install-aws-cli
+        uses: unfor19/install-aws-cli-action@v1
+        with:
+          version: 1
+
+      - uses: prepor/action-aws-iam-authenticator@master
+      - run: aws-iam-authenticator version
+
+      - name: Install Kubectl
+        run: |
+          #$(curl -Ls https://dl.k8s.io/release/stable.txt)
+          version=v1.23.6
+          echo "using kubectl@$version"
+          curl -sLO "https://dl.k8s.io/release/$version/bin/linux/amd64/kubectl" -o kubectl
+          chmod +x kubectl
+          mv kubectl /usr/local/bin
+          mkdir $HOME/.kube
+          sudo apt-get update
+          sudo apt-get install less
+          echo ${{ secrets.KUBE_CONFIG_DATA }} | base64 --decode > $HOME/.kube/config
+          aws sts get-caller-identity
+          kubectl describe deployments
+
+      - name: Build and tag the image (DEV)
+        if: github.ref == 'refs/heads/develop'
+        run: |
+          # Build and tag the image
+          docker build \
+            --target production -t $CONTAINER_IMAGE-dev:production \
+            --target production -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-dev \
+             ./desci-media-isolated
+
+      - name: Build and tag the image (PROD)
+        if: github.ref == 'refs/heads/main'
+        run: |
+          # Build and tag the image
+          docker build \
+            --target production -t $CONTAINER_IMAGE:production \
+            --target production -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE \
+             ./desci-media-isolated
+
+      # Add additional steps here like scanning of image
+
+      # Only push to registry on master
+      - name: Push (DEV)
+        if: github.ref == 'refs/heads/develop'
+        run: |
+          # Push image to AWS ECR
+          aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
+          docker tag $CONTAINER_IMAGE-dev:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-dev:${{ github.sha }}
+          docker tag $CONTAINER_IMAGE-dev:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-dev:production
+          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-dev:${{ github.sha }}
+          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-dev:production
+
+      - name: Push (PROD)
+        if: github.ref == 'refs/heads/main'
+        run: |
+          # Push image to AWS ECR
+          aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
+          docker tag $CONTAINER_IMAGE:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:${{ github.sha }}
+          docker tag $CONTAINER_IMAGE:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:production
+          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:${{ github.sha }}
+          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:production
+
+      - name: Deploy to EKS (DEV)
+        # uses: steebchen/kubectl@v2.0.0
+        if: github.ref == 'refs/heads/develop'
+        run: | # defaults to latest kubectl binary version
+          kubectl set image deployment/desci-media-isolated-dev desci-media-isolated-dev=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-dev:${{ github.sha }} --record
+
+      - name: Deploy to EKS (PROD)
+        if: github.ref == 'refs/heads/main'
+        run: | # defaults to latest kubectl binary version
+          kubectl set image deployment/desci-media-isolated desci-media-isolated=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:${{ github.sha }} --record
+
+      - name: Verify EKS Deployment (DEV)
+        if: github.ref == 'refs/heads/develop'
+        run: |
+          kubectl rollout status deployment/desci-media-isolated-dev
+
+      - name: Verify EKS Deployment (PROD)
+        if: github.ref == 'refs/heads/main'
+        run: |
+          kubectl rollout status deployment/desci-media-isolated
diff --git a/desci-media-isolated/kubernetes/deployment.yaml b/desci-media-isolated/kubernetes/deployment.yaml
new file mode 100644
index 000000000..7190a322d
--- /dev/null
+++ b/desci-media-isolated/kubernetes/deployment.yaml
@@ -0,0 +1,36 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: desci-media-isolated
+  labels:
+    App: DesciMediaIsolated
+spec:
+  replicas: 1
+  revisionHistoryLimit: 2
+  selector:
+    matchLabels:
+      App: DesciMediaIsolated
+  strategy:
+    rollingUpdate:
+      maxSurge: 25%
+      maxUnavailable: 25%
+    type: RollingUpdate
+  template:
+    metadata:
+      labels:
+        App: DesciMediaIsolated
+    spec:
+      containers:
+        - image: 523044037273.dkr.ecr.us-east-2.amazonaws.com/desci-media-isolated:production
+          name: desci-media-isolated
+          ports:
+            - containerPort: 7771
+              name: media_isolated
+          resources:
+            limits:
+              cpu: '0.5'
+              memory: 2Gi
+            requests:
+              cpu: 250m
+              memory: 1Gi
+      serviceAccountName: 'default'
diff --git a/desci-media-isolated/kubernetes/deployment_dev.yaml b/desci-media-isolated/kubernetes/deployment_dev.yaml
index 7887ea5b0..b946ef574 100644
--- a/desci-media-isolated/kubernetes/deployment_dev.yaml
+++ b/desci-media-isolated/kubernetes/deployment_dev.yaml
@@ -1,15 +1,15 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: nodes-isolated-media-server-dev
+  name: desci-media-isolated-dev
   labels:
-    App: NodesIsolatedMediaServerDev
+    App: DesciMediaIsolatedDev
 spec:
   replicas: 1
   revisionHistoryLimit: 2
   selector:
     matchLabels:
-      App: NodesIsolatedMediaServerDev
+      App: DesciMediaIsolatedDev
   strategy:
     rollingUpdate:
       maxSurge: 25%
@@ -18,14 +18,14 @@ spec:
   template:
     metadata:
       labels:
-        App: NodesIsolatedMediaServerDev
+        App: DesciMediaIsolatedDev
     spec:
       containers:
-        - image: {CHANGE}.dkr.ecr.us-east-2.amazonaws.com/nodes-isolated-media-server-dev:production
-          name: nodes-isolated-media-server-dev
+        - image: 523044037273.dkr.ecr.us-east-2.amazonaws.com/desci-media-isolated-dev:production
+          name: desci-media-isolated-dev
           ports:
             - containerPort: 7771
-              name: media_isolated
+              name: media_isolated_dev
           resources:
             limits:
               cpu: '0.5'

From a6c3f4977593bbbae031689097da3ae635090cc4 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 04:40:14 +0700
Subject: [PATCH 22/42] adjust k8s network policy configs for isolated media
 server

---
 .../kubernetes/network_policies-dev.yaml      | 34 +++++++++++++++++++
 .../kubernetes/network_policies.yaml          | 16 ++++++---
 2 files changed, 45 insertions(+), 5 deletions(-)
 create mode 100644 desci-media-isolated/kubernetes/network_policies-dev.yaml

diff --git a/desci-media-isolated/kubernetes/network_policies-dev.yaml b/desci-media-isolated/kubernetes/network_policies-dev.yaml
new file mode 100644
index 000000000..a23e838c7
--- /dev/null
+++ b/desci-media-isolated/kubernetes/network_policies-dev.yaml
@@ -0,0 +1,34 @@
+# Enable ingress from DesciServerDev only
+kind: NetworkPolicy
+apiVersion: networking.k8s.io/v1
+metadata:
+  name: allow-backend-pod-to-isolated-media-server-dev-comms
+spec:
+  podSelector:
+    matchLabels:
+      App: DesciMediaIsolatedDev
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              allow-from: 'DesciServerDev'
+  policyTypes:
+    - Ingress
+
+
+# Enable egress to DesciServerDev only
+kind: NetworkPolicy
+apiVersion: networking.k8s.io/v1
+metadata:
+  name: allow-egress-to-desciserverdev-only
+spec:
+  podSelector:
+    matchLabels:
+      App: DesciMediaIsolatedDev
+  egress:
+    - to:
+        - podSelector:
+            matchLabels:
+              allow-from: 'DesciServerDev'
+  policyTypes:
+    - Egress
diff --git a/desci-media-isolated/kubernetes/network_policies.yaml b/desci-media-isolated/kubernetes/network_policies.yaml
index 8b2819127..7bc77dff0 100644
--- a/desci-media-isolated/kubernetes/network_policies.yaml
+++ b/desci-media-isolated/kubernetes/network_policies.yaml
@@ -1,3 +1,4 @@
+# Enable ingress from DesciServer only
 kind: NetworkPolicy
 apiVersion: networking.k8s.io/v1
 metadata:
@@ -5,24 +6,29 @@ metadata:
 spec:
   podSelector:
     matchLabels:
-      App: NodesIsolatedMediaServerDev
+      App: DesciMediaIsolated
   ingress:
     - from:
         - podSelector:
             matchLabels:
-              allow-from: 'DesciServerDev'
+              allow-from: 'DesciServer'
   policyTypes:
     - Ingress
 
 
+# Enable egress to DesciServer only
 kind: NetworkPolicy
 apiVersion: networking.k8s.io/v1
 metadata:
-  name: deny-egress-from-isolated-media-server
+  name: allow-egress-to-desciserver-only
 spec:
   podSelector:
     matchLabels:
-      App: NodesIsolatedMediaServerDev
-  egress: []
+      App: DesciMediaIsolated
+  egress:
+    - to:
+        - podSelector:
+            matchLabels:
+              allow-from: 'DesciServer'
   policyTypes:
     - Egress

From cbdda118cc7b2f257a6d7c88011db9de35f45923 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 04:41:55 +0700
Subject: [PATCH 23/42] clean

---
 desci-media-isolated/.env.example | 2 --
 1 file changed, 2 deletions(-)

diff --git a/desci-media-isolated/.env.example b/desci-media-isolated/.env.example
index 01693de22..d61e65e5f 100644
--- a/desci-media-isolated/.env.example
+++ b/desci-media-isolated/.env.example
@@ -2,6 +2,4 @@ NODE_ENV=development
 
 PORT=7771
 
-# IPFS_GATEWAY=https://ipfs.desci.com/ipfs
-
 IPFS_GATEWAY=http://host.docker.internal:5420/v1/ipfs
\ No newline at end of file

From 4c96c5e8b0878e9e86a029956fa9f4c08d73ef08 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 14:25:26 +0700
Subject: [PATCH 24/42] auto gen .env in dev run if not present

---
 desci-media-isolated/Dockerfile | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 9a07b49a8..2256172d2 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -26,8 +26,12 @@ RUN --mount=type=cache,target=/usr/src/app/.npm \
   npm set cache /usr/src/app/.npm && \
   npm install
 
+
 COPY . .
 
+# Auto create .env from .env.example if it doesn't exist
+RUN if [ ! -f .env ]; then cp .env.example .env; fi
+
 # Expose debugger port
 EXPOSE 9777
 

From ff60247ea0f4668425cc5652c580e3e68c4b102f Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 14:58:08 +0700
Subject: [PATCH 25/42] prevent dotenv import issues

---
 desci-media-isolated/package.json | 3 ++-
 desci-media-isolated/src/index.ts | 4 +++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
index 8104c1fc7..f888fb47c 100644
--- a/desci-media-isolated/package.json
+++ b/desci-media-isolated/package.json
@@ -26,6 +26,7 @@
     "@typescript-eslint/parser": "^7.0.1",
     "eslint": "^8.56.0",
     "ts-node-dev": "^2.0.0",
-    "typescript": "^5.3.3"
+    "typescript": "^5.3.3",
+    "dotenv": "^16.4.5"
   }
 }
diff --git a/desci-media-isolated/src/index.ts b/desci-media-isolated/src/index.ts
index abbed5727..dfec27a1a 100644
--- a/desci-media-isolated/src/index.ts
+++ b/desci-media-isolated/src/index.ts
@@ -1,9 +1,11 @@
-import 'dotenv/config';
+import dotenv from 'dotenv';
 import express from 'express';
 import helmet from 'helmet';
 import { errorHandler } from './middleware/errorHandler.js';
 import routes from './routes/index.js';
 
+dotenv.config();
+
 const app = express();
 console.log('process.env.PORT:', process.env.PORT);
 const PORT = process.env.PORT || 7771;

From 40ad6c642e66b9eb3389dd71fd6d87fc468e0454 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 15:41:02 +0700
Subject: [PATCH 26/42] adjust image to fix prod issues

---
 desci-media-isolated/.dockerignore     |   2 +-
 desci-media-isolated/Dockerfile        |  14 +-
 desci-media-isolated/package-lock.json | 348 +------------------------
 desci-media-isolated/package.json      |   1 -
 4 files changed, 15 insertions(+), 350 deletions(-)

diff --git a/desci-media-isolated/.dockerignore b/desci-media-isolated/.dockerignore
index 0af6aafe9..4e6088d61 100755
--- a/desci-media-isolated/.dockerignore
+++ b/desci-media-isolated/.dockerignore
@@ -3,4 +3,4 @@ database
 dist
 node_modules
 .git
-# .env
\ No newline at end of file
+# .env
diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 2256172d2..83a38259e 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -18,6 +18,7 @@ RUN chown -R 1001:0 /.npm
 # App Setup
 WORKDIR /usr/src/app
 
+COPY tsconfig.json .
 COPY package*.json ./
 
 FROM base as dev
@@ -39,16 +40,21 @@ CMD ["dumb-init", "npx", "tsx","watch", "--inspect=0.0.0.0:9777", "src/index.ts"
 
 FROM base as production
 # Cache mounts for faster builds, prod env for better express perf
-ENV NODE_ENV production
 RUN --mount=type=cache,target=/usr/src/app/.npm \
   npm set cache /usr/src/app/.npm && \
-  npm ci --only=production
-
+  npm install
 
+ENV NODE_ENV production
 # 'node' user is created by the node image, prevent perm issues, run with reduced privs
+RUN mkdir -p /usr/src/app/dist && chown node:node /usr/src/app/dist
 USER node
 COPY --chown=node:node ./src/ .
-RUN npm run build
+USER root
+RUN chown -R node:node /usr/src/app
+USER node
+RUN npm run build && \
+    npm prune --production
+
 
 
 CMD ["dumb-init", "node", "dist/index.js"]
diff --git a/desci-media-isolated/package-lock.json b/desci-media-isolated/package-lock.json
index 32d011c98..fd21ae5a2 100644
--- a/desci-media-isolated/package-lock.json
+++ b/desci-media-isolated/package-lock.json
@@ -14,12 +14,14 @@
         "express": "^4.18.2",
         "filepreview_ts": "^1.0.0",
         "helmet": "^7.1.0",
-        "tsx": "^4.7.1"
+        "tsx": "^4.7.1",
+        "typescript": "^5.3.3"
       },
       "devDependencies": {
         "@types/express": "^4.17.21",
         "@typescript-eslint/eslint-plugin": "^7.0.1",
         "@typescript-eslint/parser": "^7.0.1",
+        "dotenv": "^16.4.5",
         "eslint": "^8.56.0",
         "ts-node-dev": "^2.0.0",
         "typescript": "^5.3.3"
@@ -46,246 +48,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
-      "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
-      "cpu": [
-        "ppc64"
-      ],
-      "optional": true,
-      "os": [
-        "aix"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/android-arm": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
-      "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
-      "cpu": [
-        "arm"
-      ],
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/android-arm64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
-      "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/android-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
-      "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
-      "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/darwin-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
-      "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
-      "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
-      "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-arm": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
-      "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
-      "cpu": [
-        "arm"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-arm64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
-      "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-ia32": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
-      "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
-      "cpu": [
-        "ia32"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-loong64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
-      "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
-      "cpu": [
-        "loong64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
-      "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
-      "cpu": [
-        "mips64el"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
-      "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
-      "cpu": [
-        "ppc64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
-      "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
-      "cpu": [
-        "riscv64"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/linux-s390x": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
-      "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
-      "cpu": [
-        "s390x"
-      ],
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/@esbuild/linux-x64": {
       "version": "0.19.12",
       "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
@@ -301,96 +63,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
-      "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
-      "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/sunos-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
-      "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/win32-arm64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
-      "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
-      "cpu": [
-        "arm64"
-      ],
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/win32-ia32": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
-      "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@esbuild/win32-x64": {
-      "version": "0.19.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
-      "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
-      "cpu": [
-        "x64"
-      ],
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1511,6 +1183,7 @@
       "version": "16.4.5",
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
       "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+      "dev": true,
       "engines": {
         "node": ">=12"
       },
@@ -2033,19 +1706,6 @@
       "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
       "dev": true
     },
-    "node_modules/fsevents": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
-      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "hasInstallScript": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
     "node_modules/function-bind": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
index f888fb47c..d94ece43d 100644
--- a/desci-media-isolated/package.json
+++ b/desci-media-isolated/package.json
@@ -14,7 +14,6 @@
   "license": "ISC",
   "dependencies": {
     "axios": "^1.6.7",
-    "dotenv": "^16.4.5",
     "express": "^4.18.2",
     "filepreview_ts": "^1.0.0",
     "helmet": "^7.1.0",

From cbc846dce5fa4ca4fcc0558c4c4fefc157e875e0 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 15:51:04 +0700
Subject: [PATCH 27/42] hardcode envs in for k8s deployment

---
 desci-media-isolated/kubernetes/deployment.yaml     | 5 +++++
 desci-media-isolated/kubernetes/deployment_dev.yaml | 5 +++++
 desci-media-isolated/package-lock.json              | 5 +----
 desci-media-isolated/package.json                   | 6 +++---
 4 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/desci-media-isolated/kubernetes/deployment.yaml b/desci-media-isolated/kubernetes/deployment.yaml
index 7190a322d..32c08e2b9 100644
--- a/desci-media-isolated/kubernetes/deployment.yaml
+++ b/desci-media-isolated/kubernetes/deployment.yaml
@@ -26,6 +26,11 @@ spec:
           ports:
             - containerPort: 7771
               name: media_isolated
+          env:
+            - name: PORT
+              value: '7771'
+            - name: IPFS_GATEWAY
+              value: 'http://host.docker.internal:5420/v1/ipfs'
           resources:
             limits:
               cpu: '0.5'
diff --git a/desci-media-isolated/kubernetes/deployment_dev.yaml b/desci-media-isolated/kubernetes/deployment_dev.yaml
index b946ef574..519e2f5c3 100644
--- a/desci-media-isolated/kubernetes/deployment_dev.yaml
+++ b/desci-media-isolated/kubernetes/deployment_dev.yaml
@@ -26,6 +26,11 @@ spec:
           ports:
             - containerPort: 7771
               name: media_isolated_dev
+          env:
+            - name: PORT
+              value: '7771'
+            - name: IPFS_GATEWAY
+              value: 'http://host.docker.internal:5420/v1/ipfs'
           resources:
             limits:
               cpu: '0.5'
diff --git a/desci-media-isolated/package-lock.json b/desci-media-isolated/package-lock.json
index fd21ae5a2..1a195e658 100644
--- a/desci-media-isolated/package-lock.json
+++ b/desci-media-isolated/package-lock.json
@@ -14,14 +14,12 @@
         "express": "^4.18.2",
         "filepreview_ts": "^1.0.0",
         "helmet": "^7.1.0",
-        "tsx": "^4.7.1",
-        "typescript": "^5.3.3"
+        "tsx": "^4.7.1"
       },
       "devDependencies": {
         "@types/express": "^4.17.21",
         "@typescript-eslint/eslint-plugin": "^7.0.1",
         "@typescript-eslint/parser": "^7.0.1",
-        "dotenv": "^16.4.5",
         "eslint": "^8.56.0",
         "ts-node-dev": "^2.0.0",
         "typescript": "^5.3.3"
@@ -1183,7 +1181,6 @@
       "version": "16.4.5",
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
       "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
-      "dev": true,
       "engines": {
         "node": ">=12"
       },
diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
index d94ece43d..2648360dd 100644
--- a/desci-media-isolated/package.json
+++ b/desci-media-isolated/package.json
@@ -17,7 +17,8 @@
     "express": "^4.18.2",
     "filepreview_ts": "^1.0.0",
     "helmet": "^7.1.0",
-    "tsx": "^4.7.1"
+    "tsx": "^4.7.1",
+    "dotenv": "^16.4.5"
   },
   "devDependencies": {
     "@types/express": "^4.17.21",
@@ -25,7 +26,6 @@
     "@typescript-eslint/parser": "^7.0.1",
     "eslint": "^8.56.0",
     "ts-node-dev": "^2.0.0",
-    "typescript": "^5.3.3",
-    "dotenv": "^16.4.5"
+    "typescript": "^5.3.3"
   }
 }

From a0b7b9fb226c9b885e1149c35091717603866c75 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 16:35:42 +0700
Subject: [PATCH 28/42] generate .env in makefile for isolated-media

---
 desci-media-isolated/src/services/ipfs.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/desci-media-isolated/src/services/ipfs.ts b/desci-media-isolated/src/services/ipfs.ts
index bbf4413ce..e1adc1457 100644
--- a/desci-media-isolated/src/services/ipfs.ts
+++ b/desci-media-isolated/src/services/ipfs.ts
@@ -10,7 +10,7 @@ export class IpfsService {
       console.log('IPFS_GATEWAY:', IPFS_GATEWAY);
       throw new IpfsConfigurationError('process.env.IPFS_GATEWAY is not defined in environment variables');
     }
-    debugger;
+    // debugger;
     const url = `${IPFS_GATEWAY}/${cid}`;
 
     try {

From a51c896efd75ce6c11b27411096513abc7e85d36 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 16:39:33 +0700
Subject: [PATCH 29/42] push

---
 Makefile                                         | 3 +++
 desci-server/src/controllers/nodes/thumbnails.ts | 2 +-
 desci-server/src/services/Thumbnails.ts          | 2 +-
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 9de709eb6..958c0f944 100644
--- a/Makefile
+++ b/Makefile
@@ -42,3 +42,6 @@ nodes-media/.env:
 desci-repo/.env:
 	if [ ! -f desci-repo/.env ]; then cp desci-repo/.env.example desci-repo/.env; fi
 
+desci-media-isolated/.env:
+	if [ ! -f desci-media-isolated/.env ]; then cp desci-media-isolated/.env.example desci-media-isolated/.env; fi
+
diff --git a/desci-server/src/controllers/nodes/thumbnails.ts b/desci-server/src/controllers/nodes/thumbnails.ts
index 09180e36c..b3969d385 100644
--- a/desci-server/src/controllers/nodes/thumbnails.ts
+++ b/desci-server/src/controllers/nodes/thumbnails.ts
@@ -34,7 +34,7 @@ export const thumbnails = async (
 ) => {
   const user = (req as any).user;
   const { uuid, manifestCid } = req.params;
-
+  // debugger;
   const logger = parentLogger.child({
     module: 'NODES::Thumbnails',
     uuid,
diff --git a/desci-server/src/services/Thumbnails.ts b/desci-server/src/services/Thumbnails.ts
index f868e2229..5c3b4f4c8 100644
--- a/desci-server/src/services/Thumbnails.ts
+++ b/desci-server/src/services/Thumbnails.ts
@@ -105,6 +105,7 @@ export class ThumbnailsService {
       return null;
     }
     // Generate the thumbnail
+    // debugger;
     const thumbnailStream = await axios.post(
       `${process.env.ISOLATED_MEDIA_SERVER_URL}/v1/thumbnails?height${heightPx}`,
       { cid: cid, fileName: componentFileName },
@@ -112,7 +113,6 @@ export class ThumbnailsService {
         responseType: 'stream',
       },
     );
-
     // Save it on IPFS
     const pinned = await pinFile(thumbnailStream.data);
     // Save it to the database

From 17c4e57145a0cf00ea173720f98ca0dbce95b2a3 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 19:09:58 +0700
Subject: [PATCH 30/42] fixes

---
 desci-media-isolated/Dockerfile           | 4 ++--
 desci-media-isolated/src/services/ipfs.ts | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 83a38259e..b6c2f853f 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -12,8 +12,8 @@ RUN apt-get update && apt-get install -y dumb-init ghostscript unoconv ffmpeg im
 RUN sed -i '/<policy domain="coder" rights="none" pattern="PDF" \/>/c\<policy domain="Undefined" rights="read|write" pattern="PDF" \/>' /etc/ImageMagick-6/policy.xml
 
 # NPM Permission Fix
-RUN mkdir -p /.npm
-RUN chown -R 1001:0 /.npm
+# RUN mkdir -p /.npm
+# RUN chown -R 1001:0 /.npm
 
 # App Setup
 WORKDIR /usr/src/app
diff --git a/desci-media-isolated/src/services/ipfs.ts b/desci-media-isolated/src/services/ipfs.ts
index e1adc1457..e8ee8a968 100644
--- a/desci-media-isolated/src/services/ipfs.ts
+++ b/desci-media-isolated/src/services/ipfs.ts
@@ -1,13 +1,13 @@
 import axios from 'axios';
 import fs from 'fs';
 import { pipeline } from 'stream/promises';
-import { IPFS_GATEWAY } from '../config/index.js';
 import { IpfsConfigurationError } from '../utils/customErrors.js';
 
+const IPFS_GATEWAY = process.env.IPFS_GATEWAY;
 export class IpfsService {
   static async saveFile(cid: string, outputPath: string) {
     if (!IPFS_GATEWAY) {
-      console.log('IPFS_GATEWAY:', IPFS_GATEWAY);
+      console.log('IPFS_GATEWAY:', process.env.IPFS_GATEWAY);
       throw new IpfsConfigurationError('process.env.IPFS_GATEWAY is not defined in environment variables');
     }
     // debugger;

From a13dd3af20842536928cd35b4743b3b6dad8506c Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 23:11:07 +0700
Subject: [PATCH 31/42] fix dev

---
 .../scripts/containerInitDev.sh               | 20 +++++++++++++++++++
 .../scripts/containerInitProd.sh              |  7 +++++++
 2 files changed, 27 insertions(+)
 create mode 100755 desci-media-isolated/scripts/containerInitDev.sh
 create mode 100755 desci-media-isolated/scripts/containerInitProd.sh

diff --git a/desci-media-isolated/scripts/containerInitDev.sh b/desci-media-isolated/scripts/containerInitDev.sh
new file mode 100755
index 000000000..904d86628
--- /dev/null
+++ b/desci-media-isolated/scripts/containerInitDev.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Check if .env file does not exist and auto generate it from .example.env
+if [ ! -f /usr/src/app/.env ]; then
+  cp /usr/src/app/.env.example /usr/src/app/.env
+fi
+
+# Ensure temp directories exist
+mkdir -p /usr/src/app/.temp/files /usr/src/app/.temp/thumbnails
+
+
+# Check if node_modules directory doesn't exist and run npm install if necessary
+if [ ! -d "/usr/src/app/node_modules" ]; then
+  echo "node_modules not found, running npm install..."
+  cd /usr/src/app
+  npm install
+fi
+
+# Execute the main container command
+exec "$@"
diff --git a/desci-media-isolated/scripts/containerInitProd.sh b/desci-media-isolated/scripts/containerInitProd.sh
new file mode 100755
index 000000000..d5711244a
--- /dev/null
+++ b/desci-media-isolated/scripts/containerInitProd.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# Ensure temp directories exist
+mkdir -p /usr/src/app/.temp/files /usr/src/app/.temp/thumbnails
+
+# Execute the main container command
+exec "$@"

From 03d539a2715b9778cc555b39d0f273812344a7c7 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 23:22:50 +0700
Subject: [PATCH 32/42] readd script to dockerfile

---
 desci-media-isolated/Dockerfile | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index b6c2f853f..98f4c3878 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -2,8 +2,6 @@
 FROM docker.io/node:20.9.0 as base
 
 # Install dumb-init so we can use it as PID 1
-# RUN apk update && apk add --no-cache dumb-init
-# RUN apk add --no-cache unoconv ffmpeg imagemagick curl
 
 RUN apt-get update && apt-get install -y dumb-init ghostscript unoconv ffmpeg imagemagick curl && \
     rm -rf /var/lib/apt/lists/*
@@ -11,9 +9,6 @@ RUN apt-get update && apt-get install -y dumb-init ghostscript unoconv ffmpeg im
 # Modify ImageMagick policy to allow PDF processing
 RUN sed -i '/<policy domain="coder" rights="none" pattern="PDF" \/>/c\<policy domain="Undefined" rights="read|write" pattern="PDF" \/>' /etc/ImageMagick-6/policy.xml
 
-# NPM Permission Fix
-# RUN mkdir -p /.npm
-# RUN chown -R 1001:0 /.npm
 
 # App Setup
 WORKDIR /usr/src/app
@@ -30,12 +25,10 @@ RUN --mount=type=cache,target=/usr/src/app/.npm \
 
 COPY . .
 
-# Auto create .env from .env.example if it doesn't exist
-RUN if [ ! -f .env ]; then cp .env.example .env; fi
-
 # Expose debugger port
 EXPOSE 9777
 
+ENTRYPOINT ["/usr/src/app/scripts/containerInitDev.sh"]
 CMD ["dumb-init", "npx", "tsx","watch", "--inspect=0.0.0.0:9777", "src/index.ts"]
 
 FROM base as production
@@ -48,7 +41,7 @@ ENV NODE_ENV production
 # 'node' user is created by the node image, prevent perm issues, run with reduced privs
 RUN mkdir -p /usr/src/app/dist && chown node:node /usr/src/app/dist
 USER node
-COPY --chown=node:node ./src/ .
+COPY --chown=node:node ./src/ ./src/
 USER root
 RUN chown -R node:node /usr/src/app
 USER node
@@ -56,5 +49,5 @@ RUN npm run build && \
     npm prune --production
 
 
-
+ENTRYPOINT ["/usr/src/app/scripts/containerInitProd.sh"]
 CMD ["dumb-init", "node", "dist/index.js"]

From 286b517094ce160fef9c8ed776551bafd8bd1ffe Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Mon, 26 Feb 2024 23:35:00 +0700
Subject: [PATCH 33/42] readd scripts

---
 desci-media-isolated/Dockerfile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index 98f4c3878..b3e598c5e 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -49,5 +49,6 @@ RUN npm run build && \
     npm prune --production
 
 
+COPY --chown=node:node ./scripts/containerInitProd.sh /usr/src/app/scripts/containerInitProd.sh
 ENTRYPOINT ["/usr/src/app/scripts/containerInitProd.sh"]
 CMD ["dumb-init", "node", "dist/index.js"]

From b80336a6eb2ceb523e1aeb874ce690eb6423083a Mon Sep 17 00:00:00 2001
From: shadrach-tayo <shadrachtemitayo@gmail.com>
Date: Mon, 26 Feb 2024 18:53:53 +0100
Subject: [PATCH 34/42] resolve node cover from radar api

---
 desci-server/src/controllers/communities/util.ts | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/desci-server/src/controllers/communities/util.ts b/desci-server/src/controllers/communities/util.ts
index 5e416eea2..657e6637e 100644
--- a/desci-server/src/controllers/communities/util.ts
+++ b/desci-server/src/controllers/communities/util.ts
@@ -15,13 +15,21 @@ export const resolveLatestNode = async (radar: Partial<NodeRadar>) => {
       uuid,
       isDeleted: false,
     },
+    select: {
+      id: true,
+      manifestUrl: true,
+      ownerId: true,
+      uuid: true,
+      title: true,
+      NodeCover: true,
+    },
   });
 
   if (!discovery) {
     logger.warn({ uuid }, 'uuid not found');
   }
 
-  const selectAttributes = ['manifestUrl', 'ownerId', 'title'];
+  const selectAttributes = ['manifestUrl', 'ownerId', 'title', 'NodeCover'];
   const node: Partial<Node & { versions: number }> = _.pick(discovery, selectAttributes);
   const publisedVersions =
     (await prisma.$queryRaw`SELECT * from "NodeVersion" where "nodeId" = ${discovery.id} AND "transactionId" IS NOT NULL ORDER BY "createdAt" DESC`) as NodeVersion[];

From dc4b56fb4524817d49acb3d5910e22deba4dd3f1 Mon Sep 17 00:00:00 2001
From: kadami <kadamidev@users.noreply.github.com>
Date: Tue, 27 Feb 2024 01:25:33 +0700
Subject: [PATCH 35/42] fix envs

---
 desci-media-isolated/Dockerfile                 |  2 +-
 desci-media-isolated/package-lock.json          | 12 ------------
 desci-media-isolated/package.json               |  5 ++---
 desci-media-isolated/src/index.ts               |  4 ++--
 desci-media-isolated/src/services/thumbnails.ts |  2 +-
 5 files changed, 6 insertions(+), 19 deletions(-)

diff --git a/desci-media-isolated/Dockerfile b/desci-media-isolated/Dockerfile
index b3e598c5e..49d31968c 100644
--- a/desci-media-isolated/Dockerfile
+++ b/desci-media-isolated/Dockerfile
@@ -29,7 +29,7 @@ COPY . .
 EXPOSE 9777
 
 ENTRYPOINT ["/usr/src/app/scripts/containerInitDev.sh"]
-CMD ["dumb-init", "npx", "tsx","watch", "--inspect=0.0.0.0:9777", "src/index.ts"]
+CMD ["dumb-init", "npx", "tsx","watch", "--env-file=.env", "--inspect=0.0.0.0:9777", "src/index.ts"]
 
 FROM base as production
 # Cache mounts for faster builds, prod env for better express perf
diff --git a/desci-media-isolated/package-lock.json b/desci-media-isolated/package-lock.json
index 1a195e658..551d9f750 100644
--- a/desci-media-isolated/package-lock.json
+++ b/desci-media-isolated/package-lock.json
@@ -10,7 +10,6 @@
       "license": "ISC",
       "dependencies": {
         "axios": "^1.6.7",
-        "dotenv": "^16.4.5",
         "express": "^4.18.2",
         "filepreview_ts": "^1.0.0",
         "helmet": "^7.1.0",
@@ -1177,17 +1176,6 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/dotenv": {
-      "version": "16.4.5",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
-      "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://dotenvx.com"
-      }
-    },
     "node_modules/dynamic-dedupe": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
diff --git a/desci-media-isolated/package.json b/desci-media-isolated/package.json
index 2648360dd..75d956db8 100644
--- a/desci-media-isolated/package.json
+++ b/desci-media-isolated/package.json
@@ -6,7 +6,7 @@
   "type": "module",
   "scripts": {
     "start": "node dist/index.js",
-    "dev": "tsx watch src/index.ts",
+    "dev": "tsx watch --env-file=.env src/index.ts",
     "build": "tsc"
   },
   "keywords": [],
@@ -17,8 +17,7 @@
     "express": "^4.18.2",
     "filepreview_ts": "^1.0.0",
     "helmet": "^7.1.0",
-    "tsx": "^4.7.1",
-    "dotenv": "^16.4.5"
+    "tsx": "^4.7.1"
   },
   "devDependencies": {
     "@types/express": "^4.17.21",
diff --git a/desci-media-isolated/src/index.ts b/desci-media-isolated/src/index.ts
index dfec27a1a..280ad2184 100644
--- a/desci-media-isolated/src/index.ts
+++ b/desci-media-isolated/src/index.ts
@@ -1,10 +1,10 @@
-import dotenv from 'dotenv';
+// import dotenv from 'dotenv';
 import express from 'express';
 import helmet from 'helmet';
 import { errorHandler } from './middleware/errorHandler.js';
 import routes from './routes/index.js';
 
-dotenv.config();
+// dotenv.config();
 
 const app = express();
 console.log('process.env.PORT:', process.env.PORT);
diff --git a/desci-media-isolated/src/services/thumbnails.ts b/desci-media-isolated/src/services/thumbnails.ts
index 91f5bc92f..2dae30b69 100644
--- a/desci-media-isolated/src/services/thumbnails.ts
+++ b/desci-media-isolated/src/services/thumbnails.ts
@@ -25,7 +25,7 @@ export class ThumbnailsService {
     const tempFilePath = path.join(BASE_TEMP_DIR, THUMBNAIL_FILES_DIR, `${cid + extension}`);
     const thumbnailPath = this.getThumbnailPath(cid);
     const exportPath = path.join(BASE_TEMP_DIR, THUMBNAIL_OUTPUT_DIR, thumbnailPath);
-
+    // debugger;
     await IpfsService.saveFile(cid, tempFilePath);
     try {
       await generateAsync(tempFilePath, exportPath, { ...THUMBNAIL_DIMENSIONS, height: heightPx });

From 9a96d5cb855f5801cf0c56df0d84b1085069c8d6 Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 11:38:26 -0700
Subject: [PATCH 36/42] kubeconfig edit

---
 .../kubernetes/deployment_dev.yaml              | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/desci-media-isolated/kubernetes/deployment_dev.yaml b/desci-media-isolated/kubernetes/deployment_dev.yaml
index 519e2f5c3..fcf2b0ff8 100644
--- a/desci-media-isolated/kubernetes/deployment_dev.yaml
+++ b/desci-media-isolated/kubernetes/deployment_dev.yaml
@@ -1,3 +1,18 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: desci-media-isolated-dev-service
+  labels:
+    App: DesciMediaIsolatedDev
+spec:
+  type: ClusterIP
+  selector:
+    App: DesciMediaIsolatedDev
+  ports:
+    - name: api
+      port: 7771
+      targetPort: 7771
+---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -30,7 +45,7 @@ spec:
             - name: PORT
               value: '7771'
             - name: IPFS_GATEWAY
-              value: 'http://host.docker.internal:5420/v1/ipfs'
+              value: 'https://ipfs.desci.com/ipfs'
           resources:
             limits:
               cpu: '0.5'

From de8e92d09820b4d7d824a5829b2b99febc9f8189 Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 11:39:22 -0700
Subject: [PATCH 37/42] Add env for desci-server

---
 desci-server/kubernetes/deployment_dev.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/desci-server/kubernetes/deployment_dev.yaml b/desci-server/kubernetes/deployment_dev.yaml
index ee25fc863..d937ed4b2 100644
--- a/desci-server/kubernetes/deployment_dev.yaml
+++ b/desci-server/kubernetes/deployment_dev.yaml
@@ -74,6 +74,8 @@ spec:
           export ESTUARY_API_URL={{ .Data.ESTUARY_API_URL }}
           export REPO_SERVER_URL={{ .Data.REPO_SERVER_URL }}
           export REPO_SERVICE_SECRET_KEY={{ .Data.REPO_SERVICE_SECRET_KEY }}
+          export ISOLATED_MEDIA_SERVER_URL={{ .Data.ISOLATED_MEDIA_SERVER_URL }}
+          export IPFS_READ_ONLY_GATEWAY_SERVER_URL={{ .Data.IPFS_READ_ONLY_GATEWAY_SERVER_URL }}
           export DEBUG_TEST=0;
           echo "appfinish";
           {{- end -}}

From aee80c536807f95019b87e0247f9a3a1950a3cf6 Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 11:51:42 -0700
Subject: [PATCH 38/42] trigger GH action

---
 .github/workflows/build-isolated-media-server.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build-isolated-media-server.yaml b/.github/workflows/build-isolated-media-server.yaml
index 76e49f398..94a40df05 100644
--- a/.github/workflows/build-isolated-media-server.yaml
+++ b/.github/workflows/build-isolated-media-server.yaml
@@ -11,7 +11,7 @@ on:
 
       # TODO: add demo env
 
-name: Build desci-media-isolated
+name: Build desci-isolated-media-server
 
 # https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
 env:

From 966b7a2c713fc720add386e8ec1f33971f8cc09a Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 11:54:36 -0700
Subject: [PATCH 39/42] Enable manual trigger

---
 .github/workflows/build-isolated-media-server.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/build-isolated-media-server.yaml b/.github/workflows/build-isolated-media-server.yaml
index 94a40df05..37c1b871e 100644
--- a/.github/workflows/build-isolated-media-server.yaml
+++ b/.github/workflows/build-isolated-media-server.yaml
@@ -1,5 +1,6 @@
 # build.yml
 on:
+  workflow_dispatch: # This line enables manual triggers
   push:
     paths:
       - .github/workflows/**

From a6ca005c7fbfc2673ace27edb975665f3cc235c5 Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 13:05:04 -0700
Subject: [PATCH 40/42] fix port name

---
 desci-media-isolated/kubernetes/deployment_dev.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/desci-media-isolated/kubernetes/deployment_dev.yaml b/desci-media-isolated/kubernetes/deployment_dev.yaml
index fcf2b0ff8..812ea016b 100644
--- a/desci-media-isolated/kubernetes/deployment_dev.yaml
+++ b/desci-media-isolated/kubernetes/deployment_dev.yaml
@@ -40,7 +40,7 @@ spec:
           name: desci-media-isolated-dev
           ports:
             - containerPort: 7771
-              name: media_isolated_dev
+              name: api
           env:
             - name: PORT
               value: '7771'

From efba3f89655ed30ec7359885f1993766f35b2979 Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 13:28:48 -0700
Subject: [PATCH 41/42] dont kill server

---
 desci-server/src/controllers/nodes/thumbnails.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/desci-server/src/controllers/nodes/thumbnails.ts b/desci-server/src/controllers/nodes/thumbnails.ts
index b3969d385..b9f2afe52 100644
--- a/desci-server/src/controllers/nodes/thumbnails.ts
+++ b/desci-server/src/controllers/nodes/thumbnails.ts
@@ -39,7 +39,7 @@ export const thumbnails = async (
     module: 'NODES::Thumbnails',
     uuid,
     manifestCid,
-    userId: user.id,
+    userId: user?.id,
   });
   logger.trace({ fn: 'Retrieving thumbnails' });
 

From e0bd39b997bde00d7f2120b6340e8defc3538db1 Mon Sep 17 00:00:00 2001
From: Sina Iman <sina.iman@gmail.com>
Date: Mon, 26 Feb 2024 14:18:12 -0700
Subject: [PATCH 42/42] media config for prod

---
 .../build-isolated-media-server.yaml          | 14 +++++-----
 .../kubernetes/deployment.yaml                | 27 ++++++++++++++-----
 desci-server/kubernetes/deployment.yaml       |  2 ++
 3 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/build-isolated-media-server.yaml b/.github/workflows/build-isolated-media-server.yaml
index 37c1b871e..86669ae83 100644
--- a/.github/workflows/build-isolated-media-server.yaml
+++ b/.github/workflows/build-isolated-media-server.yaml
@@ -71,7 +71,7 @@ jobs:
         run: |
           # Build and tag the image
           docker build \
-            --target production -t $CONTAINER_IMAGE:production \
+            --target production -t $CONTAINER_IMAGE-prod:production \
             --target production -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE \
              ./desci-media-isolated
 
@@ -93,10 +93,10 @@ jobs:
         run: |
           # Push image to AWS ECR
           aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
-          docker tag $CONTAINER_IMAGE:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:${{ github.sha }}
-          docker tag $CONTAINER_IMAGE:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:production
-          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:${{ github.sha }}
-          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:production
+          docker tag $CONTAINER_IMAGE:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-prod:${{ github.sha }}
+          docker tag $CONTAINER_IMAGE:production $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-prod:production
+          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-prod:${{ github.sha }}
+          docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-prod:production
 
       - name: Deploy to EKS (DEV)
         # uses: steebchen/kubectl@v2.0.0
@@ -107,7 +107,7 @@ jobs:
       - name: Deploy to EKS (PROD)
         if: github.ref == 'refs/heads/main'
         run: | # defaults to latest kubectl binary version
-          kubectl set image deployment/desci-media-isolated desci-media-isolated=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE:${{ github.sha }} --record
+          kubectl set image deployment/desci-media-isolated-prod desci-media-isolated-prod=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$CONTAINER_IMAGE-prod:${{ github.sha }} --record
 
       - name: Verify EKS Deployment (DEV)
         if: github.ref == 'refs/heads/develop'
@@ -117,4 +117,4 @@ jobs:
       - name: Verify EKS Deployment (PROD)
         if: github.ref == 'refs/heads/main'
         run: |
-          kubectl rollout status deployment/desci-media-isolated
+          kubectl rollout status deployment/desci-media-isolated-prod
diff --git a/desci-media-isolated/kubernetes/deployment.yaml b/desci-media-isolated/kubernetes/deployment.yaml
index 32c08e2b9..9d14c8e8c 100644
--- a/desci-media-isolated/kubernetes/deployment.yaml
+++ b/desci-media-isolated/kubernetes/deployment.yaml
@@ -1,15 +1,30 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: desci-media-isolated-prod-service
+  labels:
+    App: DesciMediaIsolatedProd
+spec:
+  type: ClusterIP
+  selector:
+    App: DesciMediaIsolatedProd
+  ports:
+    - name: api
+      port: 7771
+      targetPort: 7771
+---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: desci-media-isolated
+  name: desci-media-isolated-prod
   labels:
-    App: DesciMediaIsolated
+    App: DesciMediaIsolatedProd
 spec:
   replicas: 1
   revisionHistoryLimit: 2
   selector:
     matchLabels:
-      App: DesciMediaIsolated
+      App: DesciMediaIsolatedProd
   strategy:
     rollingUpdate:
       maxSurge: 25%
@@ -21,11 +36,11 @@ spec:
         App: DesciMediaIsolated
     spec:
       containers:
-        - image: 523044037273.dkr.ecr.us-east-2.amazonaws.com/desci-media-isolated:production
-          name: desci-media-isolated
+        - image: 523044037273.dkr.ecr.us-east-2.amazonaws.com/desci-media-isolated-prod:production
+          name: desci-media-isolated-prod
           ports:
             - containerPort: 7771
-              name: media_isolated
+              name: api
           env:
             - name: PORT
               value: '7771'
diff --git a/desci-server/kubernetes/deployment.yaml b/desci-server/kubernetes/deployment.yaml
index e4a9cdddf..7fbc7a293 100755
--- a/desci-server/kubernetes/deployment.yaml
+++ b/desci-server/kubernetes/deployment.yaml
@@ -74,6 +74,8 @@ spec:
           export ESTUARY_API_URL={{ .Data.ESTUARY_API_URL }}
           export REPO_SERVER_URL={{ .Data.REPO_SERVER_URL }}
           export REPO_SERVICE_SECRET_KEY={{ .Data.REPO_SERVICE_SECRET_KEY }}
+          export ISOLATED_MEDIA_SERVER_URL={{ .Data.ISOLATED_MEDIA_SERVER_URL }}
+          export IPFS_READ_ONLY_GATEWAY_SERVER_URL={{ .Data.IPFS_READ_ONLY_GATEWAY_SERVER_URL }}
           export DEBUG_TEST=0;
           echo "appfinish";
           {{- end -}}