diff --git a/.eslintrc.json b/.eslintrc.json index 6d372097..77ceedb5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,19 +1,19 @@ { - "root": true, - "extends": ["plugin:@typescript-eslint/recommended"], - "parser": "@typescript-eslint/parser", - "plugins": ["json"], - "rules": { - "no-const-assign": "error", - "camelcase": "off", - "no-extra-semi": "error" - }, - "parserOptions": { - "allowImportExportEverywhere": true, - "ecmaVersion": 2020, - "ecmaFeatures": { - "impliedStrict": true - }, - "sourceType": "module" - } + "root": true, + "extends": ["plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "plugins": ["json"], + "rules": { + "no-const-assign": "error", + "camelcase": "off", + "no-extra-semi": "error" + }, + "parserOptions": { + "allowImportExportEverywhere": true, + "ecmaVersion": 2020, + "ecmaFeatures": { + "impliedStrict": true + }, + "sourceType": "module" + } } diff --git a/.gitignore b/.gitignore index 66a78bef..88cce807 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,14 @@ # webstorm .idea -# Wrangler +# Wrangler & env +.env .wrangler .dev.vars # temp -src/routes/check.ts +scripts/ # Logs diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..06458cea --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm run typecheck && pnpm run lint && pnpm run prettier:check diff --git a/.prettierrc b/.prettierrc index d2b16425..0b03816f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,10 @@ { - "bracketSameLine": true, - "singleQuote": false, - "trailingComma": "es5", - "endOfLine": "lf", - "tabWidth": 4, - "plugins": [] + "bracketSameLine": true, + "singleQuote": false, + "trailingComma": "es5", + "endOfLine": "lf", + "tabWidth": 4, + "semi": false, + "useTabs": true, + "plugins": [] } diff --git a/README.md b/README.md index 209a547e..23f07b31 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ![Banner] -![Quality] ![API Response] ![CDN Response] +![Quality] -Source code for the API powering [**wanderer.moe**](https://wanderer.moe) — using **Cloudflare Workers** and **Hono** with **R2 Storage** for the CDN, and **Planetscale** for the Database. +Source code for the API powering [**wanderer.moe**](https://wanderer.moe) — using **Cloudflare Workers** and **Hono** with **R2 Storage** for the CDN, **Turso** and **Drizzle** for the Database. @@ -40,8 +40,6 @@ You will need to setup environment variables for the Discord Bot Token for `/con [Banner]: https://files.catbox.moe/qa3eus.svg [API Status]: https://status.wanderer.moe/history/api [CDN Status]: https://status.wanderer.moe/history/cdn -[API Response]: https://img.shields.io/endpoint?label=API%20Response&style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fwanderer-moe%2Fstatus%2FHEAD%2Fapi%2Fapi%2Fresponse-time.json -[CDN Response]: https://img.shields.io/endpoint?label=CDN%20Response&style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fwanderer-moe%2Fstatus%2FHEAD%2Fapi%2Fcdn%2Fresponse-time.json [Quality]: https://img.shields.io/codefactor/grade/github/wanderer-moe/api?label=quality&style=for-the-badge [Cloudflare API Token]: https://dash.cloudflare.com/profile/api-tokens [Dromzeh]: https://github.com/dromzeh diff --git a/drizzle.config.ts b/drizzle.config.ts index e14981af..f41fcd66 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,16 +1,16 @@ -import "dotenv/config"; - -import type { Config } from "drizzle-kit"; +import "dotenv/config" +const { TURSO_DATABASE_AUTH_TOKEN, TURSO_DATABASE_URL } = process.env +import type { Config } from "drizzle-kit" export default { - out: "./src/db/migrations", - schema: "./src/db/schema.ts", - breakpoints: true, - driver: "mysql2", - dbCredentials: { - host: process.env.DATABASE_HOST || "", - user: process.env.DATABASE_USERNAME || "", - password: process.env.DATABASE_PASSWORD || "", - database: "planetscale", - }, -} satisfies Config; + out: "./src/v2/db/migrations", + schema: "./src/v2/db/schema.ts", + driver: "turso", + breakpoints: true, + strict: true, + verbose: true, + dbCredentials: { + url: TURSO_DATABASE_URL as string, + authToken: TURSO_DATABASE_AUTH_TOKEN as string, + }, +} satisfies Config diff --git a/package.json b/package.json index 88ea8413..0d56a2c3 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,39 @@ { - "name": "wanderer-moe-api", - "version": "1.0.1b", - "scripts": { - "prettier:fmt": "prettier --write .", - "dev": "wrangler dev --remote", - "publish": "wrangler publish --minify", - "lint": "eslint . --ext .ts", - "prettier:check": "prettier --check .", - "typecheck": "tsc --noEmit", - "prisma:generate": "npx prisma generate --accelerate", - "prisma:push": "npx prisma db push" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20230807.0", - "@types/node": "^20.4.8", - "eslint": "^8.46.0", - "eslint-config-google": "^0.14.0", - "eslint-plugin-json": "^3.1.0", - "typescript": "^5.1.6", - "wrangler": "3.4.0" - }, - "private": true, - "dependencies": { - "@aws-sdk/client-s3": "^3.386.0", - "@lucia-auth/adapter-mysql": "^2.0.0", - "@lucia-auth/adapter-prisma": "^3.0.1", - "@planetscale/database": "^1.10.0", - "@prisma/client": "^5.1.1", - "@typescript-eslint/eslint-plugin": "^6.3.0", - "dotenv": "^16.3.1", - "drizzle-kit": "^0.19.12", - "drizzle-orm": "^0.28.1", - "hono": "^3.4.1", - "lucia": "^2.2.0", - "mysql2": "^3.6.0", - "prettier": "^3.0.1", - "render2": "^1.2.1", - "resend": "^0.17.2" - } + "name": "wanderer-moe-api", + "version": "1.0.1b", + "scripts": { + "prettier:fmt": "prettier --write .", + "dev": "wrangler dev --remote", + "publish": "wrangler publish --minify", + "lint": "eslint . --ext .ts", + "prettier:check": "prettier --check .", + "typecheck": "tsc --noEmit", + "drizzle:generate": "drizzle-kit generate:sqlite", + "drizzle:push": "drizzle-kit push:sqlite" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20230821.0", + "@types/node": "^20.5.8", + "dotenv": "^16.3.1", + "drizzle-kit": "^0.19.13", + "eslint": "^8.48.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-json": "^3.1.0", + "husky": "^8.0.3", + "tsx": "^3.12.8", + "typescript": "^5.2.2", + "wrangler": "3.6.0" + }, + "private": true, + "dependencies": { + "@libsql/client": "^0.3.2", + "@lucia-auth/adapter-sqlite": "^2.0.0", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "drizzle-orm": "^0.28.5", + "hono": "^3.5.6", + "lucia": "^2.4.1", + "mysql2": "^3.6.0", + "prettier": "^3.0.3", + "resend": "^1.0.0" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ee64451..1e8264a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,74 +5,68 @@ settings: excludeLinksFromLockfile: false dependencies: - "@aws-sdk/client-s3": - specifier: ^3.386.0 - version: 3.386.0 - "@lucia-auth/adapter-mysql": + "@libsql/client": + specifier: ^0.3.2 + version: 0.3.2 + "@lucia-auth/adapter-sqlite": specifier: ^2.0.0 - version: 2.0.0(@planetscale/database@1.10.0)(lucia@2.2.0)(mysql2@3.6.0) - "@lucia-auth/adapter-prisma": - specifier: ^3.0.1 - version: 3.0.1(@prisma/client@5.1.1)(lucia@2.2.0) - "@planetscale/database": - specifier: ^1.10.0 - version: 1.10.0 - "@prisma/client": - specifier: ^5.1.1 - version: 5.1.1 + version: 2.0.0(@libsql/client@0.3.2)(lucia@2.4.1) "@typescript-eslint/eslint-plugin": - specifier: ^6.3.0 - version: 6.3.0(@typescript-eslint/parser@6.3.0)(eslint@8.46.0)(typescript@5.1.6) - dotenv: - specifier: ^16.3.1 - version: 16.3.1 - drizzle-kit: - specifier: ^0.19.12 - version: 0.19.12 + specifier: ^6.5.0 + version: 6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2) drizzle-orm: - specifier: ^0.28.1 - version: 0.28.1(@cloudflare/workers-types@4.20230807.0)(@planetscale/database@1.10.0)(mysql2@3.6.0) + specifier: ^0.28.5 + version: 0.28.5(@cloudflare/workers-types@4.20230821.0)(@libsql/client@0.3.2)(mysql2@3.6.0) hono: - specifier: ^3.4.1 - version: 3.4.1 + specifier: ^3.5.6 + version: 3.5.6 lucia: - specifier: ^2.2.0 - version: 2.2.0 + specifier: ^2.4.1 + version: 2.4.1 mysql2: specifier: ^3.6.0 version: 3.6.0 prettier: - specifier: ^3.0.1 - version: 3.0.1 - render2: - specifier: ^1.2.1 - version: 1.2.1 + specifier: ^3.0.3 + version: 3.0.3 resend: - specifier: ^0.17.2 - version: 0.17.2 + specifier: ^1.0.0 + version: 1.0.0 devDependencies: "@cloudflare/workers-types": - specifier: ^4.20230807.0 - version: 4.20230807.0 + specifier: ^4.20230821.0 + version: 4.20230821.0 "@types/node": - specifier: ^20.4.8 - version: 20.4.8 + specifier: ^20.5.8 + version: 20.5.8 + dotenv: + specifier: ^16.3.1 + version: 16.3.1 + drizzle-kit: + specifier: ^0.19.13 + version: 0.19.13 eslint: - specifier: ^8.46.0 - version: 8.46.0 + specifier: ^8.48.0 + version: 8.48.0 eslint-config-google: specifier: ^0.14.0 - version: 0.14.0(eslint@8.46.0) + version: 0.14.0(eslint@8.48.0) eslint-plugin-json: specifier: ^3.1.0 version: 3.1.0 + husky: + specifier: ^8.0.3 + version: 8.0.3 + tsx: + specifier: ^3.12.8 + version: 3.12.8 typescript: - specifier: ^5.1.6 - version: 5.1.6 + specifier: ^5.2.2 + version: 5.2.2 wrangler: - specifier: 3.4.0 - version: 3.4.0 + specifier: 3.6.0 + version: 3.6.0 packages: /@aashutoshrathi/word-wrap@1.2.6: @@ -82,2343 +76,915 @@ packages: } engines: { node: ">=0.10.0" } - /@aws-crypto/crc32@3.0.0: + /@cloudflare/kv-asset-handler@0.2.0: resolution: { - integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==, + integrity: sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==, } dependencies: - "@aws-crypto/util": 3.0.0 - "@aws-sdk/types": 3.378.0 - tslib: 1.14.1 - dev: false + mime: 3.0.0 + dev: true - /@aws-crypto/crc32c@3.0.0: + /@cloudflare/workerd-darwin-64@1.20230814.1: resolution: { - integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==, + integrity: sha512-aQUO7q7qXl+SVtOiMMlVKLNOSeL6GX43RKeflwzsD74dGgyHPiSfw5KCvXhkVbyN7u+yYF6HyFdaIvHLfn5jyA==, } - dependencies: - "@aws-crypto/util": 3.0.0 - "@aws-sdk/types": 3.378.0 - tslib: 1.14.1 - dev: false + engines: { node: ">=16" } + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /@aws-crypto/ie11-detection@3.0.0: + /@cloudflare/workerd-darwin-arm64@1.20230814.1: resolution: { - integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==, + integrity: sha512-U2mcgi+AiuI/4EY5Wk/GmygiNoCNw/V2mcHmxESqe4r6XbJYOzBdEsjnqJ05rqd0JlEM8m64jRtE6/qBnQHygg==, } - dependencies: - tslib: 1.14.1 - dev: false + engines: { node: ">=16" } + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /@aws-crypto/sha1-browser@3.0.0: + /@cloudflare/workerd-linux-64@1.20230814.1: resolution: { - integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==, + integrity: sha512-Q4kITXLTCuG2i2Z01fbb5AjVRRIf3+lS4ZVsFbTbIwtcOOG4Ozcw7ee7tKsFES7hFqR4Eg9gMG4/aS0mmi+L2g==, } - dependencies: - "@aws-crypto/ie11-detection": 3.0.0 - "@aws-crypto/supports-web-crypto": 3.0.0 - "@aws-crypto/util": 3.0.0 - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-locate-window": 3.310.0 - "@aws-sdk/util-utf8-browser": 3.259.0 - tslib: 1.14.1 - dev: false + engines: { node: ">=16" } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@aws-crypto/sha256-browser@3.0.0: + /@cloudflare/workerd-linux-arm64@1.20230814.1: resolution: { - integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==, + integrity: sha512-BX5SaksXw+pkREVw3Rw2eSNXplqZw+14CcwW/5x/4oq/C6yn5qCvKxJfM7pukJGMI4wkJPOYops7B3g27FB/HA==, } - dependencies: - "@aws-crypto/ie11-detection": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-crypto/supports-web-crypto": 3.0.0 - "@aws-crypto/util": 3.0.0 - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-locate-window": 3.310.0 - "@aws-sdk/util-utf8-browser": 3.259.0 - tslib: 1.14.1 - dev: false + engines: { node: ">=16" } + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@aws-crypto/sha256-js@3.0.0: + /@cloudflare/workerd-windows-64@1.20230814.1: resolution: { - integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==, + integrity: sha512-GWHqfyhsG/1wm2W8afkYX3q3fWXUWWD8NGtHfAs6ZVTHdW3mmYyMhKR0lc6ptBwz5i5aXRlP2S+CxxxwwDbKpw==, } - dependencies: - "@aws-crypto/util": 3.0.0 - "@aws-sdk/types": 3.378.0 - tslib: 1.14.1 - dev: false + engines: { node: ">=16" } + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@aws-crypto/supports-web-crypto@3.0.0: + /@cloudflare/workers-types@4.20230821.0: resolution: { - integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==, + integrity: sha512-lVQSyr5E4CEkQw7WIdsrMTj+kHjsm28mJ0B5AhNFByKR+16KTFsU/RW/nGLKHHW2jxT5lvYI+HjNQMzC9QR8Ng==, } - dependencies: - tslib: 1.14.1 - dev: false - /@aws-crypto/util@3.0.0: + /@drizzle-team/studio@0.0.5: resolution: { - integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==, + integrity: sha512-ps5qF0tMxWRVu+V5gvCRrQNqlY92aTnIKdq27gm9LZMSdaKYZt6AVvSK1dlUMzs6Rt0Jm80b+eWct6xShBKhIw==, } - dependencies: - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-utf8-browser": 3.259.0 - tslib: 1.14.1 - dev: false - - /@aws-sdk/client-s3@3.386.0: - resolution: - { - integrity: sha512-c9c7bm3fsZwvl8pffl6OXUtxlLD1FVr8GM+0Pu+J2R76B5NkDsIvvVUa2fTcNjc0jVkvB6EkvUE4WfOCdKqEDQ==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-crypto/sha1-browser": 3.0.0 - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/client-sts": 3.386.0 - "@aws-sdk/credential-provider-node": 3.386.0 - "@aws-sdk/middleware-bucket-endpoint": 3.378.0 - "@aws-sdk/middleware-expect-continue": 3.378.0 - "@aws-sdk/middleware-flexible-checksums": 3.383.0 - "@aws-sdk/middleware-host-header": 3.379.1 - "@aws-sdk/middleware-location-constraint": 3.379.1 - "@aws-sdk/middleware-logger": 3.378.0 - "@aws-sdk/middleware-recursion-detection": 3.378.0 - "@aws-sdk/middleware-sdk-s3": 3.379.1 - "@aws-sdk/middleware-signing": 3.379.1 - "@aws-sdk/middleware-ssec": 3.378.0 - "@aws-sdk/middleware-user-agent": 3.386.0 - "@aws-sdk/signature-v4-multi-region": 3.378.0 - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-endpoints": 3.386.0 - "@aws-sdk/util-user-agent-browser": 3.378.0 - "@aws-sdk/util-user-agent-node": 3.378.0 - "@aws-sdk/xml-builder": 3.310.0 - "@smithy/config-resolver": 2.0.2 - "@smithy/eventstream-serde-browser": 2.0.2 - "@smithy/eventstream-serde-config-resolver": 2.0.2 - "@smithy/eventstream-serde-node": 2.0.2 - "@smithy/fetch-http-handler": 2.0.2 - "@smithy/hash-blob-browser": 2.0.2 - "@smithy/hash-node": 2.0.2 - "@smithy/hash-stream-node": 2.0.2 - "@smithy/invalid-dependency": 2.0.2 - "@smithy/md5-js": 2.0.2 - "@smithy/middleware-content-length": 2.0.2 - "@smithy/middleware-endpoint": 2.0.2 - "@smithy/middleware-retry": 2.0.2 - "@smithy/middleware-serde": 2.0.2 - "@smithy/middleware-stack": 2.0.0 - "@smithy/node-config-provider": 2.0.2 - "@smithy/node-http-handler": 2.0.2 - "@smithy/protocol-http": 2.0.2 - "@smithy/smithy-client": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/url-parser": 2.0.2 - "@smithy/util-base64": 2.0.0 - "@smithy/util-body-length-browser": 2.0.0 - "@smithy/util-body-length-node": 2.0.0 - "@smithy/util-defaults-mode-browser": 2.0.2 - "@smithy/util-defaults-mode-node": 2.0.2 - "@smithy/util-retry": 2.0.0 - "@smithy/util-stream": 2.0.2 - "@smithy/util-utf8": 2.0.0 - "@smithy/util-waiter": 2.0.2 - fast-xml-parser: 4.2.5 - tslib: 2.6.1 - transitivePeerDependencies: - - "@aws-sdk/signature-v4-crt" - - aws-crt - dev: false - - /@aws-sdk/client-sso@3.386.0: - resolution: - { - integrity: sha512-UBgRo0q/XSle9CHCpoFhzq9wCfdc4kJw40cpPoHJPbb5m3JMcDu0mq4LTY7Nwnoy9jBThfRVYf3EF2BMF8fo6A==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/middleware-host-header": 3.379.1 - "@aws-sdk/middleware-logger": 3.378.0 - "@aws-sdk/middleware-recursion-detection": 3.378.0 - "@aws-sdk/middleware-user-agent": 3.386.0 - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-endpoints": 3.386.0 - "@aws-sdk/util-user-agent-browser": 3.378.0 - "@aws-sdk/util-user-agent-node": 3.378.0 - "@smithy/config-resolver": 2.0.2 - "@smithy/fetch-http-handler": 2.0.2 - "@smithy/hash-node": 2.0.2 - "@smithy/invalid-dependency": 2.0.2 - "@smithy/middleware-content-length": 2.0.2 - "@smithy/middleware-endpoint": 2.0.2 - "@smithy/middleware-retry": 2.0.2 - "@smithy/middleware-serde": 2.0.2 - "@smithy/middleware-stack": 2.0.0 - "@smithy/node-config-provider": 2.0.2 - "@smithy/node-http-handler": 2.0.2 - "@smithy/protocol-http": 2.0.2 - "@smithy/smithy-client": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/url-parser": 2.0.2 - "@smithy/util-base64": 2.0.0 - "@smithy/util-body-length-browser": 2.0.0 - "@smithy/util-body-length-node": 2.0.0 - "@smithy/util-defaults-mode-browser": 2.0.2 - "@smithy/util-defaults-mode-node": 2.0.2 - "@smithy/util-retry": 2.0.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 - transitivePeerDependencies: - - aws-crt - dev: false - - /@aws-sdk/client-sts@3.386.0: - resolution: - { - integrity: sha512-VK+tdZaI971IDP/1WqpYNorf+Q5uoJTqcp3y/bQY4Hr5bf8N3aztDrX7a2GaA07zgSdUa82VkScMKzW31jhsxw==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/credential-provider-node": 3.386.0 - "@aws-sdk/middleware-host-header": 3.379.1 - "@aws-sdk/middleware-logger": 3.378.0 - "@aws-sdk/middleware-recursion-detection": 3.378.0 - "@aws-sdk/middleware-sdk-sts": 3.379.1 - "@aws-sdk/middleware-signing": 3.379.1 - "@aws-sdk/middleware-user-agent": 3.386.0 - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-endpoints": 3.386.0 - "@aws-sdk/util-user-agent-browser": 3.378.0 - "@aws-sdk/util-user-agent-node": 3.378.0 - "@smithy/config-resolver": 2.0.2 - "@smithy/fetch-http-handler": 2.0.2 - "@smithy/hash-node": 2.0.2 - "@smithy/invalid-dependency": 2.0.2 - "@smithy/middleware-content-length": 2.0.2 - "@smithy/middleware-endpoint": 2.0.2 - "@smithy/middleware-retry": 2.0.2 - "@smithy/middleware-serde": 2.0.2 - "@smithy/middleware-stack": 2.0.0 - "@smithy/node-config-provider": 2.0.2 - "@smithy/node-http-handler": 2.0.2 - "@smithy/protocol-http": 2.0.2 - "@smithy/smithy-client": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/url-parser": 2.0.2 - "@smithy/util-base64": 2.0.0 - "@smithy/util-body-length-browser": 2.0.0 - "@smithy/util-body-length-node": 2.0.0 - "@smithy/util-defaults-mode-browser": 2.0.2 - "@smithy/util-defaults-mode-node": 2.0.2 - "@smithy/util-retry": 2.0.0 - "@smithy/util-utf8": 2.0.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.1 - transitivePeerDependencies: - - aws-crt - dev: false + dev: true - /@aws-sdk/credential-provider-env@3.378.0: + /@esbuild-kit/cjs-loader@2.4.2: resolution: { - integrity: sha512-B2OVdO9kBClDwGgWTBLAQwFV8qYTYGyVujg++1FZFSFMt8ORFdZ5fNpErvJtiSjYiOOQMzyBeSNhKyYNXCiJjQ==, + integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==, } - engines: { node: ">=14.0.0" } dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/property-provider": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + "@esbuild-kit/core-utils": 3.2.2 + get-tsconfig: 4.7.0 + dev: true - /@aws-sdk/credential-provider-ini@3.386.0: + /@esbuild-kit/core-utils@3.2.2: resolution: { - integrity: sha512-TDeOFwCq6Ri58OP4CvVIAbc+iJPld7TpOFB+YYQ+q0ut+92zaVTNrNoWZkygPCXKmeHUGZcwC9Mjt/4L+fBAkg==, + integrity: sha512-Ub6LaRaAgF80dTSzUdXpFLM1pVDdmEVB9qb5iAzSpyDlX/mfJTFGOnZ516O05p5uWWteNviMKi4PAyEuRxI5gA==, } - engines: { node: ">=14.0.0" } dependencies: - "@aws-sdk/credential-provider-env": 3.378.0 - "@aws-sdk/credential-provider-process": 3.378.0 - "@aws-sdk/credential-provider-sso": 3.386.0 - "@aws-sdk/credential-provider-web-identity": 3.378.0 - "@aws-sdk/types": 3.378.0 - "@smithy/credential-provider-imds": 2.0.2 - "@smithy/property-provider": 2.0.2 - "@smithy/shared-ini-file-loader": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - transitivePeerDependencies: - - aws-crt - dev: false + esbuild: 0.18.20 + source-map-support: 0.5.21 + dev: true - /@aws-sdk/credential-provider-node@3.386.0: + /@esbuild-kit/esm-loader@2.5.5: resolution: { - integrity: sha512-dvYSN+T1B96TqiZE5tBV8bHCNU6TKq5meeI06AdClHz3VPvPKDN/EL0undXpl/GnlvuhKUftmVwdUBefG3otZA==, + integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==, } - engines: { node: ">=14.0.0" } dependencies: - "@aws-sdk/credential-provider-env": 3.378.0 - "@aws-sdk/credential-provider-ini": 3.386.0 - "@aws-sdk/credential-provider-process": 3.378.0 - "@aws-sdk/credential-provider-sso": 3.386.0 - "@aws-sdk/credential-provider-web-identity": 3.378.0 - "@aws-sdk/types": 3.378.0 - "@smithy/credential-provider-imds": 2.0.2 - "@smithy/property-provider": 2.0.2 - "@smithy/shared-ini-file-loader": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - transitivePeerDependencies: - - aws-crt - dev: false + "@esbuild-kit/core-utils": 3.2.2 + get-tsconfig: 4.7.0 + dev: true - /@aws-sdk/credential-provider-process@3.378.0: + /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.16.3): resolution: { - integrity: sha512-KFTIy7u+wXj3eDua4rgS0tODzMnXtXhAm1RxzCW9FL5JLBBrd82ymCj1Dp72217Sw5Do6NjCnDTTNkCHZMA77w==, + integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==, } - engines: { node: ">=14.0.0" } + peerDependencies: + esbuild: "*" dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/property-provider": 2.0.2 - "@smithy/shared-ini-file-loader": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + esbuild: 0.16.3 + dev: true - /@aws-sdk/credential-provider-sso@3.386.0: + /@esbuild-plugins/node-modules-polyfill@0.1.4(esbuild@0.16.3): resolution: { - integrity: sha512-7PvtrxMFpphQP8D5Zu5WpqZ/p7FBWiOQ2UQhzGKEry/9JIyTKiIZWsCu7OIWcfEx8RqSnLu3pDydc6HSTNs+lw==, + integrity: sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg==, } - engines: { node: ">=14.0.0" } + peerDependencies: + esbuild: "*" dependencies: - "@aws-sdk/client-sso": 3.386.0 - "@aws-sdk/token-providers": 3.386.0 - "@aws-sdk/types": 3.378.0 - "@smithy/property-provider": 2.0.2 - "@smithy/shared-ini-file-loader": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - transitivePeerDependencies: - - aws-crt - dev: false + esbuild: 0.16.3 + escape-string-regexp: 4.0.0 + rollup-plugin-node-polyfills: 0.2.1 + dev: true - /@aws-sdk/credential-provider-web-identity@3.378.0: + /@esbuild/android-arm64@0.16.3: resolution: { - integrity: sha512-GWjydOszhc4xDF8xuPtBvboglXQr0gwCW1oHAvmLcOT38+Hd6qnKywnMSeoXYRPgoKfF9TkWQgW1jxplzCG0UA==, + integrity: sha512-RolFVeinkeraDvN/OoRf1F/lP0KUfGNb5jxy/vkIMeRRChkrX/HTYN6TYZosRJs3a1+8wqpxAo5PI5hFmxyPRg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/property-provider": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-bucket-endpoint@3.378.0: + /@esbuild/android-arm64@0.18.20: resolution: { - integrity: sha512-3o+AYU6JWUsPM49bWglCUOgNvySiHkbIma0J6F9a68e30vEDD0FUQtKzyHPZkF7iYDyesEl166gYjwVNAmASzw==, + integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-arn-parser": 3.310.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/util-config-provider": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-expect-continue@3.378.0: + /@esbuild/android-arm@0.16.3: resolution: { - integrity: sha512-8maaNQvza3/IGDbIyVQkUbGlo+Oc6SY1gVG50UMcTUX8nwZrD1/ko+ft+pd2EDb2n+0JritoDj4bjr6pdesNBg==, + integrity: sha512-mueuEoh+s1eRbSJqq9KNBQwI4QhQV6sRXIfTyLXSHGMpyew61rOK4qY21uKbXl1iBoMb0AdL1deWFCQVlN2qHA==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-flexible-checksums@3.383.0: + /@esbuild/android-arm@0.18.20: resolution: { - integrity: sha512-RxIuby6Nz4pgKqNtt9Rdr2gWtOLrl9shZrteVuPh42n/dSOtCIhsG0fffKqy247I6oUghicoVJK9v0mxfINu/w==, + integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-crypto/crc32": 3.0.0 - "@aws-crypto/crc32c": 3.0.0 - "@aws-sdk/types": 3.378.0 - "@smithy/is-array-buffer": 2.0.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-host-header@3.379.1: + /@esbuild/android-x64@0.16.3: resolution: { - integrity: sha512-LI4KpAFWNWVr2aH2vRVblr0Y8tvDz23lj8LOmbDmCrzd5M21nxuocI/8nEAQj55LiTIf9Zs+dHCdsyegnFXdrA==, + integrity: sha512-SFpTUcIT1bIJuCCBMCQWq1bL2gPTjWoLZdjmIhjdcQHaUfV41OQfho6Ici5uvvkMmZRXIUGpM3GxysP/EU7ifQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-location-constraint@3.379.1: + /@esbuild/android-x64@0.18.20: resolution: { - integrity: sha512-+bmy8DjX9jtqJk8WiDaHoP9M5ZcqjHSJf4mkv8IUZ/FNTUl9j6Dll//bG/JxuAw5e5shtCDjmZ027W5d9ITp0g==, + integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-logger@3.378.0: + /@esbuild/darwin-arm64@0.16.3: resolution: { - integrity: sha512-l1DyaDLm3KeBMNMuANI3scWh8Xvu248x+vw6Z7ExWOhGXFmQ1MW7YvASg/SdxWkhlF9HmkkTif1LdMB22x6QDA==, + integrity: sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-recursion-detection@3.378.0: + /@esbuild/darwin-arm64@0.18.20: resolution: { - integrity: sha512-mUMfHAz0oGNIWiTZHTVJb+I515Hqs2zx1j36Le4MMiiaMkPW1SRUF1FIwGuc1wh6E8jB5q+XfEMriDjRi4TZRA==, + integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-sdk-s3@3.379.1: + /@esbuild/darwin-x64@0.16.3: resolution: { - integrity: sha512-NVHRpNLfkHCqr3CE1Bmlf8Fhys8lL78kDX7UONnTZXvSiSXmCS7EbNtGDghZ8IKi+V9S/ifB4sLsX3tfzY0i6Q==, + integrity: sha512-uEqZQ2omc6BvWqdCiyZ5+XmxuHEi1SPzpVxXCSSV2+Sh7sbXbpeNhHIeFrIpRjAs0lI1FmA1iIOxFozKBhKgRQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-arn-parser": 3.310.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-sdk-sts@3.379.1: + /@esbuild/darwin-x64@0.18.20: resolution: { - integrity: sha512-SK3gSyT0XbLiY12+AjLFYL9YngxOXHnZF3Z33Cdd4a+AUYrVBV7JBEEGD1Nlwrcmko+3XgaKlmgUaR5s91MYvg==, + integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/middleware-signing": 3.379.1 - "@aws-sdk/types": 3.378.0 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-signing@3.379.1: + /@esbuild/freebsd-arm64@0.16.3: resolution: { - integrity: sha512-kBk2ZUvR84EM4fICjr8K+Ykpf8SI1UzzPp2/UVYZ0X+4H/ZCjfSqohGRwHykMqeplne9qHSL7/rGJs1H3l3gPg==, + integrity: sha512-nJansp3sSXakNkOD5i5mIz2Is/HjzIhFs49b1tjrPrpCmwgBmH9SSzhC/Z1UqlkivqMYkhfPwMw1dGFUuwmXhw==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/property-provider": 2.0.2 - "@smithy/protocol-http": 2.0.2 - "@smithy/signature-v4": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/util-middleware": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-ssec@3.378.0: + /@esbuild/freebsd-arm64@0.18.20: resolution: { - integrity: sha512-WDT2LOd6OxlY1zkrRG9ZtW2vFms/dsqMg9VyE88RKG2oATxSXEhkr5zLbNVh3TyuUKnV9jydate56d/ECwHOHg==, + integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/middleware-user-agent@3.386.0: + /@esbuild/freebsd-x64@0.16.3: resolution: { - integrity: sha512-h6nVr5dvzrSLM+5BGbyqISh1p2NoTNv0+IZkMcGyig2jpdhbkMOPvvoOGMg16/cmelUQCguT26Jr7WYpLFDsTg==, + integrity: sha512-TfoDzLw+QHfc4a8aKtGSQ96Wa+6eimljjkq9HKR0rHlU83vw8aldMOUSJTUDxbcUdcgnJzPaX8/vGWm7vyV7ug==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@aws-sdk/util-endpoints": 3.386.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/signature-v4-multi-region@3.378.0: + /@esbuild/freebsd-x64@0.18.20: resolution: { - integrity: sha512-gtuABS7EeYZQeNzTrabY3Ruv4wWmoz4u8OMSGl47gYPDWA70WYEZ0aoi4zSGuKhXiqtVvTsO9wGEMIInwV5phQ==, + integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==, } - engines: { node: ">=14.0.0" } - peerDependencies: - "@aws-sdk/signature-v4-crt": ^3.118.0 - peerDependenciesMeta: - "@aws-sdk/signature-v4-crt": - optional: true - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/protocol-http": 2.0.2 - "@smithy/signature-v4": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/token-providers@3.386.0: + /@esbuild/linux-arm64@0.16.3: resolution: { - integrity: sha512-DStdqBtpO0FRC4mDCIPtrpLCFMnYJFo4cYlUyr9CKvKvh2IzPMU4rsKQjUhtmzdjLEvPTAcdfRx2Q9/sJkfe9Q==, + integrity: sha512-7I3RlsnxEFCHVZNBLb2w7unamgZ5sVwO0/ikE2GaYvYuUQs9Qte/w7TqWcXHtCwxvZx/2+F97ndiUQAWs47ZfQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/property-provider": 2.0.2 - "@smithy/shared-ini-file-loader": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@aws-sdk/types@3.378.0: + /@esbuild/linux-arm64@0.18.20: resolution: { - integrity: sha512-qP0CvR/ItgktmN8YXpGQglzzR/6s0nrsQ4zIfx3HMwpsBTwuouYahcCtF1Vr82P4NFcoDA412EJahJ2pIqEd+w==, + integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@aws-sdk/util-arn-parser@3.310.0: - resolution: - { - integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==, - } - engines: { node: ">=14.0.0" } - dependencies: - tslib: 2.6.1 - dev: false - - /@aws-sdk/util-endpoints@3.386.0: - resolution: - { - integrity: sha512-FDQRC9f78Kx12KsR43MukLRfqF3BNz5VfFdKP9ZYx3KK+bMjU1czjmjOS8bNMJWYM1Sn+nobBpPS3e2uupBtpg==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@aws-sdk/types": 3.378.0 - tslib: 2.6.1 - dev: false - - /@aws-sdk/util-locate-window@3.310.0: - resolution: - { - integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==, - } - engines: { node: ">=14.0.0" } - dependencies: - tslib: 2.6.1 - dev: false - - /@aws-sdk/util-user-agent-browser@3.378.0: - resolution: - { - integrity: sha512-FSCpagzftK1W+m7Ar6lpX7/Gr9y5P56nhFYz8U4EYQ4PkufS6czWX9YW+/FA5OYV0vlQ/SvPqMnzoHIPUNhZrQ==, - } - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/types": 2.1.0 - bowser: 2.11.0 - tslib: 2.6.1 - dev: false - - /@aws-sdk/util-user-agent-node@3.378.0: - resolution: - { - integrity: sha512-IdwVJV0E96MkJeFte4dlWqvB+oiqCiZ5lOlheY3W9NynTuuX0GGYNC8Y9yIsV8Oava1+ujpJq0ww6qXdYxmO4A==, - } - engines: { node: ">=14.0.0" } - peerDependencies: - aws-crt: ">=1.0.0" - peerDependenciesMeta: - aws-crt: - optional: true - dependencies: - "@aws-sdk/types": 3.378.0 - "@smithy/node-config-provider": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@aws-sdk/util-utf8-browser@3.259.0: - resolution: - { - integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==, - } - dependencies: - tslib: 2.6.1 - dev: false - - /@aws-sdk/xml-builder@3.310.0: - resolution: - { - integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==, - } - engines: { node: ">=14.0.0" } - dependencies: - tslib: 2.6.1 - dev: false - - /@cloudflare/kv-asset-handler@0.2.0: - resolution: - { - integrity: sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==, - } - dependencies: - mime: 3.0.0 - dev: true - - /@cloudflare/workerd-darwin-64@1.20230724.0: - resolution: - { - integrity: sha512-DQmFZWHhs8waQFYRb/Z8QmbitAvBMXnbUMUentp+3lS4eCYI0/iurTaQDiz5+ldUn9FTxD+1XuYZlTHzVNxoHw==, - } - engines: { node: ">=16" } - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@cloudflare/workerd-darwin-arm64@1.20230724.0: - resolution: - { - integrity: sha512-C7T0v/lMjEX7c4iROSZKgIF1eGw3+sj/gFpBD6xwxfbIcrKBjncMypeLQNpRTCdBQr1W3sNpg9jagwuVX5ByZQ==, - } - engines: { node: ">=16" } + engines: { node: ">=12" } cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@cloudflare/workerd-linux-64@1.20230724.0: - resolution: - { - integrity: sha512-o0F/hj73UXOQwkPkYqZuIxpjG8gAs2eoAGqxX1HSIYRf7iUhfFcPrupwjqlNqf7Oo1h46M+sClSFjr/ZU/LCjg==, - } - engines: { node: ">=16" } - cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@cloudflare/workerd-linux-arm64@1.20230724.0: + /@esbuild/linux-arm@0.16.3: resolution: { - integrity: sha512-UpzCoo7LOuPWxFPw84TZQTPIawIDQNSb3XnC6ffMjUH/FVwHmHdngIFZxW+xjLHKMIzGNAqSn3eRHekKgO3QqA==, + integrity: sha512-VwswmSYwVAAq6LysV59Fyqk3UIjbhuc6wb3vEcJ7HEJUtFuLK9uXWuFoH1lulEbE4+5GjtHi3MHX+w1gNHdOWQ==, } - engines: { node: ">=16" } - cpu: [arm64] + engines: { node: ">=12" } + cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@cloudflare/workerd-windows-64@1.20230724.0: - resolution: - { - integrity: sha512-wVpPNu19fnvgsD8V6NiGPSuET0bzKmgn3wJ6RwAwQA+GQ0hdDIDVYd13aImhgO6jLfQvkduCDxeZluGZ7PPojQ==, - } - engines: { node: ">=16" } - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@cloudflare/workers-types@4.20230807.0: - resolution: - { - integrity: sha512-gQczWuGE2rxmpzOCNn0zLbx8Xz0gqspdE9S7tu4Xax39q1csgO/E9flcS+KG3GHB522ugOh84inmABDhpeJnvQ==, - } - - /@drizzle-team/studio@0.0.5: - resolution: - { - integrity: sha512-ps5qF0tMxWRVu+V5gvCRrQNqlY92aTnIKdq27gm9LZMSdaKYZt6AVvSK1dlUMzs6Rt0Jm80b+eWct6xShBKhIw==, - } - dev: false - - /@esbuild-kit/core-utils@3.1.0: - resolution: - { - integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==, - } - dependencies: - esbuild: 0.17.19 - source-map-support: 0.5.21 - dev: false - - /@esbuild-kit/esm-loader@2.5.5: - resolution: - { - integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==, - } - dependencies: - "@esbuild-kit/core-utils": 3.1.0 - get-tsconfig: 4.6.2 - dev: false - - /@esbuild-plugins/node-globals-polyfill@0.1.1(esbuild@0.16.3): - resolution: - { - integrity: sha512-MR0oAA+mlnJWrt1RQVQ+4VYuRJW/P2YmRTv1AsplObyvuBMnPHiizUF95HHYiSsMGLhyGtWufaq2XQg6+iurBg==, - } - peerDependencies: - esbuild: "*" - dependencies: - esbuild: 0.16.3 - dev: true - - /@esbuild-plugins/node-modules-polyfill@0.1.4(esbuild@0.16.3): - resolution: - { - integrity: sha512-uZbcXi0zbmKC/050p3gJnne5Qdzw8vkXIv+c2BW0Lsc1ji1SkrxbKPUy5Efr0blbTu1SL8w4eyfpnSdPg3G0Qg==, - } - peerDependencies: - esbuild: "*" - dependencies: - esbuild: 0.16.3 - escape-string-regexp: 4.0.0 - rollup-plugin-node-polyfills: 0.2.1 - dev: true - - /@esbuild/android-arm64@0.16.3: + /@esbuild/linux-arm@0.18.20: resolution: { - integrity: sha512-RolFVeinkeraDvN/OoRf1F/lP0KUfGNb5jxy/vkIMeRRChkrX/HTYN6TYZosRJs3a1+8wqpxAo5PI5hFmxyPRg==, + integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==, } engines: { node: ">=12" } - cpu: [arm64] - os: [android] + cpu: [arm] + os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/android-arm64@0.17.19: - resolution: - { - integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@esbuild/android-arm64@0.18.20: - resolution: - { - integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@esbuild/android-arm@0.16.3: + /@esbuild/linux-ia32@0.16.3: resolution: { - integrity: sha512-mueuEoh+s1eRbSJqq9KNBQwI4QhQV6sRXIfTyLXSHGMpyew61rOK4qY21uKbXl1iBoMb0AdL1deWFCQVlN2qHA==, + integrity: sha512-X8FDDxM9cqda2rJE+iblQhIMYY49LfvW4kaEjoFbTTQ4Go8G96Smj2w3BRTwA8IHGoi9dPOPGAX63dhuv19UqA==, } engines: { node: ">=12" } - cpu: [arm] - os: [android] + cpu: [ia32] + os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/android-arm@0.17.19: + /@esbuild/linux-ia32@0.18.20: resolution: { - integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==, + integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==, } engines: { node: ">=12" } - cpu: [arm] - os: [android] + cpu: [ia32] + os: [linux] requiresBuild: true - dev: false + dev: true optional: true - /@esbuild/android-arm@0.18.20: + /@esbuild/linux-loong64@0.16.3: resolution: { - integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==, + integrity: sha512-hIbeejCOyO0X9ujfIIOKjBjNAs9XD/YdJ9JXAy1lHA+8UXuOqbFe4ErMCqMr8dhlMGBuvcQYGF7+kO7waj2KHw==, } engines: { node: ">=12" } - cpu: [arm] - os: [android] + cpu: [loong64] + os: [linux] requiresBuild: true - dev: false + dev: true optional: true - /@esbuild/android-x64@0.16.3: + /@esbuild/linux-loong64@0.18.20: resolution: { - integrity: sha512-SFpTUcIT1bIJuCCBMCQWq1bL2gPTjWoLZdjmIhjdcQHaUfV41OQfho6Ici5uvvkMmZRXIUGpM3GxysP/EU7ifQ==, + integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==, } engines: { node: ">=12" } - cpu: [x64] - os: [android] + cpu: [loong64] + os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/android-x64@0.17.19: + /@esbuild/linux-mips64el@0.16.3: resolution: { - integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==, + integrity: sha512-znFRzICT/V8VZQMt6rjb21MtAVJv/3dmKRMlohlShrbVXdBuOdDrGb+C2cZGQAR8RFyRe7HS6klmHq103WpmVw==, } engines: { node: ">=12" } - cpu: [x64] - os: [android] + cpu: [mips64el] + os: [linux] requiresBuild: true - dev: false + dev: true optional: true - /@esbuild/android-x64@0.18.20: + /@esbuild/linux-mips64el@0.18.20: resolution: { - integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==, + integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==, } engines: { node: ">=12" } - cpu: [x64] - os: [android] + cpu: [mips64el] + os: [linux] requiresBuild: true - dev: false + dev: true optional: true - /@esbuild/darwin-arm64@0.16.3: + /@esbuild/linux-ppc64@0.16.3: resolution: { - integrity: sha512-DO8WykMyB+N9mIDfI/Hug70Dk1KipavlGAecxS3jDUwAbTpDXj0Lcwzw9svkhxfpCagDmpaTMgxWK8/C/XcXvw==, + integrity: sha512-EV7LuEybxhXrVTDpbqWF2yehYRNz5e5p+u3oQUS2+ZFpknyi1NXxr8URk4ykR8Efm7iu04//4sBg249yNOwy5Q==, } engines: { node: ">=12" } - cpu: [arm64] - os: [darwin] + cpu: [ppc64] + os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/darwin-arm64@0.17.19: + /@esbuild/linux-ppc64@0.18.20: resolution: { - integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==, + integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==, } engines: { node: ">=12" } - cpu: [arm64] - os: [darwin] + cpu: [ppc64] + os: [linux] requiresBuild: true - dev: false + dev: true optional: true - /@esbuild/darwin-arm64@0.18.20: + /@esbuild/linux-riscv64@0.16.3: resolution: { - integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==, + integrity: sha512-uDxqFOcLzFIJ+r/pkTTSE9lsCEaV/Y6rMlQjUI9BkzASEChYL/aSQjZjchtEmdnVxDKETnUAmsaZ4pqK1eE5BQ==, } engines: { node: ">=12" } - cpu: [arm64] - os: [darwin] + cpu: [riscv64] + os: [linux] requiresBuild: true - dev: false - optional: true - - /@esbuild/darwin-x64@0.16.3: - resolution: - { - integrity: sha512-uEqZQ2omc6BvWqdCiyZ5+XmxuHEi1SPzpVxXCSSV2+Sh7sbXbpeNhHIeFrIpRjAs0lI1FmA1iIOxFozKBhKgRQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64@0.17.19: - resolution: - { - integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@esbuild/darwin-x64@0.18.20: - resolution: - { - integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-arm64@0.16.3: - resolution: - { - integrity: sha512-nJansp3sSXakNkOD5i5mIz2Is/HjzIhFs49b1tjrPrpCmwgBmH9SSzhC/Z1UqlkivqMYkhfPwMw1dGFUuwmXhw==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64@0.17.19: - resolution: - { - integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-arm64@0.18.20: - resolution: - { - integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-x64@0.16.3: - resolution: - { - integrity: sha512-TfoDzLw+QHfc4a8aKtGSQ96Wa+6eimljjkq9HKR0rHlU83vw8aldMOUSJTUDxbcUdcgnJzPaX8/vGWm7vyV7ug==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64@0.17.19: - resolution: - { - integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/freebsd-x64@0.18.20: - resolution: - { - integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm64@0.16.3: - resolution: - { - integrity: sha512-7I3RlsnxEFCHVZNBLb2w7unamgZ5sVwO0/ikE2GaYvYuUQs9Qte/w7TqWcXHtCwxvZx/2+F97ndiUQAWs47ZfQ==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64@0.17.19: - resolution: - { - integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm64@0.18.20: - resolution: - { - integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm@0.16.3: - resolution: - { - integrity: sha512-VwswmSYwVAAq6LysV59Fyqk3UIjbhuc6wb3vEcJ7HEJUtFuLK9uXWuFoH1lulEbE4+5GjtHi3MHX+w1gNHdOWQ==, - } - engines: { node: ">=12" } - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm@0.17.19: - resolution: - { - integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==, - } - engines: { node: ">=12" } - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-arm@0.18.20: - resolution: - { - integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==, - } - engines: { node: ">=12" } - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ia32@0.16.3: - resolution: - { - integrity: sha512-X8FDDxM9cqda2rJE+iblQhIMYY49LfvW4kaEjoFbTTQ4Go8G96Smj2w3BRTwA8IHGoi9dPOPGAX63dhuv19UqA==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32@0.17.19: - resolution: - { - integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ia32@0.18.20: - resolution: - { - integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-loong64@0.16.3: - resolution: - { - integrity: sha512-hIbeejCOyO0X9ujfIIOKjBjNAs9XD/YdJ9JXAy1lHA+8UXuOqbFe4ErMCqMr8dhlMGBuvcQYGF7+kO7waj2KHw==, - } - engines: { node: ">=12" } - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.17.19: - resolution: - { - integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==, - } - engines: { node: ">=12" } - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-loong64@0.18.20: - resolution: - { - integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==, - } - engines: { node: ">=12" } - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-mips64el@0.16.3: - resolution: - { - integrity: sha512-znFRzICT/V8VZQMt6rjb21MtAVJv/3dmKRMlohlShrbVXdBuOdDrGb+C2cZGQAR8RFyRe7HS6klmHq103WpmVw==, - } - engines: { node: ">=12" } - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el@0.17.19: - resolution: - { - integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==, - } - engines: { node: ">=12" } - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-mips64el@0.18.20: - resolution: - { - integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==, - } - engines: { node: ">=12" } - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ppc64@0.16.3: - resolution: - { - integrity: sha512-EV7LuEybxhXrVTDpbqWF2yehYRNz5e5p+u3oQUS2+ZFpknyi1NXxr8URk4ykR8Efm7iu04//4sBg249yNOwy5Q==, - } - engines: { node: ">=12" } - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64@0.17.19: - resolution: - { - integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==, - } - engines: { node: ">=12" } - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-ppc64@0.18.20: - resolution: - { - integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==, - } - engines: { node: ">=12" } - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-riscv64@0.16.3: - resolution: - { - integrity: sha512-uDxqFOcLzFIJ+r/pkTTSE9lsCEaV/Y6rMlQjUI9BkzASEChYL/aSQjZjchtEmdnVxDKETnUAmsaZ4pqK1eE5BQ==, - } - engines: { node: ">=12" } - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64@0.17.19: - resolution: - { - integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==, - } - engines: { node: ">=12" } - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-riscv64@0.18.20: - resolution: - { - integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==, - } - engines: { node: ">=12" } - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-s390x@0.16.3: - resolution: - { - integrity: sha512-NbeREhzSxYwFhnCAQOQZmajsPYtX71Ufej3IQ8W2Gxskfz9DK58ENEju4SbpIj48VenktRASC52N5Fhyf/aliQ==, - } - engines: { node: ">=12" } - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x@0.17.19: - resolution: - { - integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==, - } - engines: { node: ">=12" } - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-s390x@0.18.20: - resolution: - { - integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==, - } - engines: { node: ">=12" } - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-x64@0.16.3: - resolution: - { - integrity: sha512-SDiG0nCixYO9JgpehoKgScwic7vXXndfasjnD5DLbp1xltANzqZ425l7LSdHynt19UWOcDjG9wJJzSElsPvk0w==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64@0.17.19: - resolution: - { - integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/linux-x64@0.18.20: - resolution: - { - integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@esbuild/netbsd-x64@0.16.3: - resolution: - { - integrity: sha512-AzbsJqiHEq1I/tUvOfAzCY15h4/7Ivp3ff/o1GpP16n48JMNAtbW0qui2WCgoIZArEHD0SUQ95gvR0oSO7ZbdA==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64@0.17.19: - resolution: - { - integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/netbsd-x64@0.18.20: - resolution: - { - integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/openbsd-x64@0.16.3: - resolution: - { - integrity: sha512-gSABi8qHl8k3Cbi/4toAzHiykuBuWLZs43JomTcXkjMZVkp0gj3gg9mO+9HJW/8GB5H89RX/V0QP4JGL7YEEVg==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64@0.17.19: - resolution: - { - integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/openbsd-x64@0.18.20: - resolution: - { - integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: false - optional: true - - /@esbuild/sunos-x64@0.16.3: - resolution: - { - integrity: sha512-SF9Kch5Ete4reovvRO6yNjMxrvlfT0F0Flm+NPoUw5Z4Q3r1d23LFTgaLwm3Cp0iGbrU/MoUI+ZqwCv5XJijCw==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64@0.17.19: - resolution: - { - integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: false - optional: true - - /@esbuild/sunos-x64@0.18.20: - resolution: - { - integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-arm64@0.16.3: - resolution: - { - integrity: sha512-u5aBonZIyGopAZyOnoPAA6fGsDeHByZ9CnEzyML9NqntK6D/xl5jteZUKm/p6nD09+v3pTM6TuUIqSPcChk5gg==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64@0.17.19: - resolution: - { - integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-arm64@0.18.20: - resolution: - { - integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==, - } - engines: { node: ">=12" } - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-ia32@0.16.3: - resolution: - { - integrity: sha512-GlgVq1WpvOEhNioh74TKelwla9KDuAaLZrdxuuUgsP2vayxeLgVc+rbpIv0IYF4+tlIzq2vRhofV+KGLD+37EQ==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32@0.17.19: - resolution: - { - integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-ia32@0.18.20: - resolution: - { - integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==, - } - engines: { node: ">=12" } - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-x64@0.16.3: - resolution: - { - integrity: sha512-5/JuTd8OWW8UzEtyf19fbrtMJENza+C9JoPIkvItgTBQ1FO2ZLvjbPO6Xs54vk0s5JB5QsfieUEshRQfu7ZHow==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64@0.17.19: - resolution: - { - integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@esbuild/win32-x64@0.18.20: - resolution: - { - integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==, - } - engines: { node: ">=12" } - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.46.0): - resolution: - { - integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.46.0 - eslint-visitor-keys: 3.4.2 - - /@eslint-community/regexpp@4.6.2: - resolution: - { - integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - /@eslint/eslintrc@2.1.1: - resolution: - { - integrity: sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.20.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - /@eslint/js@8.46.0: - resolution: - { - integrity: sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - - /@humanwhocodes/config-array@0.11.10: - resolution: - { - integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==, - } - engines: { node: ">=10.10.0" } - dependencies: - "@humanwhocodes/object-schema": 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - /@humanwhocodes/module-importer@1.0.1: - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: ">=12.22" } - - /@humanwhocodes/object-schema@1.2.1: - resolution: - { - integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==, - } - - /@lucia-auth/adapter-mysql@2.0.0(@planetscale/database@1.10.0)(lucia@2.2.0)(mysql2@3.6.0): - resolution: - { - integrity: sha512-8a4JZ3VjDyRu/mAop2hEt/jOJO2HXwWIAid6a4wGiR8wgnlyOws9brRc+/wxQHSOlWUlrWemrfDvXLs5mMtkeQ==, - } - peerDependencies: - "@planetscale/database": ^1.0.0 - lucia: ^2.0.0 - mysql2: ^3.0.0 - peerDependenciesMeta: - "@planetscale/database": - optional: true - mysql2: - optional: true - dependencies: - "@planetscale/database": 1.10.0 - lucia: 2.2.0 - mysql2: 3.6.0 - dev: false - - /@lucia-auth/adapter-prisma@3.0.1(@prisma/client@5.1.1)(lucia@2.2.0): - resolution: - { - integrity: sha512-JZNl+721M5ApjtNiH+WUYstiC8cffcI/Y9IzKsa0uUhQMIHl2ObCrgO0R3fgHnjWHBjhQoK36g/r/iSJIDppBA==, - } - peerDependencies: - "@prisma/client": ^4.2.0 || ^5.0.0 - lucia: ^2.0.0 - dependencies: - "@prisma/client": 5.1.1 - lucia: 2.2.0 - dev: false - - /@nodelib/fs.scandir@2.1.5: - resolution: - { - integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, - } - engines: { node: ">= 8" } - dependencies: - "@nodelib/fs.stat": 2.0.5 - run-parallel: 1.2.0 - - /@nodelib/fs.stat@2.0.5: - resolution: - { - integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, - } - engines: { node: ">= 8" } - - /@nodelib/fs.walk@1.2.8: - resolution: - { - integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, - } - engines: { node: ">= 8" } - dependencies: - "@nodelib/fs.scandir": 2.1.5 - fastq: 1.15.0 - - /@one-ini/wasm@0.1.1: - resolution: - { - integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==, - } - dev: false - - /@planetscale/database@1.10.0: - resolution: - { - integrity: sha512-XMfNRjIPgGTga6g1YpGr7E21CcnHZcHZdyhRUIiZ/AlpD+ts65UF2B3wKjcu7MKMynmmcOGs6R9kAT6D1OTlZQ==, - } - engines: { node: ">=16" } - dev: false - - /@prisma/client@5.1.1: - resolution: - { - integrity: sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA==, - } - engines: { node: ">=16.13" } - requiresBuild: true - peerDependencies: - prisma: "*" - peerDependenciesMeta: - prisma: - optional: true - dependencies: - "@prisma/engines-version": 5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e - dev: false - - /@prisma/engines-version@5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e: - resolution: - { - integrity: sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ==, - } - dev: false - - /@react-email/render@0.0.7: - resolution: - { - integrity: sha512-hMMhxk6TpOcDC5qnKzXPVJoVGEwfm+U5bGOPH+MyTTlx0F02RLQygcATBKsbP7aI/mvkmBAZoFbgPIHop7ovug==, - } - engines: { node: ">=16.0.0" } - dependencies: - html-to-text: 9.0.3 - pretty: 2.0.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@selderee/plugin-htmlparser2@0.10.0: - resolution: - { - integrity: sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA==, - } - dependencies: - domhandler: 5.0.3 - selderee: 0.10.0 - dev: false - - /@smithy/abort-controller@2.0.2: - resolution: - { - integrity: sha512-ln5Cob0mksym62sLr7NiPOSqJ0jKao4qjfcNLDdgINM1lQI12hXrZBlKdPHbXJqpKhKiECDgonMoqCM8bigq4g==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@smithy/chunked-blob-reader-native@2.0.0: - resolution: - { - integrity: sha512-HM8V2Rp1y8+1343tkZUKZllFhEQPNmpNdgFAncbTsxkZ18/gqjk23XXv3qGyXWp412f3o43ZZ1UZHVcHrpRnCQ==, - } - dependencies: - "@smithy/util-base64": 2.0.0 - tslib: 2.6.1 - dev: false - - /@smithy/chunked-blob-reader@2.0.0: - resolution: - { - integrity: sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==, - } - dependencies: - tslib: 2.6.1 - dev: false - - /@smithy/config-resolver@2.0.2: - resolution: - { - integrity: sha512-0kdsqBL6BdmSbdU6YaDkodVBMua5MuQQluC3nocJ7OJ6PnOuM7i2FEQHE46LBadLqT+CimlDSM+6j91uHNL1ng==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - "@smithy/util-config-provider": 2.0.0 - "@smithy/util-middleware": 2.0.0 - tslib: 2.6.1 - dev: false - - /@smithy/credential-provider-imds@2.0.2: - resolution: - { - integrity: sha512-mbWFYEZ00LBRDk3WvcXViwpdpkJQcfrM3seuKzFxZnF6wIBLMwrcWcsj+OUC/1L+86m8aQY9imXMAaQsAoGxow==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/node-config-provider": 2.0.2 - "@smithy/property-provider": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/url-parser": 2.0.2 - tslib: 2.6.1 - dev: false - - /@smithy/eventstream-codec@2.0.2: - resolution: - { - integrity: sha512-PQZiKx7fMnNwx4zxcUCm82VjnqK6wV4MEHSmMy3taj5dKfXV782IjRGyaDT+8TsmNqVdZIkve5zLRAzh+7kOhA==, - } - dependencies: - "@aws-crypto/crc32": 3.0.0 - "@smithy/types": 2.1.0 - "@smithy/util-hex-encoding": 2.0.0 - tslib: 2.6.1 - dev: false - - /@smithy/eventstream-serde-browser@2.0.2: - resolution: - { - integrity: sha512-qaHlcFI+ILE+gZV2B/aZMVXc9LG4v1Owa20dHlP0dLOiJ9WByOjtD2qZmYA/HO4qkkDZHEL/0baWc63aqLCHKQ==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/eventstream-serde-universal": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@smithy/eventstream-serde-config-resolver@2.0.2: - resolution: - { - integrity: sha512-iVC7/NFNWfSXllAxFNUuC4QlREdZjMmAOdISb6fwny/4mUDt1EtYLCrXq7gN1mIzhRPwMpL9YvQ8jpgvfA0Jdw==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@smithy/eventstream-serde-node@2.0.2: - resolution: - { - integrity: sha512-p7py8jDpIS1bRewskwgEgJx1OkFvockA2bJnXtOAPJib42DtyRpp8oV14s2ZpjMq57r9KMCQy2j02g554DNavg==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/eventstream-serde-universal": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@smithy/eventstream-serde-universal@2.0.2: - resolution: - { - integrity: sha512-zf/hm5VIDsvl+XpI1rop4xwXLKiBUe5pxgjRFdHi7AC1p6Zc8uJfyCExLiMUP/QspoIrVV1xGwFFxRCeddDH3g==, - } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/eventstream-codec": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false - - /@smithy/fetch-http-handler@2.0.2: - resolution: - { - integrity: sha512-Wo2m1RaiXNSLF4J3D62LpdSoj/YYb+6tn0H8is1tSrzr7eXAdiYVBc0wIa23N0wT4zmN0iG/yNY6gTCDQ6799A==, - } - dependencies: - "@smithy/protocol-http": 2.0.2 - "@smithy/querystring-builder": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/util-base64": 2.0.0 - tslib: 2.6.1 - dev: false + dev: true + optional: true - /@smithy/hash-blob-browser@2.0.2: + /@esbuild/linux-riscv64@0.18.20: resolution: { - integrity: sha512-CmVGWbiyiEySGDRg3o2C3DLZYW+mH8fMoIEZrmwnBM8bQsepZGOME40tbpvv12BIhZIInJV8srMMHpQ6aKObLA==, + integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==, } - dependencies: - "@smithy/chunked-blob-reader": 2.0.0 - "@smithy/chunked-blob-reader-native": 2.0.0 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@smithy/hash-node@2.0.2: + /@esbuild/linux-s390x@0.16.3: resolution: { - integrity: sha512-JKDzZ1YVR7JzOBaJoWy3ToJCE86OQE6D4kOBvvVsu93a3lcF9kv6KYTKBYEWAjwOn/CpK4NH7mKB01OQ8H+aiA==, + integrity: sha512-NbeREhzSxYwFhnCAQOQZmajsPYtX71Ufej3IQ8W2Gxskfz9DK58ENEju4SbpIj48VenktRASC52N5Fhyf/aliQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - "@smithy/util-buffer-from": 2.0.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@smithy/hash-stream-node@2.0.2: + /@esbuild/linux-s390x@0.18.20: resolution: { - integrity: sha512-cDfGE81BbykXKZ50+eLU5Yat8WGiDFQpNa+5S3AfDIzz5h4D73DpxWwcwV4qYB7GoAw2chFqTCAGWgU/MgRS9g==, + integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@smithy/invalid-dependency@2.0.2: + /@esbuild/linux-x64@0.16.3: resolution: { - integrity: sha512-inQZQ5gCO3WRWuXpsc1YJ4KBjsvj2qsoU32yTIKznBWTCQe/D5Dp+sSaysqBqxe0VTZ+8nFEHdUMWUX2BxQThw==, + integrity: sha512-SDiG0nCixYO9JgpehoKgScwic7vXXndfasjnD5DLbp1xltANzqZ425l7LSdHynt19UWOcDjG9wJJzSElsPvk0w==, } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@smithy/is-array-buffer@2.0.0: + /@esbuild/linux-x64@0.18.20: resolution: { - integrity: sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==, + integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==, } - engines: { node: ">=14.0.0" } - dependencies: - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /@smithy/md5-js@2.0.2: + /@esbuild/netbsd-x64@0.16.3: resolution: { - integrity: sha512-qm9845tzkYOm3HM/nFiZVMsA9nE7klO69T1qrrbrQKpUJpEFV87XDInbnRpYzBAFUH4DRodbZ9spEnjF7ffoww==, + integrity: sha512-AzbsJqiHEq1I/tUvOfAzCY15h4/7Ivp3ff/o1GpP16n48JMNAtbW0qui2WCgoIZArEHD0SUQ95gvR0oSO7ZbdA==, } - dependencies: - "@smithy/types": 2.1.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true - /@smithy/middleware-content-length@2.0.2: + /@esbuild/netbsd-x64@0.18.20: resolution: { - integrity: sha512-FmHlNfuvYgDZE3fIx0G3rD/wLXfAmBYE4mVc/w6d7RllA7TygPzq2pfHL1iCMzWkWTdoAVnt3h4aavAZnhaxEQ==, + integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/protocol-http": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true - /@smithy/middleware-endpoint@2.0.2: + /@esbuild/openbsd-x64@0.16.3: resolution: { - integrity: sha512-ropE7/c+g22QeluZ+By/B/WvVep0UFreX+IeRMGIO7EbOUPgqtJRXpbJFdG6JKB1uC+CdaJLn4MnZnVBpcyjuA==, + integrity: sha512-gSABi8qHl8k3Cbi/4toAzHiykuBuWLZs43JomTcXkjMZVkp0gj3gg9mO+9HJW/8GB5H89RX/V0QP4JGL7YEEVg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/middleware-serde": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/url-parser": 2.0.2 - "@smithy/util-middleware": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true - /@smithy/middleware-retry@2.0.2: + /@esbuild/openbsd-x64@0.18.20: resolution: { - integrity: sha512-wtBUXqtZVriiXppYaFkUrybAPhFVX7vebnW/yVPliLMWMcguOMS58qhOYPZe3t9Wki2+mASfyu+kO3An8lAg2A==, + integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/protocol-http": 2.0.2 - "@smithy/service-error-classification": 2.0.0 - "@smithy/types": 2.1.0 - "@smithy/util-middleware": 2.0.0 - "@smithy/util-retry": 2.0.0 - tslib: 2.6.1 - uuid: 8.3.2 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true - /@smithy/middleware-serde@2.0.2: + /@esbuild/sunos-x64@0.16.3: resolution: { - integrity: sha512-Kw9xLdlueIaivUWslKB67WZ/cCUg3QnzYVIA3t5KfgsseEEuU4UxXw8NSTvIt71gqQloY+Um8ugS+idgxrWWnw==, + integrity: sha512-SF9Kch5Ete4reovvRO6yNjMxrvlfT0F0Flm+NPoUw5Z4Q3r1d23LFTgaLwm3Cp0iGbrU/MoUI+ZqwCv5XJijCw==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true - /@smithy/middleware-stack@2.0.0: + /@esbuild/sunos-x64@0.18.20: resolution: { - integrity: sha512-31XC1xNF65nlbc16yuh3wwTudmqs6qy4EseQUGF8A/p2m/5wdd/cnXJqpniy/XvXVwkHPz/GwV36HqzHtIKATQ==, + integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==, } - engines: { node: ">=14.0.0" } - dependencies: - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true - /@smithy/node-config-provider@2.0.2: + /@esbuild/win32-arm64@0.16.3: resolution: { - integrity: sha512-9wVJccASfuCctNWrzR0zrDkf0ox3HCHGEhFlWL2LBoghUYuK28pVRBbG69wvnkhlHnB8dDZHagxH+Nq9dm7eWw==, + integrity: sha512-u5aBonZIyGopAZyOnoPAA6fGsDeHByZ9CnEzyML9NqntK6D/xl5jteZUKm/p6nD09+v3pTM6TuUIqSPcChk5gg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/property-provider": 2.0.2 - "@smithy/shared-ini-file-loader": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@smithy/node-http-handler@2.0.2: + /@esbuild/win32-arm64@0.18.20: resolution: { - integrity: sha512-lpZjmtmyZqSAtMPsbrLhb7XoAQ2kAHeuLY/csW6I2k+QyFvOk7cZeQsqEngWmZ9SJaeYiDCBINxAIM61i5WGLw==, + integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/abort-controller": 2.0.2 - "@smithy/protocol-http": 2.0.2 - "@smithy/querystring-builder": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@smithy/property-provider@2.0.2: + /@esbuild/win32-ia32@0.16.3: resolution: { - integrity: sha512-DfaZ8cO+d/mgnMzIllcXcU4OYP+omiOl2LYdn/fTGpw/EAQSVzscYV2muV3sDDnuPYQ/r014hUqIxnF+pzh+SQ==, + integrity: sha512-GlgVq1WpvOEhNioh74TKelwla9KDuAaLZrdxuuUgsP2vayxeLgVc+rbpIv0IYF4+tlIzq2vRhofV+KGLD+37EQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@smithy/protocol-http@2.0.2: + /@esbuild/win32-ia32@0.18.20: resolution: { - integrity: sha512-qWu8g1FUy+m36KpO1sREJSF7BaLmjw9AqOuwxLVVSdYz+nUQjc9tFAZ9LB6jJXKdsZFSjfkjHJBbhD78QdE7Rw==, + integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@smithy/querystring-builder@2.0.2: + /@esbuild/win32-x64@0.16.3: resolution: { - integrity: sha512-H99LOMWEssfwqkOoTs4Y12UiZ7CTGQSX5Nrx5UkYgRbUEpC1GnnaprHiYrqclC58/xr4K76aNchdPyioxewMzA==, + integrity: sha512-5/JuTd8OWW8UzEtyf19fbrtMJENza+C9JoPIkvItgTBQ1FO2ZLvjbPO6Xs54vk0s5JB5QsfieUEshRQfu7ZHow==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - "@smithy/util-uri-escape": 2.0.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@smithy/querystring-parser@2.0.2: + /@esbuild/win32-x64@0.18.20: resolution: { - integrity: sha512-L4VtKQ8O4/aWPQJbiFymbhAmxdfLnEaROh/Vs0OstJ7jtOZeBl2QJmuWY2V7hjt64W7V+tEn2sv6vVvnxkm/xQ==, + integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12" } + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true - /@smithy/service-error-classification@2.0.0: + /@eslint-community/eslint-utils@4.4.0(eslint@8.48.0): resolution: { - integrity: sha512-2z5Nafy1O0cTf69wKyNjGW/sNVMiqDnb4jgwfMG8ye8KnFJ5qmJpDccwIbJNhXIfbsxTg9SEec2oe1cexhMJvw==, + integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==, } - engines: { node: ">=14.0.0" } - dev: false + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.48.0 + eslint-visitor-keys: 3.4.3 - /@smithy/shared-ini-file-loader@2.0.2: + /@eslint-community/regexpp@4.8.0: resolution: { - integrity: sha512-2VkNOM/82u4vatVdK5nfusgGIlvR48Fkq6me17Oc+V1iyxfR/1x0pG6LzW0br1qlGtzBYFZKmDyviBRcPVFTVw==, + integrity: sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - /@smithy/signature-v4@2.0.2: + /@eslint/eslintrc@2.1.2: resolution: { - integrity: sha512-YMooDEw/UmGxcXY4qWnSXkbPFsRloluSvyXVT678YPDN/K2AS1GzKfRsvSU7fbccOB4WF8MHZf2UqcRGEltE3Q==, + integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==, } - engines: { node: ">=14.0.0" } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - "@smithy/eventstream-codec": 2.0.2 - "@smithy/is-array-buffer": 2.0.0 - "@smithy/types": 2.1.0 - "@smithy/util-hex-encoding": 2.0.0 - "@smithy/util-middleware": 2.0.0 - "@smithy/util-uri-escape": 2.0.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 - dev: false + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.21.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color - /@smithy/smithy-client@2.0.2: + /@eslint/js@8.48.0: resolution: { - integrity: sha512-mDfokI8WwLU5C0gcQ4ww/zJI/WLGSh2+vdIA42JRnjfYUjJNH/rKfX9YOnn2eBOxl3loATERVUqkHmKe+P8s2Q==, + integrity: sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/middleware-stack": 2.0.0 - "@smithy/types": 2.1.0 - "@smithy/util-stream": 2.0.2 - tslib: 2.6.1 - dev: false + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - /@smithy/types@2.1.0: + /@humanwhocodes/config-array@0.11.11: resolution: { - integrity: sha512-KLsCsqxX0j2l99iP8s0f7LBlcsp7a7ceXGn0LPYPyVOsqmIKvSaPQajq0YevlL4T9Bm+DtcyXfBTbtBcLX1I7A==, + integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==, } - engines: { node: ">=14.0.0" } + engines: { node: ">=10.10.0" } dependencies: - tslib: 2.6.1 - dev: false + "@humanwhocodes/object-schema": 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color - /@smithy/url-parser@2.0.2: + /@humanwhocodes/module-importer@1.0.1: resolution: { - integrity: sha512-X1mHCzrSVDlhVy7d3S7Vq+dTfYzwh4n7xGHhyJumu77nJqIss0lazVug85Pwo0DKIoO314wAOvMnBxNYDa+7wA==, + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, } - dependencies: - "@smithy/querystring-parser": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + engines: { node: ">=12.22" } - /@smithy/util-base64@2.0.0: + /@humanwhocodes/object-schema@1.2.1: resolution: { - integrity: sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA==, + integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/util-buffer-from": 2.0.0 - tslib: 2.6.1 - dev: false - /@smithy/util-body-length-browser@2.0.0: + /@libsql/client@0.3.2: resolution: { - integrity: sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==, + integrity: sha512-e0qgf7gFOwhu0ueU/RK68TmD0PnjX3RaeM5lrJowGMEZjvh/shZ4BhwPA26Ec0nSa4oiZ0s0zn/T5YjcgY1bTw==, } dependencies: - tslib: 2.6.1 + "@libsql/hrana-client": 0.5.0 + better-sqlite3: 8.5.2 + js-base64: 3.7.5 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate dev: false - /@smithy/util-body-length-node@2.0.0: + /@libsql/hrana-client@0.5.0: resolution: { - integrity: sha512-ZV7Z/WHTMxHJe/xL/56qZwSUcl63/5aaPAGjkfynJm4poILjdD4GmFI+V+YWabh2WJIjwTKZ5PNsuvPQKt93Mg==, + integrity: sha512-o9yXH+9XBPnMSrBkY17q2xknfNXJaCHQv4rFJikt1g8M0d80hwp4ZZ1jHwacuL61wRa4j6qKMFqh9ti+CoTH1A==, } - engines: { node: ">=14.0.0" } dependencies: - tslib: 2.6.1 + "@libsql/isomorphic-fetch": 0.1.6 + "@libsql/isomorphic-ws": 0.1.3 + js-base64: 3.7.5 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate dev: false - /@smithy/util-buffer-from@2.0.0: + /@libsql/isomorphic-fetch@0.1.6: resolution: { - integrity: sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==, + integrity: sha512-8qhxEDmVBDb54E9xdW1xqw3zLNShkMZpf5YQU3PvwjtKNLOPde59Oqez+RnZHsYkt9zQxxOF+7gSHVJeP/UWqg==, } - engines: { node: ">=14.0.0" } dependencies: - "@smithy/is-array-buffer": 2.0.0 - tslib: 2.6.1 + "@types/node-fetch": 2.6.4 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding dev: false - /@smithy/util-config-provider@2.0.0: + /@libsql/isomorphic-ws@0.1.3: resolution: { - integrity: sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==, + integrity: sha512-54dZXgYwWDKsnfWv8GCVYvhn6RDlqFDGAc8EQMd941yvGMsGzo06Gn6Iyjw//nJ1iJO97FbXgoQ1apikoFD/WA==, } - engines: { node: ">=14.0.0" } dependencies: - tslib: 2.6.1 + "@types/ws": 8.5.5 + ws: 8.13.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate dev: false - /@smithy/util-defaults-mode-browser@2.0.2: + /@lucia-auth/adapter-sqlite@2.0.0(@libsql/client@0.3.2)(lucia@2.4.1): resolution: { - integrity: sha512-c2tMMjb624XLuzmlRoZpnFOkejVxcgw3WQKdmgdGZYZapcLzXyC0H9JhnXMjQCt30GqLTlsILRNVBYwFRbw/4Q==, + integrity: sha512-Hiy8WND/1mi25r0M2ruSml+Ab5GJLr4XZEHApYw1lxybF+VlPUyX7oTtIVCSgvq/SWiP/tvnyPwnXAPmNr8/bg==, } - engines: { node: ">= 10.0.0" } + peerDependencies: + "@libsql/client": ^0.3.0 + better-sqlite3: ^8.0.0 + lucia: ^2.0.0 + peerDependenciesMeta: + "@libsql/client": + optional: true + better-sqlite3: + optional: true dependencies: - "@smithy/property-provider": 2.0.2 - "@smithy/types": 2.1.0 - bowser: 2.11.0 - tslib: 2.6.1 + "@libsql/client": 0.3.2 + lucia: 2.4.1 dev: false - /@smithy/util-defaults-mode-node@2.0.2: + /@nodelib/fs.scandir@2.1.5: resolution: { - integrity: sha512-gt7m5LLqUtEKldJLyc14DE4kb85vxwomvt9AfEMEvWM4VwfWS1kGJqiStZFb5KNqnQPXw8vvpgLTi8NrWAOXqg==, + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, } - engines: { node: ">= 10.0.0" } + engines: { node: ">= 8" } dependencies: - "@smithy/config-resolver": 2.0.2 - "@smithy/credential-provider-imds": 2.0.2 - "@smithy/node-config-provider": 2.0.2 - "@smithy/property-provider": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 - dev: false + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 - /@smithy/util-hex-encoding@2.0.0: + /@nodelib/fs.stat@2.0.5: resolution: { - integrity: sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==, + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, } - engines: { node: ">=14.0.0" } - dependencies: - tslib: 2.6.1 - dev: false + engines: { node: ">= 8" } - /@smithy/util-middleware@2.0.0: + /@nodelib/fs.walk@1.2.8: resolution: { - integrity: sha512-eCWX4ECuDHn1wuyyDdGdUWnT4OGyIzV0LN1xRttBFMPI9Ff/4heSHVxneyiMtOB//zpXWCha1/SWHJOZstG7kA==, + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, } - engines: { node: ">=14.0.0" } + engines: { node: ">= 8" } dependencies: - tslib: 2.6.1 - dev: false + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.15.0 - /@smithy/util-retry@2.0.0: + /@one-ini/wasm@0.1.1: resolution: { - integrity: sha512-/dvJ8afrElasuiiIttRJeoS2sy8YXpksQwiM/TcepqdRVp7u4ejd9C4IQURHNjlfPUT7Y6lCDSa2zQJbdHhVTg==, + integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==, } - engines: { node: ">= 14.0.0" } - dependencies: - "@smithy/service-error-classification": 2.0.0 - tslib: 2.6.1 dev: false - /@smithy/util-stream@2.0.2: + /@react-email/render@0.0.7: resolution: { - integrity: sha512-Mg9IJcKIu4YKlbzvpp1KLvh4JZLdcPgpxk+LICuDwzZCfxe47R9enVK8dNEiuyiIGK2ExbfvzCVT8IBru62vZw==, + integrity: sha512-hMMhxk6TpOcDC5qnKzXPVJoVGEwfm+U5bGOPH+MyTTlx0F02RLQygcATBKsbP7aI/mvkmBAZoFbgPIHop7ovug==, } - engines: { node: ">=14.0.0" } + engines: { node: ">=16.0.0" } dependencies: - "@smithy/fetch-http-handler": 2.0.2 - "@smithy/node-http-handler": 2.0.2 - "@smithy/types": 2.1.0 - "@smithy/util-base64": 2.0.0 - "@smithy/util-buffer-from": 2.0.0 - "@smithy/util-hex-encoding": 2.0.0 - "@smithy/util-utf8": 2.0.0 - tslib: 2.6.1 + html-to-text: 9.0.3 + pretty: 2.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: false - /@smithy/util-uri-escape@2.0.0: + /@selderee/plugin-htmlparser2@0.10.0: resolution: { - integrity: sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==, + integrity: sha512-gW69MEamZ4wk1OsOq1nG1jcyhXIQcnrsX5JwixVw/9xaiav8TCyjESAruu1Rz9yyInhgBXxkNwMeygKnN2uxNA==, } - engines: { node: ">=14.0.0" } dependencies: - tslib: 2.6.1 + domhandler: 5.0.3 + selderee: 0.10.0 dev: false - /@smithy/util-utf8@2.0.0: + /@types/json-schema@7.0.12: resolution: { - integrity: sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ==, + integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==, } - engines: { node: ">=14.0.0" } - dependencies: - "@smithy/util-buffer-from": 2.0.0 - tslib: 2.6.1 dev: false - /@smithy/util-waiter@2.0.2: + /@types/node-fetch@2.6.4: resolution: { - integrity: sha512-7XCEVXDLguf3Og0NIF/KYEAHtrzNXmCdtEwMfOXr4iBKOUWYzNj91YB9O7tLrct8VGvysGA0x2xYzbxMbvF0QQ==, + integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==, } - engines: { node: ">=14.0.0" } dependencies: - "@smithy/abort-controller": 2.0.2 - "@smithy/types": 2.1.0 - tslib: 2.6.1 + "@types/node": 20.5.8 + form-data: 3.0.1 dev: false - /@types/json-schema@7.0.12: + /@types/node@20.5.8: resolution: { - integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==, + integrity: sha512-eajsR9aeljqNhK028VG0Wuw+OaY5LLxYmxeoXynIoE6jannr9/Ucd1LL0hSSoafk5LTYG+FfqsyGt81Q6Zkybw==, } - dev: false - /@types/node@20.4.8: + /@types/semver@7.5.1: resolution: { - integrity: sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==, + integrity: sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==, } - dev: true + dev: false - /@types/semver@7.5.0: + /@types/ws@8.5.5: resolution: { - integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==, + integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==, } + dependencies: + "@types/node": 20.5.8 dev: false - /@typescript-eslint/eslint-plugin@6.3.0(@typescript-eslint/parser@6.3.0)(eslint@8.46.0)(typescript@5.1.6): + /@typescript-eslint/eslint-plugin@6.5.0(@typescript-eslint/parser@6.5.0)(eslint@8.48.0)(typescript@5.2.2): resolution: { - integrity: sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A==, + integrity: sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==, } engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: @@ -2429,29 +995,28 @@ packages: typescript: optional: true dependencies: - "@eslint-community/regexpp": 4.6.2 - "@typescript-eslint/parser": 6.3.0(eslint@8.46.0)(typescript@5.1.6) - "@typescript-eslint/scope-manager": 6.3.0 - "@typescript-eslint/type-utils": 6.3.0(eslint@8.46.0)(typescript@5.1.6) - "@typescript-eslint/utils": 6.3.0(eslint@8.46.0)(typescript@5.1.6) - "@typescript-eslint/visitor-keys": 6.3.0 + "@eslint-community/regexpp": 4.8.0 + "@typescript-eslint/parser": 6.5.0(eslint@8.48.0)(typescript@5.2.2) + "@typescript-eslint/scope-manager": 6.5.0 + "@typescript-eslint/type-utils": 6.5.0(eslint@8.48.0)(typescript@5.2.2) + "@typescript-eslint/utils": 6.5.0(eslint@8.48.0)(typescript@5.2.2) + "@typescript-eslint/visitor-keys": 6.5.0 debug: 4.3.4 - eslint: 8.46.0 + eslint: 8.48.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 - natural-compare-lite: 1.4.0 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.1.6) - typescript: 5.1.6 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/parser@6.3.0(eslint@8.46.0)(typescript@5.1.6): + /@typescript-eslint/parser@6.5.0(eslint@8.48.0)(typescript@5.2.2): resolution: { - integrity: sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==, + integrity: sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==, } engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: @@ -2461,32 +1026,32 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/scope-manager": 6.3.0 - "@typescript-eslint/types": 6.3.0 - "@typescript-eslint/typescript-estree": 6.3.0(typescript@5.1.6) - "@typescript-eslint/visitor-keys": 6.3.0 + "@typescript-eslint/scope-manager": 6.5.0 + "@typescript-eslint/types": 6.5.0 + "@typescript-eslint/typescript-estree": 6.5.0(typescript@5.2.2) + "@typescript-eslint/visitor-keys": 6.5.0 debug: 4.3.4 - eslint: 8.46.0 - typescript: 5.1.6 + eslint: 8.48.0 + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/scope-manager@6.3.0: + /@typescript-eslint/scope-manager@6.5.0: resolution: { - integrity: sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==, + integrity: sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==, } engines: { node: ^16.0.0 || >=18.0.0 } dependencies: - "@typescript-eslint/types": 6.3.0 - "@typescript-eslint/visitor-keys": 6.3.0 + "@typescript-eslint/types": 6.5.0 + "@typescript-eslint/visitor-keys": 6.5.0 dev: false - /@typescript-eslint/type-utils@6.3.0(eslint@8.46.0)(typescript@5.1.6): + /@typescript-eslint/type-utils@6.5.0(eslint@8.48.0)(typescript@5.2.2): resolution: { - integrity: sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ==, + integrity: sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==, } engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: @@ -2496,28 +1061,28 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/typescript-estree": 6.3.0(typescript@5.1.6) - "@typescript-eslint/utils": 6.3.0(eslint@8.46.0)(typescript@5.1.6) + "@typescript-eslint/typescript-estree": 6.5.0(typescript@5.2.2) + "@typescript-eslint/utils": 6.5.0(eslint@8.48.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.46.0 - ts-api-utils: 1.0.1(typescript@5.1.6) - typescript: 5.1.6 + eslint: 8.48.0 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/types@6.3.0: + /@typescript-eslint/types@6.5.0: resolution: { - integrity: sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==, + integrity: sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==, } engines: { node: ^16.0.0 || >=18.0.0 } dev: false - /@typescript-eslint/typescript-estree@6.3.0(typescript@5.1.6): + /@typescript-eslint/typescript-estree@6.5.0(typescript@5.2.2): resolution: { - integrity: sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==, + integrity: sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==, } engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: @@ -2526,49 +1091,49 @@ packages: typescript: optional: true dependencies: - "@typescript-eslint/types": 6.3.0 - "@typescript-eslint/visitor-keys": 6.3.0 + "@typescript-eslint/types": 6.5.0 + "@typescript-eslint/visitor-keys": 6.5.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.1.6) - typescript: 5.1.6 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: false - /@typescript-eslint/utils@6.3.0(eslint@8.46.0)(typescript@5.1.6): + /@typescript-eslint/utils@6.5.0(eslint@8.48.0)(typescript@5.2.2): resolution: { - integrity: sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==, + integrity: sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==, } engines: { node: ^16.0.0 || >=18.0.0 } peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - "@eslint-community/eslint-utils": 4.4.0(eslint@8.46.0) + "@eslint-community/eslint-utils": 4.4.0(eslint@8.48.0) "@types/json-schema": 7.0.12 - "@types/semver": 7.5.0 - "@typescript-eslint/scope-manager": 6.3.0 - "@typescript-eslint/types": 6.3.0 - "@typescript-eslint/typescript-estree": 6.3.0(typescript@5.1.6) - eslint: 8.46.0 + "@types/semver": 7.5.1 + "@typescript-eslint/scope-manager": 6.5.0 + "@typescript-eslint/types": 6.5.0 + "@typescript-eslint/typescript-estree": 6.5.0(typescript@5.2.2) + eslint: 8.48.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: false - /@typescript-eslint/visitor-keys@6.3.0: + /@typescript-eslint/visitor-keys@6.5.0: resolution: { - integrity: sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==, + integrity: sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==, } engines: { node: ^16.0.0 || >=18.0.0 } dependencies: - "@typescript-eslint/types": 6.3.0 - eslint-visitor-keys: 3.4.2 + "@typescript-eslint/types": 6.5.0 + eslint-visitor-keys: 3.4.3 dev: false /abbrev@1.1.1: @@ -2672,19 +1237,6 @@ packages: } dev: false - /axios@1.4.0: - resolution: - { - integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==, - } - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: false - /balanced-match@1.0.2: resolution: { @@ -2696,18 +1248,16 @@ packages: { integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, } - dev: true - /better-sqlite3@8.5.0: + /better-sqlite3@8.5.2: resolution: { - integrity: sha512-vbPcv/Hx5WYdyNg/NbcfyaBZyv9s/NVbxb7yCeC5Bq1pVocNxeL2tZmSu3Rlm4IEOTjYdGyzWQgyx0OSdORBzw==, + integrity: sha512-w/EZ/jwuZF+/47mAVC2+rhR2X/gwkZ+fd1pbX7Y90D5NRaRzDQcxrHY10t6ijGiYIonCVsBSF5v1cay07bP5sg==, } requiresBuild: true dependencies: bindings: 1.5.0 prebuild-install: 7.1.1 - dev: true /binary-extensions@2.2.0: resolution: @@ -2724,7 +1274,6 @@ packages: } dependencies: file-uri-to-path: 1.0.0 - dev: true /bl@4.1.0: resolution: @@ -2735,7 +1284,6 @@ packages: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true /blake3-wasm@2.1.5: resolution: @@ -2744,13 +1292,6 @@ packages: } dev: true - /bowser@2.11.0: - resolution: - { - integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==, - } - dev: false - /brace-expansion@1.1.11: resolution: { @@ -2767,7 +1308,6 @@ packages: } dependencies: balanced-match: 1.0.2 - dev: false /braces@3.0.2: resolution: @@ -2783,6 +1323,7 @@ packages: { integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, } + dev: true /buffer@5.7.1: resolution: @@ -2792,7 +1333,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true /busboy@1.6.0: resolution: @@ -2817,7 +1357,7 @@ packages: integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==, } engines: { node: ">=14.16" } - dev: false + dev: true /capnp-ts@0.7.0: resolution: @@ -2826,7 +1366,7 @@ packages: } dependencies: debug: 4.3.4 - tslib: 2.6.1 + tslib: 2.6.2 transitivePeerDependencies: - supports-color dev: true @@ -2847,7 +1387,7 @@ packages: integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==, } engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } - dev: false + dev: true /chokidar@3.5.3: resolution: @@ -2864,7 +1404,7 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: true /chownr@1.1.4: @@ -2872,7 +1412,6 @@ packages: { integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==, } - dev: true /cli-color@2.0.3: resolution: @@ -2886,7 +1425,7 @@ packages: es6-iterator: 2.0.3 memoizee: 0.4.15 timers-ext: 0.1.7 - dev: false + dev: true /color-convert@2.0.1: resolution: @@ -2927,7 +1466,7 @@ packages: integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==, } engines: { node: ^12.20.0 || >=14 } - dev: false + dev: true /concat-map@0.0.1: resolution: @@ -2984,7 +1523,7 @@ packages: dependencies: es5-ext: 0.10.62 type: 1.2.0 - dev: false + dev: true /data-uri-to-buffer@2.0.2: resolution: @@ -3015,7 +1554,6 @@ packages: engines: { node: ">=10" } dependencies: mimic-response: 3.1.0 - dev: true /deep-extend@0.6.0: resolution: @@ -3023,7 +1561,6 @@ packages: integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==, } engines: { node: ">=4.0.0" } - dev: true /deep-is@0.1.4: resolution: @@ -3061,7 +1598,6 @@ packages: integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==, } engines: { node: ">=8" } - dev: true /difflib@0.2.4: resolution: @@ -3070,7 +1606,7 @@ packages: } dependencies: heap: 0.2.7 - dev: false + dev: true /dir-glob@3.0.1: resolution: @@ -3136,7 +1672,7 @@ packages: integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==, } engines: { node: ">=12" } - dev: false + dev: true /dreamopt@0.8.0: resolution: @@ -3146,12 +1682,12 @@ packages: engines: { node: ">=0.4.0" } dependencies: wordwrap: 1.0.0 - dev: false + dev: true - /drizzle-kit@0.19.12: + /drizzle-kit@0.19.13: resolution: { - integrity: sha512-rcsmh5gUIkvuD0WrbEc+aLpqY2q2J8ltynRcJiJo2l01hhsYvPnX0sgxWlFXlfAIa5ZXNw2nJZhYlslI6tG3MA==, + integrity: sha512-Rba5VW1O2JfJlwVBeZ8Zwt2E2us5oZ08PQBDiVSGlug53TOc8hzXjblZFuF+dnll9/RQEHrkzBmJFgqTvn5Rxg==, } hasBin: true dependencies: @@ -3166,15 +1702,15 @@ packages: hanji: 0.0.5 json-diff: 0.9.0 minimatch: 7.4.6 - zod: 3.21.4 + zod: 3.22.2 transitivePeerDependencies: - supports-color - dev: false + dev: true - /drizzle-orm@0.28.1(@cloudflare/workers-types@4.20230807.0)(@planetscale/database@1.10.0)(mysql2@3.6.0): + /drizzle-orm@0.28.5(@cloudflare/workers-types@4.20230821.0)(@libsql/client@0.3.2)(mysql2@3.6.0): resolution: { - integrity: sha512-6ms2pVxvkBJtuP1BZTUCzLLkr+iK/cNgd+tCw+I5/PoM8PB9kXaYoW9yNe/cnaXb0TLJ1gDEndDIyQdRX7zCdQ==, + integrity: sha512-6r6Iw4c38NAmW6TiKH3TUpGUQ1YdlEoLJOQptn8XPx3Z63+vFNKfAiANqrIiYZiMjKR9+NYAL219nFrmo1duXA==, } peerDependencies: "@aws-sdk/client-rds-data": ">=3" @@ -3236,8 +1772,8 @@ packages: sqlite3: optional: true dependencies: - "@cloudflare/workers-types": 4.20230807.0 - "@planetscale/database": 1.10.0 + "@cloudflare/workers-types": 4.20230821.0 + "@libsql/client": 0.3.2 mysql2: 3.6.0 dev: false @@ -3262,7 +1798,6 @@ packages: } dependencies: once: 1.4.0 - dev: true /entities@4.5.0: resolution: @@ -3283,7 +1818,7 @@ packages: es6-iterator: 2.0.3 es6-symbol: 3.1.3 next-tick: 1.1.0 - dev: false + dev: true /es6-iterator@2.0.3: resolution: @@ -3294,7 +1829,7 @@ packages: d: 1.0.1 es5-ext: 0.10.62 es6-symbol: 3.1.3 - dev: false + dev: true /es6-symbol@3.1.3: resolution: @@ -3304,7 +1839,7 @@ packages: dependencies: d: 1.0.1 ext: 1.7.0 - dev: false + dev: true /es6-weak-map@2.0.3: resolution: @@ -3316,7 +1851,7 @@ packages: es5-ext: 0.10.62 es6-iterator: 2.0.3 es6-symbol: 3.1.3 - dev: false + dev: true /esbuild-register@3.4.2(esbuild@0.18.20): resolution: @@ -3330,7 +1865,7 @@ packages: esbuild: 0.18.20 transitivePeerDependencies: - supports-color - dev: false + dev: true /esbuild@0.16.3: resolution: @@ -3365,39 +1900,6 @@ packages: "@esbuild/win32-x64": 0.16.3 dev: true - /esbuild@0.17.19: - resolution: - { - integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==, - } - engines: { node: ">=12" } - hasBin: true - requiresBuild: true - optionalDependencies: - "@esbuild/android-arm": 0.17.19 - "@esbuild/android-arm64": 0.17.19 - "@esbuild/android-x64": 0.17.19 - "@esbuild/darwin-arm64": 0.17.19 - "@esbuild/darwin-x64": 0.17.19 - "@esbuild/freebsd-arm64": 0.17.19 - "@esbuild/freebsd-x64": 0.17.19 - "@esbuild/linux-arm": 0.17.19 - "@esbuild/linux-arm64": 0.17.19 - "@esbuild/linux-ia32": 0.17.19 - "@esbuild/linux-loong64": 0.17.19 - "@esbuild/linux-mips64el": 0.17.19 - "@esbuild/linux-ppc64": 0.17.19 - "@esbuild/linux-riscv64": 0.17.19 - "@esbuild/linux-s390x": 0.17.19 - "@esbuild/linux-x64": 0.17.19 - "@esbuild/netbsd-x64": 0.17.19 - "@esbuild/openbsd-x64": 0.17.19 - "@esbuild/sunos-x64": 0.17.19 - "@esbuild/win32-arm64": 0.17.19 - "@esbuild/win32-ia32": 0.17.19 - "@esbuild/win32-x64": 0.17.19 - dev: false - /esbuild@0.18.20: resolution: { @@ -3429,7 +1931,7 @@ packages: "@esbuild/win32-arm64": 0.18.20 "@esbuild/win32-ia32": 0.18.20 "@esbuild/win32-x64": 0.18.20 - dev: false + dev: true /escape-string-regexp@4.0.0: resolution: @@ -3438,7 +1940,7 @@ packages: } engines: { node: ">=10" } - /eslint-config-google@0.14.0(eslint@8.46.0): + /eslint-config-google@0.14.0(eslint@8.48.0): resolution: { integrity: sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==, @@ -3447,7 +1949,7 @@ packages: peerDependencies: eslint: ">=5.16.0" dependencies: - eslint: 8.46.0 + eslint: 8.48.0 dev: true /eslint-plugin-json@3.1.0: @@ -3471,26 +1973,26 @@ packages: esrecurse: 4.3.0 estraverse: 5.3.0 - /eslint-visitor-keys@3.4.2: + /eslint-visitor-keys@3.4.3: resolution: { - integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==, + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - /eslint@8.46.0: + /eslint@8.48.0: resolution: { - integrity: sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==, + integrity: sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==, } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } hasBin: true dependencies: - "@eslint-community/eslint-utils": 4.4.0(eslint@8.46.0) - "@eslint-community/regexpp": 4.6.2 - "@eslint/eslintrc": 2.1.1 - "@eslint/js": 8.46.0 - "@humanwhocodes/config-array": 0.11.10 + "@eslint-community/eslint-utils": 4.4.0(eslint@8.48.0) + "@eslint-community/regexpp": 4.8.0 + "@eslint/eslintrc": 2.1.2 + "@eslint/js": 8.48.0 + "@humanwhocodes/config-array": 0.11.11 "@humanwhocodes/module-importer": 1.0.1 "@nodelib/fs.walk": 1.2.8 ajv: 6.12.6 @@ -3500,7 +2002,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.2 + eslint-visitor-keys: 3.4.3 espree: 9.6.1 esquery: 1.5.0 esutils: 2.0.3 @@ -3508,7 +2010,7 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.20.0 + globals: 13.21.0 graphemer: 1.4.0 ignore: 5.2.4 imurmurhash: 0.1.4 @@ -3535,7 +2037,7 @@ packages: dependencies: acorn: 8.10.0 acorn-jsx: 5.3.2(acorn@8.10.0) - eslint-visitor-keys: 3.4.2 + eslint-visitor-keys: 3.4.3 /esquery@1.5.0: resolution: @@ -3584,7 +2086,7 @@ packages: dependencies: d: 1.0.1 es5-ext: 0.10.62 - dev: false + dev: true /exit-hook@2.2.1: resolution: @@ -3600,7 +2102,6 @@ packages: integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==, } engines: { node: ">=6" } - dev: true /ext@1.7.0: resolution: @@ -3609,7 +2110,7 @@ packages: } dependencies: type: 2.7.2 - dev: false + dev: true /extend-shallow@2.0.1: resolution: @@ -3653,16 +2154,6 @@ packages: integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, } - /fast-xml-parser@4.2.5: - resolution: - { - integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==, - } - hasBin: true - dependencies: - strnum: 1.0.5 - dev: false - /fastq@1.15.0: resolution: { @@ -3678,14 +2169,13 @@ packages: } engines: { node: ^10.12.0 || >=12.0.0 } dependencies: - flat-cache: 3.0.4 + flat-cache: 3.1.0 /file-uri-to-path@1.0.0: resolution: { integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==, } - dev: true /fill-range@7.0.1: resolution: @@ -3706,14 +2196,15 @@ packages: locate-path: 6.0.0 path-exists: 4.0.0 - /flat-cache@3.0.4: + /flat-cache@3.1.0: resolution: { - integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==, + integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==, } - engines: { node: ^10.12.0 || >=12.0.0 } + engines: { node: ">=12.0.0" } dependencies: flatted: 3.2.7 + keyv: 4.5.3 rimraf: 3.0.2 /flatted@3.2.7: @@ -3722,23 +2213,10 @@ packages: integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==, } - /follow-redirects@1.15.2: - resolution: - { - integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==, - } - engines: { node: ">=4.0" } - peerDependencies: - debug: "*" - peerDependenciesMeta: - debug: - optional: true - dev: false - - /form-data@4.0.0: + /form-data@3.0.1: resolution: { - integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==, + integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==, } engines: { node: ">= 6" } dependencies: @@ -3752,7 +2230,6 @@ packages: { integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==, } - dev: true /fs.realpath@1.0.0: resolution: @@ -3760,10 +2237,10 @@ packages: integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, } - /fsevents@2.3.2: + /fsevents@2.3.3: resolution: { - integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, } engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] @@ -3790,21 +2267,20 @@ packages: source-map: 0.6.1 dev: true - /get-tsconfig@4.6.2: + /get-tsconfig@4.7.0: resolution: { - integrity: sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==, + integrity: sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==, } dependencies: resolve-pkg-maps: 1.0.0 - dev: false + dev: true /github-from-package@0.0.0: resolution: { integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==, } - dev: true /glob-parent@5.1.2: resolution: @@ -3856,12 +2332,11 @@ packages: inherits: 2.0.4 minimatch: 5.1.6 once: 1.4.0 - dev: false - /globals@13.20.0: + /globals@13.21.0: resolution: { - integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==, + integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==, } engines: { node: ">=8" } dependencies: @@ -3896,7 +2371,7 @@ packages: dependencies: lodash.throttle: 4.1.1 sisteransi: 1.0.5 - dev: false + dev: true /has-flag@4.0.0: resolution: @@ -3910,12 +2385,12 @@ packages: { integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==, } - dev: false + dev: true - /hono@3.4.1: + /hono@3.5.6: resolution: { - integrity: sha512-fA7/cfgNg060mt12cYykdstwqM/bRvqYmQlPpEjoKIwMs6QF1rPJzZjppCrFuZZJvKE1vEP6kEAaaDH96XRg7Q==, + integrity: sha512-ycTOpIZJ6yLbjzoE+ojsesC7G7ZXfGSoCIDyvqmzlHc5Mk4Aj48Ed9R5g7gw3v7rOkS81pjcYIvWef/karq1iA==, } engines: { node: ">=16.0.0" } dev: false @@ -3953,6 +2428,15 @@ packages: } dev: true + /husky@8.0.3: + resolution: + { + integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==, + } + engines: { node: ">=14" } + hasBin: true + dev: true + /iconv-lite@0.6.3: resolution: { @@ -3968,7 +2452,6 @@ packages: { integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, } - dev: true /ignore@5.2.4: resolution: @@ -4075,7 +2558,7 @@ packages: { integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==, } - dev: false + dev: true /is-property@1.0.2: resolution: @@ -4098,6 +2581,13 @@ packages: integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + /js-base64@3.7.5: + resolution: + { + integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==, + } + dev: false + /js-beautify@1.14.9: resolution: { @@ -4128,6 +2618,12 @@ packages: dependencies: argparse: 2.0.1 + /json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + /json-diff@0.9.0: resolution: { @@ -4138,7 +2634,7 @@ packages: cli-color: 2.0.3 difflib: 0.2.4 dreamopt: 0.8.0 - dev: false + dev: true /json-schema-traverse@0.4.1: resolution: @@ -4159,6 +2655,14 @@ packages: } dev: true + /keyv@4.5.3: + resolution: + { + integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==, + } + dependencies: + json-buffer: 3.0.1 + /kind-of@3.2.2: resolution: { @@ -4214,7 +2718,7 @@ packages: { integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==, } - dev: false + dev: true /lodash@4.17.21: resolution: @@ -4272,12 +2776,12 @@ packages: } dependencies: es5-ext: 0.10.62 - dev: false + dev: true - /lucia@2.2.0: + /lucia@2.4.1: resolution: { - integrity: sha512-bCKSbWGpUQl+YbWaIy4t9eqqZQwKa5XKTBMTAjhAWwg9rEgPWu6L7vH55SueRsvB+2XNa2NA71ASdBUyS2fwLA==, + integrity: sha512-MM5b6LLT//a8qR02490ShwYvsTdlAj4WoFP+Ok9NE31zwBm0dM9lK9TivHwAb69eka71OAcnZeaBi1CBfHtwZA==, } dev: false @@ -4304,7 +2808,7 @@ packages: lru-queue: 0.1.0 next-tick: 1.1.0 timers-ext: 0.1.7 - dev: false + dev: true /merge2@1.4.1: resolution: @@ -4358,18 +2862,17 @@ packages: integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==, } engines: { node: ">=10" } - dev: true - /miniflare@3.20230724.0: + /miniflare@3.20230814.1: resolution: { - integrity: sha512-YU8yUwoVJCiuzY2i9ZBJ+McjL/mqwKnMJfn23QedSCvx82Mys8GAlkHCH69mqSqzlSw8IVcdxec330meRRf9bg==, + integrity: sha512-LMgqd1Ut0+fnlvQepVbbBYQczQnyuuap8bgUwOyPETka0S9NR9NxMQSNaBgVZ0uOaG7xMJ/OVTRlz+TGB86PWA==, } engines: { node: ">=16.13" } dependencies: acorn: 8.10.0 acorn-walk: 8.2.0 - better-sqlite3: 8.5.0 + better-sqlite3: 8.5.2 capnp-ts: 0.7.0 exit-hook: 2.2.1 glob-to-regexp: 0.4.1 @@ -4379,10 +2882,10 @@ packages: source-map-support: 0.5.21 stoppable: 1.1.0 undici: 5.23.0 - workerd: 1.20230724.0 + workerd: 1.20230814.1 ws: 8.13.0 youch: 3.2.3 - zod: 3.21.4 + zod: 3.22.2 transitivePeerDependencies: - bufferutil - supports-color @@ -4405,7 +2908,6 @@ packages: engines: { node: ">=10" } dependencies: brace-expansion: 2.0.1 - dev: false /minimatch@7.4.6: resolution: @@ -4415,7 +2917,7 @@ packages: engines: { node: ">=10" } dependencies: brace-expansion: 2.0.1 - dev: false + dev: true /minimatch@9.0.1: resolution: @@ -4432,14 +2934,12 @@ packages: { integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, } - dev: true /mkdirp-classic@0.5.3: resolution: { integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==, } - dev: true /ms@2.1.2: resolution: @@ -4496,14 +2996,6 @@ packages: { integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==, } - dev: true - - /natural-compare-lite@1.4.0: - resolution: - { - integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==, - } - dev: false /natural-compare@1.4.0: resolution: @@ -4516,17 +3008,46 @@ packages: { integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==, } - dev: false + dev: true - /node-abi@3.45.0: + /node-abi@3.47.0: resolution: { - integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==, + integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==, } engines: { node: ">=10" } dependencies: semver: 7.5.4 - dev: true + + /node-fetch@2.6.12: + resolution: + { + integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /node-fetch@2.7.0: + resolution: + { + integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, + } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false /node-forge@1.3.1: resolution: @@ -4678,13 +3199,12 @@ packages: minimist: 1.2.8 mkdirp-classic: 0.5.3 napi-build-utils: 1.0.2 - node-abi: 3.45.0 + node-abi: 3.47.0 pump: 3.0.0 rc: 1.2.8 simple-get: 4.0.1 tar-fs: 2.1.1 tunnel-agent: 0.6.0 - dev: true /prelude-ls@1.2.1: resolution: @@ -4693,10 +3213,10 @@ packages: } engines: { node: ">= 0.8.0" } - /prettier@3.0.1: + /prettier@3.0.3: resolution: { - integrity: sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==, + integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==, } engines: { node: ">=14" } hasBin: true @@ -4728,13 +3248,6 @@ packages: } dev: false - /proxy-from-env@1.1.0: - resolution: - { - integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, - } - dev: false - /pump@3.0.0: resolution: { @@ -4743,7 +3256,6 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: true /punycode@2.3.0: resolution: @@ -4758,14 +3270,6 @@ packages: integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, } - /range-parser@1.2.1: - resolution: - { - integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==, - } - engines: { node: ">= 0.6" } - dev: false - /rc@1.2.8: resolution: { @@ -4777,7 +3281,6 @@ packages: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: true /react-dom@18.2.0(react@18.2.0): resolution: @@ -4812,7 +3315,6 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: true /readdirp@3.6.0: resolution: @@ -4824,26 +3326,18 @@ packages: picomatch: 2.3.1 dev: true - /render2@1.2.1: - resolution: - { - integrity: sha512-HfLOYtG6p6jx6GG6uub7YGJ4iv+GlOwFmDtGdtSe2NQJ6peMZ0u76k7GAZ0z7GSf4e9UfeCcQxme4Mayh7DLqw==, - } - dependencies: - range-parser: 1.2.1 - dev: false - - /resend@0.17.2: + /resend@1.0.0: resolution: { - integrity: sha512-lakm76u4MiIDeMF1s2tCmjtksOhwZOs4WcAXkA7aUTvl+63/h+0h6Q6WnkB8RGtj6GakUhQuUkiZshfXgtIrGw==, + integrity: sha512-8LEE4gncmcm8bsvxvahZFpFk5hxUrKdagqWoX/MRXVU2YZ9coYxqZDeDYXG9pexz1A694bjE1hiQbBAA+bHAow==, } + engines: { node: ">=16" } dependencies: "@react-email/render": 0.0.7 - axios: 1.4.0 + node-fetch: 2.6.12 type-fest: 3.13.0 transitivePeerDependencies: - - debug + - encoding dev: false /resolve-from@4.0.0: @@ -4858,7 +3352,7 @@ packages: { integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, } - dev: false + dev: true /reusify@1.0.4: resolution: @@ -4919,7 +3413,6 @@ packages: { integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, } - dev: true /safer-buffer@2.1.2: resolution: @@ -5001,7 +3494,6 @@ packages: { integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==, } - dev: true /simple-get@4.0.1: resolution: @@ -5012,14 +3504,13 @@ packages: decompress-response: 6.0.0 once: 1.4.0 simple-concat: 1.0.1 - dev: true /sisteransi@1.0.5: resolution: { integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==, } - dev: false + dev: true /slash@3.0.0: resolution: @@ -5037,6 +3528,7 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true /source-map@0.6.1: resolution: @@ -5044,6 +3536,7 @@ packages: integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, } engines: { node: ">=0.10.0" } + dev: true /source-map@0.7.4: resolution: @@ -5102,7 +3595,6 @@ packages: } dependencies: safe-buffer: 5.2.1 - dev: true /strip-ansi@6.0.1: resolution: @@ -5119,7 +3611,6 @@ packages: integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==, } engines: { node: ">=0.10.0" } - dev: true /strip-json-comments@3.1.1: resolution: @@ -5128,13 +3619,6 @@ packages: } engines: { node: ">=8" } - /strnum@1.0.5: - resolution: - { - integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==, - } - dev: false - /supports-color@7.2.0: resolution: { @@ -5154,7 +3638,6 @@ packages: mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - dev: true /tar-stream@2.2.0: resolution: @@ -5168,7 +3651,6 @@ packages: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true /text-table@0.2.0: resolution: @@ -5184,7 +3666,7 @@ packages: dependencies: es5-ext: 0.10.62 next-tick: 1.1.0 - dev: false + dev: true /to-regex-range@5.0.1: resolution: @@ -5195,30 +3677,45 @@ packages: dependencies: is-number: 7.0.0 - /ts-api-utils@1.0.1(typescript@5.1.6): + /tr46@0.0.3: + resolution: + { + integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, + } + dev: false + + /ts-api-utils@1.0.2(typescript@5.2.2): resolution: { - integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==, + integrity: sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==, } engines: { node: ">=16.13.0" } peerDependencies: typescript: ">=4.2.0" dependencies: - typescript: 5.1.6 + typescript: 5.2.2 dev: false - /tslib@1.14.1: + /tslib@2.6.2: resolution: { - integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, + integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==, } - dev: false + dev: true - /tslib@2.6.1: + /tsx@3.12.8: resolution: { - integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==, + integrity: sha512-Lt9KYaRGF023tlLInPj8rgHwsZU8qWLBj4iRXNWxTfjIkU7canGL806AqKear1j722plHuiYNcL2ZCo6uS9UJA==, } + hasBin: true + dependencies: + "@esbuild-kit/cjs-loader": 2.4.2 + "@esbuild-kit/core-utils": 3.2.2 + "@esbuild-kit/esm-loader": 2.5.5 + optionalDependencies: + fsevents: 2.3.3 + dev: true /tunnel-agent@0.6.0: resolution: @@ -5227,7 +3724,6 @@ packages: } dependencies: safe-buffer: 5.2.1 - dev: true /type-check@0.4.0: resolution: @@ -5258,19 +3754,19 @@ packages: { integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==, } - dev: false + dev: true /type@2.7.2: resolution: { integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==, } - dev: false + dev: true - /typescript@5.1.6: + /typescript@5.2.2: resolution: { - integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==, + integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==, } engines: { node: ">=14.17" } hasBin: true @@ -5298,15 +3794,6 @@ packages: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, } - dev: true - - /uuid@8.3.2: - resolution: - { - integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, - } - hasBin: true - dev: false /vscode-json-languageservice@4.2.1: resolution: @@ -5349,6 +3836,23 @@ packages: } dev: true + /webidl-conversions@3.0.1: + resolution: + { + integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, + } + dev: false + + /whatwg-url@5.0.0: + resolution: + { + integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, + } + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which@2.0.2: resolution: { @@ -5364,28 +3868,28 @@ packages: { integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==, } - dev: false + dev: true - /workerd@1.20230724.0: + /workerd@1.20230814.1: resolution: { - integrity: sha512-++D7JqS4/dk7zvtGpk+i/7G9bZtEl6lTtgAsIoSSGR1qJAxxEu21ktm9+FH0EYh7uKfizuM5H9lrTsR+3u44PA==, + integrity: sha512-zJeSEteXuAD+bpYJT8WvzTAHvIAkKPVxOV+Jy6zCLKz5e08N3OUbAF+wrvGWc8b2aB1sj+IYsdXfkv4puH+qXQ==, } engines: { node: ">=16" } hasBin: true requiresBuild: true optionalDependencies: - "@cloudflare/workerd-darwin-64": 1.20230724.0 - "@cloudflare/workerd-darwin-arm64": 1.20230724.0 - "@cloudflare/workerd-linux-64": 1.20230724.0 - "@cloudflare/workerd-linux-arm64": 1.20230724.0 - "@cloudflare/workerd-windows-64": 1.20230724.0 + "@cloudflare/workerd-darwin-64": 1.20230814.1 + "@cloudflare/workerd-darwin-arm64": 1.20230814.1 + "@cloudflare/workerd-linux-64": 1.20230814.1 + "@cloudflare/workerd-linux-arm64": 1.20230814.1 + "@cloudflare/workerd-windows-64": 1.20230814.1 dev: true - /wrangler@3.4.0: + /wrangler@3.6.0: resolution: { - integrity: sha512-sATQ84zH/zFUHSaa4hY3V24TBrad3R9HhGV47U6Ek7XRQDLQHBm0jt84mJD3sSV/hhaq5s+xidIYulhm+m1/Tg==, + integrity: sha512-GWs4+gIUK+086svW/TgFhhxxrl/hdW2L7WASbdc10dJT7yFmCXse0SnHiqWUxbFu3ScP2t3a3LszJ08wwolWHg==, } engines: { node: ">=16.13.0" } hasBin: true @@ -5396,14 +3900,14 @@ packages: blake3-wasm: 2.1.5 chokidar: 3.5.3 esbuild: 0.16.3 - miniflare: 3.20230724.0 + miniflare: 3.20230814.1 nanoid: 3.3.6 path-to-regexp: 6.2.1 selfsigned: 2.1.1 source-map: 0.7.4 xxhash-wasm: 1.0.2 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 transitivePeerDependencies: - bufferutil - supports-color @@ -5430,7 +3934,6 @@ packages: optional: true utf-8-validate: optional: true - dev: true /xxhash-wasm@1.0.2: resolution: @@ -5463,8 +3966,9 @@ packages: stacktracey: 2.1.8 dev: true - /zod@3.21.4: + /zod@3.22.2: resolution: { - integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==, + integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==, } + dev: true diff --git a/renovate.json b/renovate.json index 5d39e8b8..7ec37f6f 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,4 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:base"] + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:base"] } diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json deleted file mode 100644 index d55cb7e3..00000000 --- a/src/db/migrations/meta/_journal.json +++ /dev/null @@ -1 +0,0 @@ -{ "version": "5", "dialect": "mysql", "entries": [] } diff --git a/src/db/migrations/schema.ts b/src/db/migrations/schema.ts deleted file mode 100644 index 674d516e..00000000 --- a/src/db/migrations/schema.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { - mysqlTable, - index, - primaryKey, - int, - varchar, - mysqlEnum, - unique, - bigint, - datetime, -} from "drizzle-orm/mysql-core"; -import { tableNames } from "@/lib/drizzle"; -// import { sql } from "drizzle-orm"; - -export const assets = mysqlTable( - tableNames.assets, - { - id: int("id").autoincrement().notNull(), - name: varchar("name", { length: 191 }).notNull(), - game: varchar("game", { length: 191 }).notNull(), - assetCategory: varchar("asset_category", { length: 191 }).notNull(), - tags: mysqlEnum("tags", ["OFFICIAL", "FANMADE"]) - .default("OFFICIAL") - .notNull(), - url: varchar("url", { length: 191 }).notNull(), - status: mysqlEnum("status", ["PENDING", "APPROVED", "REJECTED"]) - .default("PENDING") - .notNull(), - uploadedBy: varchar("uploaded_by", { length: 191 }) - .notNull() - .references(() => authUser.id), - uploadedDate: varchar("uploaded_date", { length: 191 }).notNull(), - viewCount: int("view_count").default(0).notNull(), - downloadCount: int("download_count").default(0).notNull(), - fileSize: int("file_size").notNull(), - width: int("width").default(0).notNull(), - height: int("height").default(0).notNull(), - }, - (table) => { - return { - idIdx: index("assets_id_idx").on(table.id), - nameIdx: index("assets_name_idx").on(table.name), - gameIdx: index("assets_game_idx").on(table.game), - assetCategoryIdx: index("assets_asset_category_idx").on( - table.assetCategory - ), - statusIdx: index("assets_status_idx").on(table.status), - tagsIdx: index("assets_tags_idx").on(table.tags), - uploadedByIdx: index("assets_uploaded_by_idx").on(table.uploadedBy), - assetsId: primaryKey(table.id), - }; - } -); - -export const authKey = mysqlTable( - tableNames.authKey, - { - id: varchar("id", { length: 191 }).notNull(), - hashedPassword: varchar("hashed_password", { length: 191 }), - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - }, - (table) => { - return { - userIdIdx: index("authKey_user_id_idx").on(table.userId), - authKeyId: primaryKey(table.id), - authKeyIdKey: unique("authKey_id_key").on(table.id), - }; - } -); - -export const authSession = mysqlTable( - tableNames.authSession, - { - id: varchar("id", { length: 191 }).notNull(), - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - activeExpires: bigint("active_expires", { mode: "number" }).notNull(), - idleExpires: bigint("idle_expires", { mode: "number" }).notNull(), - }, - (table) => { - return { - userIdIdx: index("authSession_user_id_idx").on(table.userId), - authSessionId: primaryKey(table.id), - authSessionIdKey: unique("authSession_id_key").on(table.id), - }; - } -); - -export const authUser = mysqlTable( - tableNames.authUser, - { - id: varchar("id", { length: 191 }).notNull(), - avatarUrl: varchar("avatar_url", { length: 191 }), - bannerUrl: varchar("banner_url", { length: 191 }), - username: varchar("username", { length: 191 }).notNull(), - usernameColour: varchar("username_colour", { length: 191 }), - email: varchar("email", { length: 191 }).notNull(), - emailVerified: int("email_verified").default(0).notNull(), - pronouns: varchar("pronouns", { length: 191 }), - verified: int("verified").default(0).notNull(), - bio: varchar("bio", { length: 191 }).default(""), - dateJoined: datetime("date_joined", { - mode: "string", - fsp: 3, - }).notNull(), - roleFlags: int("role_flags").default(1).notNull(), - selfAssignableRoleFlags: int("self_assignable_role_flags"), - }, - (table) => { - return { - authUserId: primaryKey(table.id), - authUserIdKey: unique("authUser_id_key").on(table.id), - authUserUsernameKey: unique("authUser_username_key").on( - table.username - ), - authUserEmailKey: unique("authUser_email_key").on(table.email), - }; - } -); - -export const emailVerificationToken = mysqlTable( - tableNames.emailVerificationToken, - { - id: varchar("id", { length: 191 }).notNull(), - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - expires: bigint("expires", { mode: "number" }).notNull(), - }, - (table) => { - return { - userIdIdx: index("emailVerificationToken_user_id_idx").on( - table.userId - ), - emailVerificationTokenId: primaryKey(table.id), - emailVerificationTokenIdKey: unique( - "emailVerificationToken_id_key" - ).on(table.id), - }; - } -); - -export const follower = mysqlTable( - tableNames.follower, - { - id: varchar("id", { length: 191 }).notNull(), - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - }, - (table) => { - return { - userIdIdx: index("follower_user_id_idx").on(table.userId), - followerId: primaryKey(table.id), - followerIdKey: unique("follower_id_key").on(table.id), - }; - } -); - -export const following = mysqlTable( - tableNames.following, - { - id: varchar("id", { length: 191 }).notNull(), - userId: varchar("user_id", { length: 191 }).notNull(), - }, - (table) => { - return { - userIdIdx: index("following_user_id_idx").on(table.userId), - followingId: primaryKey(table.id), - followingIdKey: unique("following_id_key").on(table.id), - }; - } -); - -export const games = mysqlTable( - tableNames.games, - { - id: int("id").autoincrement().notNull(), - name: varchar("name", { length: 191 }).notNull(), - assetCount: int("asset_count").default(0).notNull(), - assetCategories: varchar("asset_categories", { length: 191 }) // minor inconvenience - .default("") - .notNull(), - lastUpdated: datetime("last_updated", { - mode: "string", - fsp: 3, - }).notNull(), - categoryCount: int("category_count").default(0).notNull(), - }, - (table) => { - return { - idIdx: index("games_id_idx").on(table.id), - nameIdx: index("games_name_idx").on(table.name), - gamesId: primaryKey(table.id), - }; - } -); - -export const passwordResetToken = mysqlTable( - tableNames.passwordResetToken, - { - id: varchar("id", { length: 191 }).notNull(), - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - expires: bigint("expires", { mode: "number" }).notNull(), - }, - (table) => { - return { - userIdIdx: index("passwordResetToken_user_id_idx").on(table.userId), - passwordResetTokenId: primaryKey(table.id), - passwordResetTokenIdKey: unique("passwordResetToken_id_key").on( - table.id - ), - }; - } -); - -export const savedOcGenerators = mysqlTable( - tableNames.savedOcGenerators, - { - id: int("id").autoincrement().notNull(), - game: varchar("game", { length: 191 }).notNull(), - data: varchar("data", { length: 191 }).notNull(), // data will be stored in opt index -> response index e.g 1:1,2:4,3:1,4:2 - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - savedDate: datetime("saved_date", { mode: "string", fsp: 3 }).notNull(), - }, - (table) => { - return { - idIdx: index("savedOCGenerators_id_idx").on(table.id), - gameIdx: index("savedOCGenerators_game_idx").on(table.game), - userIdIdx: index("savedOCGenerators_user_id_idx").on(table.userId), - savedOcGeneratorsId: primaryKey(table.id), - }; - } -); - -export const socialsConnection = mysqlTable( - tableNames.socialsConnection, - { - id: varchar("id", { length: 191 }).notNull(), - userId: varchar("user_id", { length: 191 }) - .notNull() - .references(() => authUser.id), - tiktok: varchar("tiktok", { length: 191 }), - discord: varchar("discord", { length: 191 }), - }, - (table) => { - return { - userIdIdx: index("socialsConnection_user_id_idx").on(table.userId), - socialsConnectionId: primaryKey(table.id), - socialsConnectionIdKey: unique("socialsConnection_id_key").on( - table.id - ), - }; - } -); diff --git a/src/index.ts b/src/index.ts index 0eb56934..20687590 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,39 +1,38 @@ -import { Hono } from "hono"; -import { Env } from "./worker-configuration"; -import assetRoute from "./routes/asset/assetRoute"; -import discordRoute from "./routes/discord/discordRoute"; -import ocGeneratorRoute from "./routes/oc-generators/ocGeneratorRoutes"; -import assetSearchRoute from "./routes/search/asset/searchRoute"; -import gamesRoute from "./routes/games/gamesRoute"; -import userRoute from "./routes/user/userRoute"; -import authRoute from "./routes/auth/authRoute"; -interface Bindings extends Env { - [key: string]: unknown; -} +import { Hono } from "hono" +import assetRoute from "./v2/routes/asset/assetRoute" +import discordRoute from "./v2/routes/discord/discordRoute" +import ocGeneratorRoute from "./v2/routes/oc-generators/ocGeneratorRoutes" +import gamesRoute from "./v2/routes/games/gamesRoute" +import authRoute from "./v2/routes/auth/authRoute" +import searchRoute from "./v2/routes/search/searchRoute" +import { getRuntimeKey } from "hono/adapter" +import { Bindings } from "@/worker-configuration" -const app = new Hono<{ Bindings: Bindings }>(); +const app = new Hono<{ Bindings: Bindings }>() app.get("/status", (c) => { - c.status(200); - return c.json({ status: "ok" }); -}); + c.status(200) + return c.json({ + status: "ok", + runtime: getRuntimeKey(), + }) +}) app.get("/", (c) => { - c.status(200); - return c.json({ success: "true", status: "ok", routes: app.routes }); -}); -app.route("/asset", assetRoute); -app.route("/discord", discordRoute); -app.route("/oc-generators", ocGeneratorRoute); -app.route("/search/assets", assetSearchRoute); -app.route("/games", gamesRoute); -app.route("/user", userRoute); -app.route("/auth", authRoute); + c.status(200) + return c.json({ success: "true", status: "ok", routes: app.routes }) +}) +app.route("/v2/asset", assetRoute) +app.route("/v2/discord", discordRoute) +app.route("/v2/oc-generators", ocGeneratorRoute) +app.route("/v2/search", searchRoute) +app.route("/v2/games", gamesRoute) +app.route("/v2/auth", authRoute) app.all("*", (c) => { - c.status(404); - return c.json({ status: "not found" }); -}); + c.status(404) + return c.json({ status: "not found" }) +}) // https://hono.dev/api/hono#showroutes -app.showRoutes(); +app.showRoutes() -export default app; +export default app diff --git a/src/lib/auth/lucia.ts b/src/lib/auth/lucia.ts deleted file mode 100644 index 093be05b..00000000 --- a/src/lib/auth/lucia.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { lucia } from "lucia"; -import { hono } from "lucia/middleware"; -import { planetscale } from "@lucia-auth/adapter-mysql"; -import { getConnection } from "../planetscale"; -import { Env } from "@/worker-configuration"; -import { tableNames } from "../drizzle"; - -// this is so we can pass in env during requests, -// so: it would be called: auth(c.env)... instead of auth -export const auth = (env: Env) => { - const db = getConnection(env); - const connection = db.planetscale; - - return lucia({ - adapter: planetscale(connection, { - key: tableNames.authKey, - user: tableNames.authUser, - session: tableNames.authSession, - }), - middleware: hono(), - sessionExpiresIn: { - idlePeriod: 0, - activePeriod: 30 * 24 * 60 * 60 * 1000, // 30 days - }, - env: env.ENVIRONMENT === "DEV" ? "DEV" : "PROD", - experimental: { - debugMode: env.ENVIRONMENT === "DEV" ? true : false, - }, - // csrfProtection: { - // baseDomain: env.ENVIRONMENT === "DEV" ? "localhost" : "wanderer.moe", - // allowedSubDomains: ["*"], - // }, - getUserAttributes: (dbUser) => { - return { - username: dbUser.username, - usernameColour: dbUser.username_colour, - avatarUrl: dbUser.avatar_url, - bannerUrl: dbUser.banner_url, - email: dbUser.email, - emailVerified: dbUser.email_verified, - pronouns: dbUser.pronouns, - verified: dbUser.verified, - bio: dbUser.bio, - roleFlags: dbUser.role_flags, - selfAssignableRoleFlags: dbUser.self_assignable_role_flags, - dateJoined: dbUser.date_joined, - }; - }, - getSessionAttributes: (dbSession) => { - return { - userAgentHash: dbSession.user_agent_hash as unknown as string, // md5 - countryCode: dbSession.country_code as string, - }; - }, - }); -}; - -export type Auth = typeof auth; diff --git a/src/lib/auth/roleFlags.ts b/src/lib/auth/roleFlags.ts deleted file mode 100644 index 8d088b9c..00000000 --- a/src/lib/auth/roleFlags.ts +++ /dev/null @@ -1,42 +0,0 @@ -// bitwise for role flags allows for multiple roles to be assigned to a user, and for easy checking of roles - -// permission based roles -export const roleFlags = { - USER: 1 << 0, - CONTRIBUTOR: 1 << 1, - TRANSLATOR: 1 << 2, - STAFF: 1 << 3, - DEVELOPER: 1 << 4, - CREATOR: 1 << 5, -}; - -export const roleFlagsToArray = (roleFlagsInt: number): string[] => { - const roles: string[] = []; - - for (const [role, flag] of Object.entries(roleFlags)) { - if (roleFlagsInt & flag) roles.push(role); - } - - return roles; -}; - -// self assignable roles -export const SelfAssignableRoleFlags = { - CONTENT_CREATOR: 1 << 0, - ARTIST: 1 << 1, - WRITER: 1 << 2, - DEVELOPER: 1 << 3, - DESIGNER: 1 << 4, -}; - -export const SelfAssignableRoleFlagsToArray = ( - roleFlagsInt: number -): string[] => { - const roles: string[] = []; - - for (const [role, flag] of Object.entries(SelfAssignableRoleFlags)) { - if (roleFlagsInt & flag) roles.push(role); - } - - return roles; -}; diff --git a/src/lib/discord.ts b/src/lib/discord.ts deleted file mode 100644 index 8dac8b4b..00000000 --- a/src/lib/discord.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const roles: { [key: string]: string } = { - "982387185259012096": "Project Lead", - "1112182244572938310": "Developer", - "1038892176479895584": "Admin", - "1000916582538674249": "Senior Moderator", - "983817984923562034": "Moderator", - "1088105796908355584": "Translator", - "1005805438031364129": "Contributor", - "983883539772751912": "Server Booster", -}; - -export const guildId = "982385887000272956"; diff --git a/src/lib/drizzle.ts b/src/lib/drizzle.ts deleted file mode 100644 index d5deacdf..00000000 --- a/src/lib/drizzle.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const tableNames = { - assets: "assets", - authKey: "authKey", - authSession: "authSession", - authUser: "authUser", - emailVerificationToken: "emailVerificationToken", - follower: "follower", - following: "following", - games: "games", - passwordResetToken: "passwordResetToken", - savedOcGenerators: "savedOcGenerators", - socialsConnection: "socialsConnection", -}; - -export * as schema from "@/db/migrations/schema"; diff --git a/src/lib/helpers/responses/notFoundResponse.ts b/src/lib/helpers/responses/notFoundResponse.ts deleted file mode 100644 index 4818a796..00000000 --- a/src/lib/helpers/responses/notFoundResponse.ts +++ /dev/null @@ -1,14 +0,0 @@ -// helper function to create a 404 Not Found response -export function createNotFoundResponse(errorMessage, responseHeaders) { - return new Response( - JSON.stringify({ - success: false, - status: "error", - error: errorMessage, - }), - { - status: 404, - headers: responseHeaders, - } - ); -} diff --git a/src/lib/planetscale.ts b/src/lib/planetscale.ts deleted file mode 100644 index 2e866293..00000000 --- a/src/lib/planetscale.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from "@planetscale/database"; -import { Env } from "../worker-configuration"; -import * as schema from "@/db/migrations/schema"; -import { drizzle } from "drizzle-orm/planetscale-serverless"; - -// useful wrapper for planetscale connection -export function getConnection(env: Env) { - const client = connect({ - // this can be set with "wrangler secret put" or through the planetscale integration on cf dashboard - host: env.DATABASE_HOST, - username: env.DATABASE_USERNAME, - password: env.DATABASE_PASSWORD, - fetch: (url, init) => { - delete init["cache"]; - return fetch(url, init); - }, - }); - - const db = drizzle(client, { - schema, - logger: true, - }); - - return { - drizzle: db, - planetscale: client, - }; -} diff --git a/src/lib/query.ts b/src/lib/query.ts deleted file mode 100644 index 811a10be..00000000 --- a/src/lib/query.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { Asset } from "@/lib/types/asset"; -import { getConnection } from "@/lib/planetscale"; - -type queryParameter = string | number; - -export const getSearchResults = async ( - query: string, - gameArray: string[], - assetArray: string[], - tagsArray: string[], - c -): Promise => { - let sqlQuery = `SELECT * FROM assets WHERE 1=1`; - const parameters = []; - - sqlQuery = addQueryToSqlQuery(query, sqlQuery, parameters); - sqlQuery = addGameToSqlQuery(gameArray, sqlQuery, parameters); - sqlQuery = addAssetToSqlQuery(assetArray, sqlQuery, parameters); - sqlQuery = addTagsToSqlQuery(tagsArray, sqlQuery, parameters); - - sqlQuery += ` ORDER BY uploaded_date DESC`; - - sqlQuery = limitResults(sqlQuery); - - if ( - !query && - !gameArray.length && - !assetArray.length && - !tagsArray.length - ) { - sqlQuery = `SELECT * FROM assets ORDER BY uploaded_date DESC LIMIT 30`; - } - - const conn = await getConnection(c.env); - const db = conn.planetscale; - - return await db - .execute(sqlQuery, parameters) - .then((row) => row.rows as Asset[]); -}; - -const addQueryToSqlQuery = ( - query: string, - sqlQuery: string, - parameters: queryParameter[] -): string => { - if (query) { - sqlQuery += ` AND name LIKE ?`; - parameters.push(`%${query}%`); - } - return sqlQuery; -}; - -const addGameToSqlQuery = ( - gameArray: string[], - sqlQuery: string, - parameters: queryParameter[] -): string => { - if (gameArray.length) { - sqlQuery += ` AND game IN (${gameArray.map(() => "?").join(",")})`; - parameters.push(...gameArray); - } - return sqlQuery; -}; - -const addAssetToSqlQuery = ( - assetArray: string[], - sqlQuery: string, - parameters: queryParameter[] -): string => { - if (assetArray.length) { - sqlQuery += ` AND asset_category IN (${assetArray - .map(() => "?") - .join(",")})`; - parameters.push(...assetArray); - } - return sqlQuery; -}; - -const addTagsToSqlQuery = ( - tagsArray: string[], - sqlQuery: string, - parameters: queryParameter[] -): string => { - if (tagsArray.length) { - sqlQuery += ` AND tags IN (${tagsArray - .map(() => "?") - .join(",") - .toUpperCase()})`; - parameters.push(...tagsArray); - } - return sqlQuery; -}; - -const limitResults = (sqlQuery: string): string => { - return (sqlQuery += ` LIMIT 1500`); -}; diff --git a/src/lib/resend/email.ts b/src/lib/resend/email.ts deleted file mode 100644 index ea955cd9..00000000 --- a/src/lib/resend/email.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Resend } from "resend"; - -// TODO: use react email w/ tailwind -export const resend = new Resend(""); - -export const sendPasswordResetEmail = async ( - email: string, - link: string, - username: string -) => { - try { - await resend.emails.send({ - from: "Test ", - to: email, - subject: "Password Reset Request", - html: `Password reset for ${username}
Click here to reset your password`, - }); - } catch (error) { - throw new Error("Error sending password reset email."); - } -}; - -export const sendPasswordChangeEmail = async ( - email: string, - username: string -) => { - try { - await resend.emails.send({ - from: "Test ", - to: email, - subject: "Password Updated Confirmation", - html: `Your password for ${username} has been updated.
Wasn't you? Contact us at support@wanderer.moe`, - }); - } catch (error) { - throw new Error("Error sending password change email."); - } -}; - -export const sendEmailChangeEmail = async (email: string, username: string) => { - try { - await resend.emails.send({ - from: "Test ", - to: email, - subject: "Email Change Request", - html: `Your email address for ${username} has been changed.
Wasn't you? Contact us at support@wanderer.moe`, - }); - } catch (error) { - throw new Error("Error sending email change email."); - } -}; - -export const sendEmailConfirmationEmail = async ( - email: string, - link: string, - username: string -) => { - try { - await resend.emails.send({ - from: "Test ", - to: email, - subject: "Email Confirmation", - html: `Email confirmation for ${username}
Click here to confirm your email`, - }); - } catch (error) { - throw new Error("Error sending email confirmation email."); - } -}; diff --git a/src/lib/responseHeaders.ts b/src/lib/responseHeaders.ts deleted file mode 100644 index a913dc9c..00000000 --- a/src/lib/responseHeaders.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const responseHeaders: Record = { - "X-Content-Type-Options": "nosniff", - "Referrer-Policy": "strict-origin-when-cross-origin", - "content-type": "application/json;charset=UTF-8", - "access-control-allow-origin": "*", -}; diff --git a/src/lib/types/asset.ts b/src/lib/types/asset.ts deleted file mode 100644 index 94e1cd32..00000000 --- a/src/lib/types/asset.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface Asset { - id: number; - name: string; - game: string; - asset_category: string; - tags: string; - url: string; - status: string; - uploaded_by: string; - uploaded_date: string; - view_count?: number; - download_count?: number; - file_size: number; - width: number; - height: number; -} diff --git a/src/lib/types/discord.ts b/src/lib/types/discord.ts deleted file mode 100644 index a0bc932e..00000000 --- a/src/lib/types/discord.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface Contributor { - id: string; - username: string; - globalname: string | null; - avatar: string; - roles: string[]; -} - -export interface GuildMember { - roles: string[]; - user: { - id: string; - username: string; - global_name: string | null; - avatar: string; - }; -} diff --git a/src/lib/types/game.ts b/src/lib/types/game.ts deleted file mode 100644 index 8f915c41..00000000 --- a/src/lib/types/game.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface Game { - id: number; - name: string; - asset_count: number; - asset_categories: string; // comma separated category1,category2,category3 - category_count: number; - last_updated: string; - has_generator: boolean; -} diff --git a/src/lib/types/user.ts b/src/lib/types/user.ts deleted file mode 100644 index 2743d0e4..00000000 --- a/src/lib/types/user.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface User { - id: string; - username: string; - username_colour: string | null; - avatar_url: string | null; - banner_url: string | null; - email: string; - email_verified: number; - pronouns: string | null; - verified: number; - bio: string | null; - role_flags: number; - self_assignable_role_flags: number; - date_joined: Date; -} diff --git a/src/lib/validate/regexValidation.ts b/src/lib/validate/regexValidation.ts deleted file mode 100644 index 6f3cf132..00000000 --- a/src/lib/validate/regexValidation.ts +++ /dev/null @@ -1,18 +0,0 @@ -// must have @ and . inside to be considered an email -export function email(email: string) { - const emailRegex = /\S+@\S+\.\S+/; - return emailRegex.test(email); -} - -// minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character -export function password(password: string) { - const passwordRegex = - /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/; - return passwordRegex.test(password); -} - -// 5 to 16 characters which contain only characters, numeric digits, underscore and first character must be a letter -export function username(username: string) { - const usernameRegex = /^[a-zA-Z0-9]{5,16}$/; - return usernameRegex.test(username); -} diff --git a/src/lucia.d.ts b/src/lucia.d.ts index 10ac99de..5e3d8e74 100644 --- a/src/lucia.d.ts +++ b/src/lucia.d.ts @@ -1,22 +1,23 @@ /// declare namespace Lucia { - type Auth = import("./lib/auth/lucia").Auth; - type DatabaseUserAttributes = { - username: string; - username_colour: string | null; - avatar_url: string | null; - banner_url: string | null; - email: string; - email_verified: number; - pronouns: string | null; - verified: number; - bio: string | null; - role_flags: number; - self_assignable_role_flags: number | null; - date_joined: Date; - }; - type DatabaseSessionAttributes = { - country_code: string; - user_agent_hash: ArrayBuffer; - }; + type Auth = import("./v2/lib/auth/lucia").Auth + type DatabaseUserAttributes = { + username: string + username_colour: string | null + avatar_url: string | null + banner_url: string | null + email: string + email_verified: number + pronouns: string | null + is_contributor: number + verified: number + bio: string | null + role_flags: number + self_assignable_role_flags: number | null + date_joined: number + } + type DatabaseSessionAttributes = { + country_code: string + user_agent: string + } } diff --git a/src/routes/asset/assetRoute.ts b/src/routes/asset/assetRoute.ts deleted file mode 100644 index ef48672d..00000000 --- a/src/routes/asset/assetRoute.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Hono } from "hono"; -import { getAssetFromId } from "./getAssetFromId"; -import { downloadAsset } from "./downloadAsset"; - -const assetRoute = new Hono(); - -assetRoute.get("/:id", async (c) => { - return getAssetFromId(c); -}); - -// setting both of these to id returns "duplicate param name" error, will fix later -assetRoute.get("/download/:assetId", async (c) => { - return downloadAsset(c); -}); - -export default assetRoute; diff --git a/src/routes/asset/downloadAsset.ts b/src/routes/asset/downloadAsset.ts deleted file mode 100644 index 56f10ebd..00000000 --- a/src/routes/asset/downloadAsset.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import type { Asset } from "@/lib/types/asset"; -import { getConnection } from "@/lib/planetscale"; -import { createNotFoundResponse } from "@/lib/helpers/responses/notFoundResponse"; -import { Context } from "hono"; - -export const downloadAsset = async (c: Context) => { - const { assetId } = c.req.param(); - - const conn = await getConnection(c.env); - const db = conn.planetscale; - - const row = await db - .execute("SELECT * FROM assets WHERE id = ?", [assetId]) - .then((row) => row.rows[0] as Asset | undefined); - - if (!row) - return createNotFoundResponse("Asset ID not found", responseHeaders); - - const response = await fetch( - `https://files.wanderer.moe/assets/${row.url}` - ); - - const headers = new Headers(response.headers); - headers.set("Content-Disposition", `attachment; filename="${row.name}"`); - const blob = await response.blob(); - - await db.execute( - "UPDATE assets SET download_count = download_count + 1 WHERE id = ?", - [assetId] - ); - - return new Response(blob, { - headers: headers, - }); -}; diff --git a/src/routes/asset/getAssetFromId.ts b/src/routes/asset/getAssetFromId.ts deleted file mode 100644 index 564fcd58..00000000 --- a/src/routes/asset/getAssetFromId.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import type { Asset } from "@/lib/types/asset"; -import { getConnection } from "@/lib/planetscale"; -import { createNotFoundResponse } from "@/lib/helpers/responses/notFoundResponse"; - -export const getAssetFromId = async (c) => { - const { id } = c.req.param(); - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - - if (response) return response; - - const conn = await getConnection(c.env); - const db = conn.planetscale; - - const row = await db - .execute("SELECT * FROM assets WHERE id = ?", [id]) - .then((row) => row.rows[0] as Asset | undefined); - - if (!row) - return createNotFoundResponse("Asset ID not found", responseHeaders); - - const asset = { - id: row.id, - name: row.name, - game: row.game, - asset_category: row.asset_category, - url: row.url, - tags: row.tags, - status: row.status, - uploaded_by: row.uploaded_by, - uploaded_date: row.uploaded_date, - file_size: row.file_size, - width: row.width, - height: row.height, - }; - - const similarAssets = ( - await db - .execute( - "SELECT * FROM assets WHERE game = ? AND asset_category = ? AND id != ? ORDER BY RAND() LIMIT 4", - [row.game, row.asset_category, row.id] - ) - .then((row) => row.rows as Asset[]) - ).map((asset) => { - return { - id: asset.id, - name: asset.name, - game: asset.game, - asset_category: asset.asset_category, - url: asset.url, - tags: asset.tags, - status: asset.status, - uploaded_by: asset.uploaded_by, - uploaded_date: asset.uploaded_date, - file_size: asset.file_size, - width: asset.width, - height: asset.height, - }; - }); - - await db.execute( - "UPDATE assets SET view_count = view_count + 1 WHERE id = ?", - [id] - ); - - response = c.json( - { - success: true, - status: "ok", - asset, - similarAssets, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=604800"); - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/auth/authRoute.ts b/src/routes/auth/authRoute.ts deleted file mode 100644 index 99e8a456..00000000 --- a/src/routes/auth/authRoute.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Hono } from "hono"; -import { login } from "./login"; -import { logout } from "./logout"; -import { signup } from "./signup"; -import { cors } from "hono/cors"; -import { validate } from "./validate"; - -const authRoute = new Hono(); - -authRoute.use( - "*", - cors({ - credentials: true, - origin: ["https://next.wanderer.moe"], - }) -); - -authRoute.post("/login", async (c) => { - return login(c); -}); - -authRoute.post("/signup", async (c) => { - return signup(c); -}); - -authRoute.get("/validate", async (c) => { - return validate(c); -}); - -authRoute.post("/logout", async (c) => { - return logout(c); -}); - -export default authRoute; diff --git a/src/routes/auth/login.ts b/src/routes/auth/login.ts deleted file mode 100644 index 2f2dee45..00000000 --- a/src/routes/auth/login.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { auth } from "@/lib/auth/lucia"; -import { Context } from "hono"; -// import * as validate from "@/lib/regex/accountValidation"; - -const usernameThrottling = new Map< - string, - { - timeoutUntil: number; - timeoutSeconds: number; - } ->(); - -export const login = async (c: Context): Promise => { - const formData = await c.req.formData(); - - const username = formData.get("username") as string; - const password = formData.get("password") as string; - // console.log(username, password); - - const validSession = await auth(c.env).handleRequest(c).validate(); - - if (validSession) - return c.json({ success: false, state: "already logged in" }, 200); - - const storedThrottling = usernameThrottling.get(username); - const timeoutUntil = storedThrottling?.timeoutUntil ?? 0; - - if (timeoutUntil > Date.now()) { - return c.json( - { - success: false, - status: "error", - error: `Too many login attempts - wait ${ - (timeoutUntil - Date.now()) / 1000 - } seconds`, - }, - 400 - ); - } - - const user = await auth(c.env).useKey( - "username", - username.toLowerCase(), - password - ); - - if (!user) { - const timeoutSeconds = storedThrottling - ? storedThrottling.timeoutSeconds * 2 - : 1; - usernameThrottling.set(username, { - timeoutUntil: Date.now() + timeoutSeconds * 1000, - timeoutSeconds, - }); - return c.json( - { - success: false, - status: "error", - error: `Invalid credentials - wait ${timeoutSeconds} seconds`, - }, - 400 - ); - } - - const userAgentHash = await crypto.subtle.digest( - { name: "MD5" }, - new TextEncoder().encode(c.req.headers.get("user-agent") ?? "") - ); - const countryCode = c.req.headers.get("cf-ipcountry") ?? ""; - - const newSession = await auth(c.env).createSession({ - userId: user.userId, - attributes: { - country_code: countryCode, - user_agent_hash: userAgentHash, - }, - }); - - const authRequest = await auth(c.env).handleRequest(c); - authRequest.setSession(newSession); - - return c.json({ success: true, state: "logged in" }, 200); -}; diff --git a/src/routes/auth/logout.ts b/src/routes/auth/logout.ts deleted file mode 100644 index 624c2637..00000000 --- a/src/routes/auth/logout.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { auth } from "@/lib/auth/lucia"; -import { Context } from "hono"; - -export const logout = async (c: Context): Promise => { - const authRequest = auth(c.env).handleRequest(c); - const session = await authRequest.validate(); - - if (!session) { - return c.json({ success: false, state: "invalid session" }, 200); - } - - await auth(c.env).invalidateSession(session.sessionId); - authRequest.setSession(null); - - return c.json({ success: true, state: "logged out" }, 200); -}; diff --git a/src/routes/auth/signup.ts b/src/routes/auth/signup.ts deleted file mode 100644 index 6a5d5d1c..00000000 --- a/src/routes/auth/signup.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { auth } from "@/lib/auth/lucia"; -import { Context } from "hono"; -// import * as validate from "@/lib/regex/accountValidation"; - -export const signup = async (c: Context) => { - const formData = await c.req.formData(); - - const secretKeyRequiredForSignup = c.env.VERY_SECRET_SIGNUP_KEY; - - const username = formData.get("username") as string; - const email = formData.get("email") as string; - const password = formData.get("password") as string; - const passwordConfirm = formData.get("passwordConfirm") as string; - const secretKey = formData.get("secretKey") as string; - // console.log(username, email, password, passwordConfirm); - - const validSession = await auth(c.env).handleRequest(c).validate(); - if (validSession) - return c.json({ success: false, state: "already logged in" }, 200); - - if ( - secretKeyRequiredForSignup !== secretKey || - passwordConfirm !== password - ) { - return c.json( - { - success: false, - status: "error", - error: "Invalid credentials", - }, - 400 - ); - } - - console.log("creating user"); - - try { - const user = await auth(c.env).createUser({ - key: { - providerId: "username", - providerUserId: username.toLowerCase(), - password, - }, - attributes: { - username, - email, - email_verified: 0, - date_joined: new Date(), - verified: 0, - role_flags: 1, - self_assignable_role_flags: null, - username_colour: null, - avatar_url: null, - banner_url: null, - pronouns: null, - bio: null, - }, - }); - - const userAgentHash = await crypto.subtle.digest( - { name: "MD5" }, - new TextEncoder().encode(c.req.headers.get("user-agent") ?? "") - ); - const countryCode = c.req.headers.get("cf-ipcountry") ?? ""; - - const newSession = await auth(c.env).createSession({ - userId: user.userId, - attributes: { - country_code: countryCode, - user_agent_hash: userAgentHash, - }, - }); - - const authRequest = auth(c.env).handleRequest(c); - authRequest.setSession(newSession); - return c.json({ success: true, state: "logged in" }, 200); - } catch (e) { - console.log(e); - return c.json( - { - success: false, - status: "error", - error: "Error creating user", - }, - 500 - ); - } -}; diff --git a/src/routes/auth/validate.ts b/src/routes/auth/validate.ts deleted file mode 100644 index 39e4ef5e..00000000 --- a/src/routes/auth/validate.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { auth } from "@/lib/auth/lucia"; -import { Context } from "hono"; - -export const validate = async (c: Context): Promise => { - console.log(c); - const authRequest = auth(c.env).handleRequest(c); - - // console.log("validate") - // console.log(authRequest); - - const session = await authRequest.validate(); - - const userAgentHash = await crypto.subtle.digest( - { name: "MD5" }, - new TextEncoder().encode(c.req.headers.get("user-agent") ?? "") - ); - const countryCode = c.req.headers.get("cf-ipcountry") ?? ""; - - if (!session) { - authRequest.setSession(null); - return c.json({ success: false, state: "invalid session" }, 200); - } - - if ( - session.userAgentHash !== userAgentHash || - session.countryCode !== countryCode - ) { - await auth(c.env).invalidateSession(session.sessionId); - authRequest.setSession(null); - return c.json({ success: false, state: "invalid session" }, 200); - } - - if (session.state === "idle") { - await auth(c.env).invalidateSession(session.sessionId); - authRequest.setSession(null); - return c.json({ success: false, state: "invalid session" }, 200); - } - - return c.json({ success: true, state: "valid session", session }, 200); -}; diff --git a/src/routes/discord/contributors.ts b/src/routes/discord/contributors.ts deleted file mode 100644 index 275b0880..00000000 --- a/src/routes/discord/contributors.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import { roles, guildId } from "@/lib/discord"; -import type { Contributor, GuildMember } from "@/lib/types/discord"; -import { Context } from "hono"; - -export const contributors = async (c: Context) => { - const members: Contributor[] = []; - - let after: string | null = null; - let fetchUsers = true; - - while (fetchUsers) { - const response = await fetch( - `https://discord.com/api/guilds/${guildId}/members?limit=1000${ - after ? `&after=${after}` : "" - }`, - { - headers: { - Authorization: `Bot ${c.env.DISCORD_TOKEN}`, - }, - } - ); - - const guildMembers: GuildMember[] = await response.json(); - - const filteredMembers: GuildMember[] = guildMembers.filter((member) => { - return member.roles.some((role) => - Object.keys(roles).includes(role) - ); - }); - - const contributors: Contributor[] = filteredMembers.map((member) => { - const rolesArray = member.roles - .map((role) => roles[role]) - .filter((role) => role); - - // TODO: support animated avatars - return { - id: member.user.id, - username: member.user.username, - globalname: member.user.global_name || null, - avatar: `https://cdn.discordapp.com/avatars/${member.user.id}/${member.user.avatar}.webp`, - roles: rolesArray, - }; - }); - - members.push(...contributors); - - if ( - !guildMembers.length || - !guildMembers[guildMembers.length - 1].user - ) { - fetchUsers = false; - } - - after = guildMembers[guildMembers.length - 1]?.user?.id; - } - - return c.json( - { - success: true, - status: "ok", - contributors: members, - }, - 200, - responseHeaders - ); -}; diff --git a/src/routes/discord/discordRoute.ts b/src/routes/discord/discordRoute.ts deleted file mode 100644 index fbe37d06..00000000 --- a/src/routes/discord/discordRoute.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Hono } from "hono"; -import { contributors } from "./contributors"; - -const discordRoute = new Hono(); - -discordRoute.get("/contributors", async (c) => { - return contributors(c); -}); - -export default discordRoute; diff --git a/src/routes/games/allGames.ts b/src/routes/games/allGames.ts deleted file mode 100644 index 104efcf3..00000000 --- a/src/routes/games/allGames.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import { getConnection } from "@/lib/planetscale"; -import { Context } from "hono"; -import { listBucket } from "@/lib/listBucket"; -import { Game } from "@/lib/types/game"; - -export const getAllGames = async (c: Context) => { - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - - if (response) return response; - - const files = await listBucket(c.env.bucket, { - prefix: "oc-generators/", - delimiter: "/", - }); - - const results = files.delimitedPrefixes.map((file) => { - return { - name: file.replace("oc-generators/", "").replace("/", ""), - }; - }); - - const conn = await getConnection(c.env); - const db = conn.planetscale; - - const gameList = await db - .execute("SELECT * FROM games ORDER BY asset_count DESC") - .then((row) => - row.rows.map((game: Game) => ({ - ...game, - // asset categories are stored as a comma separated string in the database, so we need to split them into an array - asset_categories: game.asset_categories.split(","), - has_generator: results.some( - (generator) => generator.name === game.name - ), - })) - ); - - response = c.json( - { - success: true, - status: "ok", - results: gameList, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=1200"); - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/games/gamesRoute.ts b/src/routes/games/gamesRoute.ts deleted file mode 100644 index cc18fa3e..00000000 --- a/src/routes/games/gamesRoute.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Hono } from "hono"; -import { getAllGames } from "./allGames"; - -const gamesRoute = new Hono(); - -gamesRoute.get("/all", async (c) => { - return getAllGames(c); -}); - -export default gamesRoute; diff --git a/src/routes/oc-generators/getGenerator.ts b/src/routes/oc-generators/getGenerator.ts deleted file mode 100644 index 330d194f..00000000 --- a/src/routes/oc-generators/getGenerator.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import { listBucket } from "@/lib/listBucket"; -import { createNotFoundResponse } from "@/lib/helpers/responses/notFoundResponse"; -import { Context } from "hono"; - -export const getGeneratorFromName = async (c: Context) => { - const { gameName } = c.req.param(); - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - - if (response) return response; - - const files = await listBucket(c.env.bucket, { - prefix: `oc-generators/${gameName}/list.json`, - }); - - if (files.objects.length === 0) - return createNotFoundResponse("Generator not found", responseHeaders); - - const data = await fetch( - `https://files.wanderer.moe/${files.objects[0].key}` - ); - - const generatorData = await data.json(); - - response = c.json( - { - status: "ok", - data: generatorData, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=604800"); // the content of this file is unlikely to change, so caching is fine - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/oc-generators/getGenerators.ts b/src/routes/oc-generators/getGenerators.ts deleted file mode 100644 index df2125a1..00000000 --- a/src/routes/oc-generators/getGenerators.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import { listBucket } from "@/lib/listBucket"; -import { Context } from "hono"; - -export const getGenerators = async (c: Context) => { - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - - if (response) return response; - - // listing all files inside of oc-generators subfolder, as they can't be manually inputted - // by users but instead stored on the oc-generators repo - const files = await listBucket(c.env.bucket, { - prefix: "oc-generators/", - delimiter: "/", - }); - - // console.log(files); - - const results = files.delimitedPrefixes.map((file) => { - return { - name: file.replace("oc-generators/", "").replace("/", ""), - path: `/oc-generators/${file - .replace("oc-generators/", "") - .replace("/", "")}`, - }; - }); - - response = c.json( - { - status: "ok", - data: results, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=28800"); - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/oc-generators/ocGeneratorRoutes.ts b/src/routes/oc-generators/ocGeneratorRoutes.ts deleted file mode 100644 index 7e763295..00000000 --- a/src/routes/oc-generators/ocGeneratorRoutes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Hono } from "hono"; -import { getGeneratorFromName } from "./getGenerator"; -import { getGenerators } from "./getGenerators"; - -const ocGeneratorRoute = new Hono(); - -ocGeneratorRoute.get("/", async (c) => { - return getGenerators(c); -}); - -ocGeneratorRoute.get("/:gameName", async (c) => { - return getGeneratorFromName(c); -}); - -export default ocGeneratorRoute; diff --git a/src/routes/search/asset/assetSearch.ts b/src/routes/search/asset/assetSearch.ts deleted file mode 100644 index 551f4bae..00000000 --- a/src/routes/search/asset/assetSearch.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import type { Asset } from "@/lib/types/asset"; -import { getSearchResults } from "@/lib/query"; -import { getConnection } from "@/lib/planetscale"; -import { Context } from "hono"; - -export const getAssetSearch = async (c: Context) => { - const queryParams = c.req.query(); - // console.log(queryParams); - const { query, game, asset, tags } = queryParams; - - // Convert game and asset parameters to arrays - const gameArray = game ? game.split(",") : []; - const assetArray = asset ? asset.split(",") : []; - const tagsArray = tags ? tags.split(",") : []; - - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - - if (response) return response; - - // console.log(query, gameArray, assetArray, tags); - - const results = ( - await getSearchResults(query, gameArray, assetArray, tagsArray, c) - ).map((results) => { - return { - id: results.id, - name: results.name, - game: results.game, - asset_category: results.asset_category, - url: results.url, - tags: results.tags, - status: results.status, - uploaded_by: results.uploaded_by, - uploaded_date: results.uploaded_date, - file_size: results.file_size, - width: results.width, - height: results.height, - }; - }); - - response = c.json( - { - success: true, - status: "ok", - query, - game, - asset, - tags, - results, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=3600"); - await cache.put(cacheKey, response.clone()); - - return response; -}; - -export const recentAssets = async (c) => { - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - if (response) return response; - - const conn = await getConnection(c.env); - const db = conn.planetscale; - - const row = await db - .execute( - "SELECT * FROM assets WHERE 1=1 ORDER BY uploaded_date DESC LIMIT 30" - ) - .then((row) => row.rows as Asset[] | undefined); - - if (!row) throw new Error("No results found"); - - const results = row.map((asset) => { - return { - id: asset.id, - name: asset.name, - game: asset.game, - asset_category: asset.asset_category, - url: asset.url, - tags: asset.tags, - status: asset.status, - uploaded_by: asset.uploaded_by, - uploaded_date: asset.uploaded_date, - file_size: asset.file_size, - width: asset.width, - height: asset.height, - }; - }); - - response = c.json( - { - success: true, - status: "ok", - results, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=3600"); - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/search/asset/searchRoute.ts b/src/routes/search/asset/searchRoute.ts deleted file mode 100644 index 8cf68132..00000000 --- a/src/routes/search/asset/searchRoute.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Hono } from "hono"; -import { getAssetSearch } from "./assetSearch"; -import { recentAssets } from "./assetSearch"; - -const assetSearchRoute = new Hono(); - -assetSearchRoute.get("/", async (c) => { - return getAssetSearch(c); -}); - -assetSearchRoute.get("/recent", async (c) => { - return recentAssets(c); -}); - -export default assetSearchRoute; diff --git a/src/routes/user/getUserByUsername.ts b/src/routes/user/getUserByUsername.ts deleted file mode 100644 index 2b6256dc..00000000 --- a/src/routes/user/getUserByUsername.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import type { User } from "@/lib/types/user"; -import type { Asset } from "@/lib/types/asset"; -import { Context } from "hono"; -import { getConnection } from "@/lib/planetscale"; -import { createNotFoundResponse } from "@/lib/helpers/responses/notFoundResponse"; - -export const getUserByUsername = async (c: Context) => { - const { name } = c.req.param(); - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - - if (response) return response; - - const conn = await getConnection(c.env); - const db = conn.planetscale; - - const row = await db - .execute("SELECT * FROM authUser WHERE username = ?", [name]) - .then((row) => row.rows[0] as User | undefined); - - const user = { - id: row.id, - username: row.username, - avatar_url: row.avatar_url || null, - banner_url: row.banner_url || null, - bio: row.bio || null, - pronouns: row.pronouns || null, - verified: row.verified, - date_joined: row.date_joined, - role_flags: row.role_flags, - self_assignable_role_flags: row.self_assignable_role_flags || null, - }; - - const uploadedAssets = await db - .execute( - "SELECT * FROM assets WHERE uploaded_by = ? ORDER BY uploaded_date DESC LIMIT 5", - [user.id] - ) - .then((row) => row.rows as Asset[] | undefined) - .then( - (row) => - row?.map((asset) => { - return { - id: asset.id, - name: asset.name, - game: asset.game, - assetCategory: asset.asset_category, - url: asset.url, - tags: asset.tags, - status: asset.status, - uploadedBy: asset.uploaded_by, - uploadedDate: asset.uploaded_date, - fileSize: asset.file_size, - }; - }) - ); - - if (!row) return createNotFoundResponse("User not found", responseHeaders); - - response = c.json( - { - success: true, - status: "ok", - user, - uploadedAssets, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=300"); - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/user/getUsersBySearch.ts b/src/routes/user/getUsersBySearch.ts deleted file mode 100644 index 4b412c8b..00000000 --- a/src/routes/user/getUsersBySearch.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { responseHeaders } from "@/lib/responseHeaders"; -import type { User } from "@/lib/types/user"; -import { createNotFoundResponse } from "@/lib/helpers/responses/notFoundResponse"; -import { getConnection } from "@/lib/planetscale"; -import { Context } from "hono"; - -export const getUsersBySearch = async (c: Context) => { - const cacheKey = new Request(c.req.url.toString(), c.req); - const cache = caches.default; - let response = await cache.match(cacheKey); - if (response) return response; - - const { query } = c.req.param(); - const conn = await getConnection(c.env); - const db = conn.planetscale; - - const row = await db - .execute("SELECT * FROM authUser WHERE username LIKE ?", [query]) - .then((row) => row.rows as User[] | undefined); - - if (!row) return createNotFoundResponse("User not found", responseHeaders); - - const results = row?.map((user) => { - return { - id: user.id, - username: user.username, - avatar_url: user.avatar_url || null, - banner_url: user.banner_url || null, - bio: user.bio || null, - pronouns: user.pronouns || null, - verified: user.verified, - date_joined: user.date_joined, - role_flags: user.role_flags, - }; - }); - - results.sort((a, b) => - a.username === query ? -1 : b.username === query ? 1 : 0 - ); - - response = c.json( - { - success: true, - status: "ok", - query, - results, - }, - 200, - responseHeaders - ); - - response.headers.set("Cache-Control", "s-maxage=60"); - await cache.put(cacheKey, response.clone()); - - return response; -}; diff --git a/src/routes/user/userRoute.ts b/src/routes/user/userRoute.ts deleted file mode 100644 index cdbf7e80..00000000 --- a/src/routes/user/userRoute.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Hono } from "hono"; -import { getUsersBySearch } from "./getUsersBySearch"; -import { getUserByUsername } from "./getUserByUsername"; - -const userRoute = new Hono(); - -userRoute.get("/u/:name", async (c) => { - return getUserByUsername(c); -}); - -userRoute.get("/s/:query", async (c) => { - return getUsersBySearch(c); -}); - -export default userRoute; diff --git a/src/v2/db/drizzle.ts b/src/v2/db/drizzle.ts new file mode 100644 index 00000000..2faf860f --- /dev/null +++ b/src/v2/db/drizzle.ts @@ -0,0 +1,24 @@ +export const tableNames = { + assets: "assets", + authKey: "authKey", + authSession: "authSession", + authUser: "authUser", + emailVerificationTokens: "emailVerificationToken", + follower: "follower", + following: "following", + gameAssetCategories: "gameAssetCategories", + games: "games", + assetTags: "assetTags", + assetTagsAssets: "assetTagsAssets", + emailVerificationToken: "emailVerificationToken", + passwordResetToken: "passwordResetToken", + assetCategories: "assetCategories", + savedOcGenerators: "savedOcGenerators", + userFavorites: "userFavorites", + userFavoritesAssets: "userFavoritesAssets", + userCollections: "assetCollection", + userCollectionAssets: "assetCollectionAsset", + socialsConnections: "socialsConnections", +} + +export * as schema from "@/v2/db/schema" diff --git a/src/v2/db/schema.ts b/src/v2/db/schema.ts new file mode 100644 index 00000000..a59dfdf5 --- /dev/null +++ b/src/v2/db/schema.ts @@ -0,0 +1,556 @@ +import { tableNames } from "@/v2/db/drizzle" +import { relations } from "drizzle-orm" +import { + sqliteTable, + text, + integer, + uniqueIndex, +} from "drizzle-orm/sqlite-core" + +// users, games, categories, assets etc, i will clean this up later, sorry for anyone looking at this +export const users = sqliteTable( + tableNames.authUser, + { + id: text("id").primaryKey(), + avatarUrl: text("avatar_url"), + bannerUrl: text("banner_url"), + username: text("username").notNull(), + usernameColour: text("username_colour"), + email: text("email").notNull(), + emailVerified: integer("email_verified").default(0).notNull(), + pronouns: text("pronouns"), + verified: integer("verified").default(0).notNull(), + bio: text("bio").default("No bio set").notNull(), + dateJoined: integer("date_joined").notNull(), + roleFlags: integer("role_flags").default(1).notNull(), + isContributor: integer("is_contributor").default(0).notNull(), + selfAssignableRoleFlags: integer("self_assignable_role_flags"), + }, + (user) => { + return { + userIdx: uniqueIndex("user_id_idx").on(user.id), + usernameIdx: uniqueIndex("user_username_idx").on(user.username), + emailIdx: uniqueIndex("user_email_idx").on(user.email), + } + } +) + +export const sessions = sqliteTable( + tableNames.authSession, + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + activeExpires: integer("active_expires").notNull(), + idleExpires: integer("idle_expires").notNull(), + userAgent: text("user_agent").notNull(), + countryCode: text("country_code").notNull(), + }, + (session) => { + return { + userIdx: uniqueIndex("session_user_id_idx").on(session.userId), + } + } +) + +export const keys = sqliteTable( + tableNames.authKey, + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + hashedPassword: text("hashed_password"), + }, + (key) => { + return { + userIdx: uniqueIndex("key_user_id_idx").on(key.userId), + } + } +) + +export const socialsConnections = sqliteTable( + tableNames.socialsConnections, + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + discordId: text("discord_id"), + }, + (socialsConnection) => { + return { + userIdx: uniqueIndex("socials_connection_user_id_idx").on( + socialsConnection.userId + ), + } + } +) + +export const games = sqliteTable( + tableNames.games, + { + id: text("id").primaryKey(), + name: text("name").notNull(), + formattedName: text("formatted_name").notNull(), + assetCount: integer("asset_count").default(0), + lastUpdated: integer("last_updated").notNull(), + }, + (game) => { + return { + gameIdx: uniqueIndex("game_id_idx").on(game.id), + nameIdx: uniqueIndex("game_name_idx").on(game.name), + } + } +) + +export const assetCategories = sqliteTable( + tableNames.assetCategories, + { + id: text("id").primaryKey(), + name: text("name").notNull(), // e.g genshin-impact, honkai-impact-3rd + formattedName: text("formatted_name").notNull(), // e.g Genshin Impact, Honkai Impact 3rd + assetCount: integer("asset_count").default(0).notNull(), + lastUpdated: integer("last_updated").notNull(), + }, + (assetCategory) => { + return { + assetCategoryIdx: uniqueIndex("asset_category_id_idx").on( + assetCategory.id + ), + nameIdx: uniqueIndex("asset_category_name_idx").on( + assetCategory.name + ), + } + } +) + +export const assets = sqliteTable( + tableNames.assets, + { + id: integer("id").primaryKey(), // primary key auto increments on sqlite + name: text("name").notNull(), + extension: text("extension").notNull(), + game: text("game") + .notNull() + .references(() => games.name, { + onUpdate: "cascade", + onDelete: "cascade", + }), + assetCategory: text("asset_category") + .notNull() + .references(() => assetCategories.name, { + onUpdate: "cascade", + onDelete: "cascade", + }), + url: text("url").notNull(), + status: integer("status").notNull(), + uploadedById: text("uploaded_by").references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + uploadedByName: text("uploaded_by_name").references( + () => users.username, + { + onUpdate: "cascade", + onDelete: "cascade", + } + ), + uploadedDate: integer("uploaded_date").notNull(), + viewCount: integer("view_count").default(0).notNull(), + downloadCount: integer("download_count").default(0).notNull(), + fileSize: integer("file_size").default(0).notNull(), + width: integer("width").default(0).notNull(), + height: integer("height").default(0).notNull(), + }, + (table) => { + return { + idIdx: uniqueIndex("assets_id_idx").on(table.id), + nameIdx: uniqueIndex("assets_name_idx").on(table.name), + gameIdx: uniqueIndex("assets_game_idx").on(table.game), + assetCategoryIdx: uniqueIndex("assets_asset_category_idx").on( + table.assetCategory + ), + uploadedByIdIdx: uniqueIndex("assets_uploaded_by_idx").on( + table.uploadedById + ), + uploadedByNameIdx: uniqueIndex("assets_uploaded_by_name_idx").on( + table.uploadedByName + ), + } + } +) + +export const assetTags = sqliteTable( + tableNames.assetTags, + { + id: text("id").primaryKey(), + name: text("name").notNull(), + formattedName: text("formatted_name").notNull(), + assetCount: integer("asset_count").default(0).notNull(), + lastUpdated: integer("last_updated").notNull(), + }, + (assetTag) => { + return { + assetTagIdx: uniqueIndex("asset_tag_id_idx").on(assetTag.id), + nameIdx: uniqueIndex("asset_tag_name_idx").on(assetTag.name), + } + } +) + +export const assetTagsAssets = sqliteTable( + tableNames.assetTagsAssets, + { + id: text("id").primaryKey(), + assetTagId: text("asset_tag_id") + .notNull() + .references(() => assetTags.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + assetId: integer("asset_id") + .notNull() + .references(() => assets.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + }, + (assetTagsAssets) => { + return { + assetTagsAssetsIdx: uniqueIndex("asset_tags_assets_id_idx").on( + assetTagsAssets.id + ), + assetTagsAssetsAssetTagIdx: uniqueIndex( + "asset_tags_assets_asset_tag_id_idx" + ).on(assetTagsAssets.assetTagId), + assetTagsAssetsAssetIdx: uniqueIndex( + "asset_tags_assets_asset_id_idx" + ).on(assetTagsAssets.assetId), + } + } +) + +export const following = sqliteTable(tableNames.following, { + id: text("id").primaryKey(), + followerUserId: text("follower_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + followingUserId: text("following_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), +}) + +export const follower = sqliteTable(tableNames.follower, { + id: text("id").primaryKey(), + followerUserId: text("follower_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + followingUserId: text("following_id") + .notNull() + .references(() => users.id), +}) + +export const userFavorites = sqliteTable( + tableNames.userFavorites, + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + isPublic: integer("is_public").default(0).notNull(), + }, + (userFavorites) => { + return { + favoritedAssetsIdx: uniqueIndex("favorited_assets_id_idx").on( + userFavorites.id + ), + favoritedAssetsUserIdx: uniqueIndex( + "favorited_assets_user_id_idx" + ).on(userFavorites.userId), + } + } +) + +export const userFavoritesAssets = sqliteTable( + tableNames.userFavoritesAssets, + { + id: text("id").primaryKey(), + userFavoritesId: text("favorited_assets_id") + .notNull() + .references(() => userFavorites.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + assetId: integer("asset_id") + .notNull() + .references(() => assets.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + }, + (userFavoritesAssets) => { + return { + favoritedAssetsAssetsIdx: uniqueIndex("favorited_assets_id_idx").on( + userFavoritesAssets.id + ), + favoritedAssetsUserIdx: uniqueIndex( + "favorited_assets_user_id_idx" + ).on(userFavoritesAssets.userFavoritesId), + favoritedAssetsAssetsAssetIdx: uniqueIndex( + "favorited_assets_assets_asset_id_idx" + ).on(userFavoritesAssets.assetId), + } + } +) + +export const userCollections = sqliteTable( + tableNames.userCollections, + { + id: text("id").primaryKey(), + name: text("name").notNull(), + description: text("description").notNull(), + userId: text("user_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + dateCreated: integer("date_created").notNull(), + isPublic: integer("is_public").default(0).notNull(), + }, + (collection) => { + return { + collectionIdx: uniqueIndex("collection_id_idx").on(collection.id), + userCollectionIdx: uniqueIndex("user_collection_id_idx").on( + collection.userId + ), + } + } +) + +export const userCollectionAssets = sqliteTable( + tableNames.userCollectionAssets, + { + id: text("id").primaryKey(), + collectionId: text("collection_id") + .notNull() + .references(() => userCollections.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + assetId: integer("asset_id") + .notNull() + .references(() => assets.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + }, + (collectionAssets) => { + return { + collectionAssetsIdx: uniqueIndex("collection_assets_id_idx").on( + collectionAssets.id + ), + collectionAssetsCollectionIdx: uniqueIndex( + "collection_assets_collection_id_idx" + ).on(collectionAssets.collectionId), + collectionAssetsAssetIdx: uniqueIndex( + "collection_assets_asset_id_idx" + ).on(collectionAssets.assetId), + } + } +) + +export const savedOcGenerators = sqliteTable( + tableNames.savedOcGenerators, + { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id, { + onUpdate: "cascade", + onDelete: "cascade", + }), + name: text("name").notNull(), + game: text("game").notNull(), + dateCreated: integer("date_created").notNull(), + isPublic: integer("is_public").default(0).notNull(), + content: text("content").notNull(), // this is stored as json, which is then parsed on the frontend + }, + (savedOcGenerators) => { + return { + savedOcGeneratorsIdx: uniqueIndex("saved_oc_generators_id_idx").on( + savedOcGenerators.id + ), + savedOcGeneratorsUserIdx: uniqueIndex( + "saved_oc_generators_user_id_idx" + ).on(savedOcGenerators.userId), + } + } +) + +// relations +export const gameRelations = relations(games, ({ many }) => ({ + assets: many(assets), +})) + +export const assetRelations = relations(assets, ({ one, many }) => ({ + uploadedBy: one(users, { + fields: [assets.uploadedById, assets.uploadedByName], + references: [users.id, users.username], + }), + assetTagsAssets: many(assetTagsAssets), + assetCategory: one(assetCategories, { + fields: [assets.assetCategory], + references: [assetCategories.name], + }), + game: one(games, { + fields: [assets.game], + references: [games.name], + }), +})) + +export const assetCategoryRelations = relations( + assetCategories, + ({ many }) => ({ + assets: many(assets), + }) +) + +export const assetTagsAssetsRelations = relations( + assetTagsAssets, + ({ one }) => ({ + assetTag: one(assetTags, { + fields: [assetTagsAssets.assetTagId], + references: [assetTags.id], + }), + asset: one(assets, { + fields: [assetTagsAssets.assetId], + references: [assets.id], + }), + }) +) + +export const collectionRelations = relations( + userCollections, + ({ one, many }) => ({ + user: one(users, { + fields: [userCollections.userId], + references: [users.id], + }), + assets: many(userCollectionAssets), + }) +) + +export const collectionAssetsRelations = relations( + userCollectionAssets, + ({ one }) => ({ + collection: one(userCollections, { + fields: [userCollectionAssets.collectionId], + references: [userCollections.id], + }), + asset: one(assets, { + fields: [userCollectionAssets.assetId], + references: [assets.id], + }), + }) +) + +export const userFavoritesRelations = relations(userFavorites, ({ one }) => ({ + user: one(users, { + fields: [userFavorites.userId], + references: [users.id], + }), +})) + +export const userFavoritesAssetsRelations = relations( + userFavoritesAssets, + ({ one }) => ({ + favoritedAssets: one(userFavorites, { + fields: [userFavoritesAssets.userFavoritesId], + references: [userFavorites.id], + }), + asset: one(assets, { + fields: [userFavoritesAssets.assetId], + references: [assets.id], + }), + }) +) + +export const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { + fields: [sessions.userId], + references: [users.id], + }), +})) + +export const keysRelations = relations(keys, ({ one }) => ({ + user: one(users, { + fields: [keys.userId], + references: [users.id], + }), +})) + +export const socialsConnectionsRelations = relations( + socialsConnections, + ({ one }) => ({ + user: one(users, { + fields: [socialsConnections.userId], + references: [users.id], + }), + }) +) + +export const usersRelations = relations(users, ({ one, many }) => ({ + session: many(sessions), + key: many(keys), + assets: many(assets), + follower: many(follower), + userFavorites: one(userFavorites), + following: many(following), + socialsConnection: one(socialsConnections), + userCollections: many(userCollections), + savedOcGenerators: many(savedOcGenerators), +})) + +// export types + +// game, asset types +export type game = typeof games.$inferSelect +export type assetCategory = typeof assetCategories.$inferSelect +export type asset = typeof assets.$inferSelect +export type assetTag = typeof assetTags.$inferSelect +export type assetTagsAsset = typeof assetTagsAssets.$inferSelect + +// user types +export type user = typeof users.$inferSelect +export type session = typeof sessions.$inferSelect +export type key = typeof keys.$inferSelect +export type socialsConnection = typeof socialsConnections.$inferSelect +export type following = typeof following.$inferSelect +export type follower = typeof follower.$inferSelect +export type userFavorites = typeof userFavorites.$inferSelect +export type userFavoritesAssets = typeof userFavoritesAssets.$inferSelect +export type userCollections = typeof userCollections.$inferSelect +export type userCollectionAssets = typeof userCollectionAssets.$inferSelect diff --git a/src/v2/db/turso.ts b/src/v2/db/turso.ts new file mode 100644 index 00000000..21ef7990 --- /dev/null +++ b/src/v2/db/turso.ts @@ -0,0 +1,29 @@ +import { Bindings as Env } from "@/worker-configuration" +import * as schema from "@/v2/db/schema" +import { drizzle as drizzleORM } from "drizzle-orm/libsql" +import { createClient } from "@libsql/client/web" // because we're in a worker +import { Logger } from "drizzle-orm/logger" + +class LoggerWrapper implements Logger { + logQuery(query: string, params: unknown[]): void { + console.log(`DRIZZLE: Query: ${query}, Paremeters: ${params ?? "none"}`) + } +} + +// wrapper for turso & drizzle +export function getConnection(env: Env) { + const turso = createClient({ + url: env.TURSO_DATABASE_URL, + authToken: env.TURSO_DATABASE_AUTH_TOKEN, + }) + + const drizzle = drizzleORM(turso, { + schema, + logger: new LoggerWrapper(), + }) + + return { + drizzle, + turso, + } +} diff --git a/src/v2/lib/auth/lucia.ts b/src/v2/lib/auth/lucia.ts new file mode 100644 index 00000000..3e6c245e --- /dev/null +++ b/src/v2/lib/auth/lucia.ts @@ -0,0 +1,58 @@ +import { lucia } from "lucia" +import { hono } from "lucia/middleware" +import { getConnection } from "@/v2/db/turso" +import { Bindings as Env } from "@/worker-configuration" +import { tableNames } from "@/v2/db/drizzle" +import { libsql } from "@lucia-auth/adapter-sqlite" + +// this is so we can pass in env during requests, +// so, it would be called: auth(c.env)... instead of auth +export const auth = (env: Env) => { + const db = getConnection(env) + const connection = db.turso + + return lucia({ + adapter: libsql(connection, { + key: tableNames.authKey, + session: tableNames.authSession, + user: tableNames.authUser, + }), + middleware: hono(), + sessionExpiresIn: { + idlePeriod: 0, + activePeriod: 30 * 24 * 60 * 60 * 1000, // 30 days + }, + env: env.ENVIRONMENT === "DEV" ? "DEV" : "PROD", + experimental: { + debugMode: env.ENVIRONMENT === "DEV" ? true : false, + }, + csrfProtection: { + enabled: true, + allowedSubDomains: ["*"], + }, + getUserAttributes: (dbUser) => { + return { + username: dbUser.username, + usernameColour: dbUser.username_colour, + avatarUrl: dbUser.avatar_url, + bannerUrl: dbUser.banner_url, + email: dbUser.email, + emailVerified: dbUser.email_verified, + pronouns: dbUser.pronouns, + verified: dbUser.verified, + bio: dbUser.bio, + roleFlags: dbUser.role_flags, + selfAssignableRoleFlags: dbUser.self_assignable_role_flags, + dateJoined: dbUser.date_joined, + } + }, + getSessionAttributes: (dbSession) => { + return { + userAgent: dbSession.user_agent as string, + countryCode: dbSession.country_code as string, + } + }, + }) +} + +export type Auth = typeof auth diff --git a/src/v2/lib/discord.ts b/src/v2/lib/discord.ts new file mode 100644 index 00000000..78a44b70 --- /dev/null +++ b/src/v2/lib/discord.ts @@ -0,0 +1,12 @@ +export const roles: { [key: string]: string } = { + "982387185259012096": "Project Lead", + "1112182244572938310": "Developer", + "1038892176479895584": "Admin", + "1000916582538674249": "Senior Moderator", + "983817984923562034": "Moderator", + "1088105796908355584": "Translator", + "1005805438031364129": "Contributor", + "983883539772751912": "Server Booster", +} + +export const guildId = "982385887000272956" diff --git a/src/v2/lib/helpers/assetStatus.ts b/src/v2/lib/helpers/assetStatus.ts new file mode 100644 index 00000000..8f638d40 --- /dev/null +++ b/src/v2/lib/helpers/assetStatus.ts @@ -0,0 +1,5 @@ +export const AssetStatus = { + 1: "APPROVED", + 2: "PENDING", + 3: "FLAGGED", +} diff --git a/src/lib/helpers/rename.ts b/src/v2/lib/helpers/rename.ts similarity index 56% rename from src/lib/helpers/rename.ts rename to src/v2/lib/helpers/rename.ts index b1112074..24425262 100644 --- a/src/lib/helpers/rename.ts +++ b/src/v2/lib/helpers/rename.ts @@ -1,3 +1,3 @@ export const rename = (name: string): string => { - return name.replace(/-/g, "_"); -}; + return name.replace(/-/g, "_") +} diff --git a/src/v2/lib/helpers/responses/notFoundResponse.ts b/src/v2/lib/helpers/responses/notFoundResponse.ts new file mode 100644 index 00000000..5e19bd5f --- /dev/null +++ b/src/v2/lib/helpers/responses/notFoundResponse.ts @@ -0,0 +1,12 @@ +// helper function to create a 404 Not Found response +export function createNotFoundResponse(c, errorMessage, responseHeaders) { + return c.json( + { + success: false, + status: "error", + error: errorMessage, + }, + 404, + responseHeaders + ) +} diff --git a/src/v2/lib/helpers/roleFlags.ts b/src/v2/lib/helpers/roleFlags.ts new file mode 100644 index 00000000..54b1e489 --- /dev/null +++ b/src/v2/lib/helpers/roleFlags.ts @@ -0,0 +1,43 @@ +// bitwise for role flags allows for multiple roles to be assigned to a user, and for easy checking of roles + +// permission based roles +export const roleFlags = { + USER: 1 << 0, + UPLOADER: 1 << 1, + CONTRIBUTOR: 1 << 2, + TRANSLATOR: 1 << 3, + STAFF: 1 << 4, + DEVELOPER: 1 << 5, + CREATOR: 1 << 6, +} + +export const roleFlagsToArray = (roleFlagsInt: number): string[] => { + const roles: string[] = [] + + for (const [role, flag] of Object.entries(roleFlags)) { + if (roleFlagsInt & flag) roles.push(role) + } + + return roles +} + +// self assignable roles +export const SelfAssignableRoleFlags = { + CONTENT_CREATOR: 1 << 0, + ARTIST: 1 << 1, + WRITER: 1 << 2, + DEVELOPER: 1 << 3, + DESIGNER: 1 << 4, +} + +export const SelfAssignableRoleFlagsToArray = ( + roleFlagsInt: number +): string[] => { + const roles: string[] = [] + + for (const [role, flag] of Object.entries(SelfAssignableRoleFlags)) { + if (roleFlagsInt & flag) roles.push(role) + } + + return roles +} diff --git a/src/v2/lib/helpers/splitQueryByCommas.ts b/src/v2/lib/helpers/splitQueryByCommas.ts new file mode 100644 index 00000000..dcd0337d --- /dev/null +++ b/src/v2/lib/helpers/splitQueryByCommas.ts @@ -0,0 +1,3 @@ +export function SplitQueryByCommas(query: string) { + return query.split(",").map((q) => q.trim()) +} diff --git a/src/lib/listBucket.ts b/src/v2/lib/listBucket.ts similarity index 56% rename from src/lib/listBucket.ts rename to src/v2/lib/listBucket.ts index ba1dbebd..4a9e0263 100644 --- a/src/lib/listBucket.ts +++ b/src/v2/lib/listBucket.ts @@ -1,3 +1,3 @@ export const listBucket = async (bucket, options) => { - return await bucket.list(options); -}; + return await bucket.list(options) +} diff --git a/src/v2/lib/resend/email.ts b/src/v2/lib/resend/email.ts new file mode 100644 index 00000000..ce17a594 --- /dev/null +++ b/src/v2/lib/resend/email.ts @@ -0,0 +1,67 @@ +import { Resend } from "resend" + +// TODO: use react email w/ tailwind +const resend = new Resend("") + +export const sendPasswordResetEmail = async ( + email: string, + link: string, + username: string +) => { + try { + await resend.emails.send({ + from: "Test ", + to: email, + subject: "Password Reset Request", + html: `Password reset for ${username}
Click here to reset your password`, + }) + } catch (error) { + throw new Error("Error sending password reset email.") + } +} + +export const sendPasswordChangeEmail = async ( + email: string, + username: string +) => { + try { + await resend.emails.send({ + from: "Test ", + to: email, + subject: "Password Updated Confirmation", + html: `Your password for ${username} has been updated.
Wasn't you? Contact us at support@wanderer.moe`, + }) + } catch (error) { + throw new Error("Error sending password change email.") + } +} + +export const sendEmailChangeEmail = async (email: string, username: string) => { + try { + await resend.emails.send({ + from: "Test ", + to: email, + subject: "Email Change Request", + html: `Your email address for ${username} has been changed.
Wasn't you? Contact us at support@wanderer.moe`, + }) + } catch (error) { + throw new Error("Error sending email change email.") + } +} + +export const sendEmailConfirmationEmail = async ( + email: string, + link: string, + username: string +) => { + try { + await resend.emails.send({ + from: "Test ", + to: email, + subject: "Email Confirmation", + html: `Email confirmation for ${username}
Click here to confirm your email`, + }) + } catch (error) { + throw new Error("Error sending email confirmation email.") + } +} diff --git a/src/v2/lib/responseHeaders.ts b/src/v2/lib/responseHeaders.ts new file mode 100644 index 00000000..7910acdb --- /dev/null +++ b/src/v2/lib/responseHeaders.ts @@ -0,0 +1,5 @@ +export const responseHeaders: Record = { + "X-Content-Type-Options": "nosniff", + "Referrer-Policy": "strict-origin-when-cross-origin", + "content-type": "application/json;charset=UTF-8", +} diff --git a/src/v2/lib/types/discord.ts b/src/v2/lib/types/discord.ts new file mode 100644 index 00000000..9082433f --- /dev/null +++ b/src/v2/lib/types/discord.ts @@ -0,0 +1,18 @@ +// types used for the /contributor endpoint +export interface Contributor { + id: string + username: string + globalname: string | null + avatar: string + roles: string[] +} + +export interface GuildMember { + roles: string[] + user: { + id: string + username: string + global_name: string | null + avatar: string + } +} diff --git a/src/v2/routes/asset/assetRoute.ts b/src/v2/routes/asset/assetRoute.ts new file mode 100644 index 00000000..781f73fd --- /dev/null +++ b/src/v2/routes/asset/assetRoute.ts @@ -0,0 +1,17 @@ +import { Hono } from "hono" +import { getAssetFromId } from "./getAssetFromId" +import { downloadAsset } from "./downloadAsset" +import { Bindings } from "@/worker-configuration" + +const assetRoute = new Hono<{ Bindings: Bindings }>() + +assetRoute.get("/:id", async (c) => { + return getAssetFromId(c) +}) + +// setting both of these to id returns "duplicate param name" error, will fix later +assetRoute.get("/download/:assetId", async (c) => { + return downloadAsset(c) +}) + +export default assetRoute diff --git a/src/v2/routes/asset/downloadAsset.ts b/src/v2/routes/asset/downloadAsset.ts new file mode 100644 index 00000000..1f882675 --- /dev/null +++ b/src/v2/routes/asset/downloadAsset.ts @@ -0,0 +1,39 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import { eq } from "drizzle-orm" +import { assets } from "@/v2/db/schema" +import type { APIContext as Context } from "@/worker-configuration" + +export async function downloadAsset(c: Context): Promise { + const { assetId } = c.req.param() + + const drizzle = await getConnection(c.env).drizzle + + const asset = await drizzle + .select() + .from(assets) + .where(eq(assets.id, parseInt(assetId))) + .execute() + + if (!asset) + return createNotFoundResponse(c, "Asset not found", responseHeaders) + + if (asset) + await drizzle + .update(assets) + .set({ downloadCount: asset[0].downloadCount + 1 }) + .where(eq(assets.id, parseInt(assetId))) + .execute() + + const response = await fetch(asset[0].url) + + const blob = await response.blob() + + const headers = new Headers() + headers.set("Content-Disposition", `attachment; filename=${asset[0].name}`) + + return new Response(blob, { + headers: headers, + }) +} diff --git a/src/v2/routes/asset/getAssetFromId.ts b/src/v2/routes/asset/getAssetFromId.ts new file mode 100644 index 00000000..e0bdc5a1 --- /dev/null +++ b/src/v2/routes/asset/getAssetFromId.ts @@ -0,0 +1,61 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { assets } from "@/v2/db/schema" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import { desc } from "drizzle-orm" +import type { APIContext as Context } from "@/worker-configuration" + +export async function getAssetFromId(c: Context): Promise { + const { id } = c.req.param() + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + if (response) return response + + const drizzle = await getConnection(c.env).drizzle + + const asset = await drizzle.query.assets.findFirst({ + where: (assets, { eq, and }) => + and(eq(assets.status, 1), eq(assets.id, parseInt(id))), + with: { + assetTagsAssets: { + with: { + assetTags: true, + }, + }, + }, + }) + + if (!asset) { + response = createNotFoundResponse(c, "Asset not found", responseHeaders) + await cache.put(cacheKey, response.clone()) + return response + } + + const similarAssets = await drizzle.query.assets.findMany({ + where: (assets, { eq, and }) => + and( + eq(assets.status, 1), + eq(assets.assetCategory, asset.assetCategory) + ), + limit: 6, + orderBy: desc(assets.id), + }) + + response = c.json( + { + success: true, + status: "ok", + asset, + similarAssets, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=604800") + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/v2/routes/auth/asset-categories/createAssetCategory.ts b/src/v2/routes/auth/asset-categories/createAssetCategory.ts new file mode 100644 index 00000000..cb69491c --- /dev/null +++ b/src/v2/routes/auth/asset-categories/createAssetCategory.ts @@ -0,0 +1,63 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { assetCategories } from "@/v2/db/schema" + +export async function createAssetCategory(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + const assetCategory = { + id: crypto.randomUUID(), + name: formData.get("name") as string, + formattedName: formData.get("formattedName") as string, + assetCount: 0, + lastUpdated: new Date().getTime(), // unix timestamp + } + + // check if assetCategory.name exists + const assetCategoryExists = await drizzle.query.assetCategories.findFirst({ + where: (assetCategories, { eq }) => + eq(assetCategories.name, assetCategory.name), + }) + + if (assetCategoryExists) { + return c.json( + { success: false, state: "assetCategory with name already exists" }, + 200 + ) + } + + try { + await drizzle.insert(assetCategories).values(assetCategory).execute() + } catch (e) { + return c.json( + { success: false, state: "failed to create assetCategory" }, + 200 + ) + } + + return c.json( + { success: true, state: "created assetcategory", assetCategory }, + 200 + ) +} diff --git a/src/v2/routes/auth/asset-categories/deleteAssetCategory.ts b/src/v2/routes/auth/asset-categories/deleteAssetCategory.ts new file mode 100644 index 00000000..d404f9bb --- /dev/null +++ b/src/v2/routes/auth/asset-categories/deleteAssetCategory.ts @@ -0,0 +1,67 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { eq } from "drizzle-orm" +import { assetCategories } from "@/v2/db/schema" + +export async function deleteAssetCategory(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + const assetCategory = { + id: formData.get("id") as string | null, + } + + if (!assetCategory.id) { + return c.json({ success: false, state: "no id entered" }, 200) + } + + // check if assetCategory exists + const assetCategoryExists = await drizzle.query.assetCategories.findFirst({ + where: (assetCategories, { eq }) => + eq(assetCategories.id, assetCategory.id), + }) + + if (!assetCategoryExists) { + return c.json( + { success: false, state: "assetCategory with ID doesn't exist" }, + 200 + ) + } + + try { + await drizzle + .delete(assetCategories) + .where(eq(assetCategories.id, assetCategory.id)) + .execute() + } catch (e) { + return c.json( + { success: false, state: "failed to delete assetCategory" }, + 200 + ) + } + + return c.json( + { success: true, state: "deleted assetCategory", assetCategory }, + 200 + ) +} diff --git a/src/v2/routes/auth/assets/approveAsset.ts b/src/v2/routes/auth/assets/approveAsset.ts new file mode 100644 index 00000000..a819c7b5 --- /dev/null +++ b/src/v2/routes/auth/assets/approveAsset.ts @@ -0,0 +1,58 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { assets } from "@/v2/db/schema" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import { eq } from "drizzle-orm" +import type { APIContext as Context } from "@/worker-configuration" +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" + +export async function approveAsset(c: Context): Promise { + const { assetIdToApprove } = c.req.param() + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const asset = await drizzle.query.assets.findFirst({ + where: (assets, { eq }) => eq(assets.id, parseInt(assetIdToApprove)), + }) + + if (!asset) { + return createNotFoundResponse(c, "Asset not found", responseHeaders) + } + + const updatedAsset = await drizzle + .update(assets) + .set({ + status: 1, + }) + .where(eq(assets.id, parseInt(assetIdToApprove))) + .execute() + + const response = c.json( + { + success: true, + status: "ok", + updatedAsset, + }, + 200, + responseHeaders + ) + + return response +} diff --git a/src/v2/routes/auth/assets/collections/addAssetToCollection.ts b/src/v2/routes/auth/assets/collections/addAssetToCollection.ts new file mode 100644 index 00000000..7141a8ef --- /dev/null +++ b/src/v2/routes/auth/assets/collections/addAssetToCollection.ts @@ -0,0 +1,95 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { userCollectionAssets } from "@/v2/db/schema" + +export async function addAssetToCollection(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const collection = { + id: formData.get("collectionId") as string | null, + assetId: formData.get("assetId") as string | null, + } + + if (!collection.id) { + return c.json( + { success: false, state: "no collection id entered" }, + 200 + ) + } + + if (!collection.assetId) { + return c.json({ success: false, state: "no asset id entered" }, 200) + } + + // check if collection exists + const collectionExists = await drizzle.query.userCollections.findFirst({ + where: (userCollections, { eq }) => + eq(userCollections.id, collection.id), + }) + + if (!collectionExists) { + return c.json( + { success: false, state: "collection with ID doesn't exist" }, + 200 + ) + } + + // check if asset exists, and status is 1 (approved) + const assetExists = await drizzle.query.assets.findFirst({ + where: (assets, { eq, and }) => + and( + eq(assets.id, parseInt(collection.assetId)), + eq(assets.status, 1) + ), + }) + + if (!assetExists) { + return c.json({ success: false, state: "asset not found" }, 200) + } + + // check if userCollectionAssets exists + const userCollectionAssetsExists = + await drizzle.query.userCollectionAssets.findFirst({ + where: (userCollectionAssets, { eq, and }) => + and( + eq(userCollectionAssets.collectionId, collection.id), + eq( + userCollectionAssets.assetId, + parseInt(collection.assetId) + ) + ), + }) + + if (userCollectionAssetsExists) { + return c.json( + { success: false, state: "asset already exists in collection" }, + 200 + ) + } + + // create entry in userCollectionAssets + await drizzle + .insert(userCollectionAssets) + .values({ + id: crypto.randomUUID(), + collectionId: collection.id, + assetId: parseInt(collection.assetId), + }) + .execute() + + return c.json({ success: true, state: "added asset to collection" }, 200) +} diff --git a/src/v2/routes/auth/assets/collections/createAssetCollection.ts b/src/v2/routes/auth/assets/collections/createAssetCollection.ts new file mode 100644 index 00000000..94f8fab6 --- /dev/null +++ b/src/v2/routes/auth/assets/collections/createAssetCollection.ts @@ -0,0 +1,68 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { userCollections } from "@/v2/db/schema" + +export async function createAssetCollection(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const collection = { + name: formData.get("collectionName") as string | null, + description: formData.get("collectionDescription") as string | null, + } + + if (!collection.name) { + return c.json( + { success: false, state: "no collection name entered" }, + 200 + ) + } + + if (!collection.description) { + return c.json( + { success: false, state: "no collection description entered" }, + 200 + ) + } + + // check if collection exists + const collectionExists = await drizzle.query.userCollections.findFirst({ + where: (userCollections, { eq }) => + eq(userCollections.name, collection.name), + }) + + if (collectionExists) { + return c.json( + { success: false, state: "collection with name already exists" }, + 200 + ) + } + + // create entry in userCollections + await drizzle + .insert(userCollections) + .values({ + id: crypto.randomUUID(), + name: collection.name, + description: collection.description, + userId: session.userId, + dateCreated: new Date().getTime(), + isPublic: 0, // default to private + }) + .execute() + + return c.json({ success: true, state: "created collection" }, 200) +} diff --git a/src/v2/routes/auth/assets/collections/deleteAssetCollection.ts b/src/v2/routes/auth/assets/collections/deleteAssetCollection.ts new file mode 100644 index 00000000..cae8fbd2 --- /dev/null +++ b/src/v2/routes/auth/assets/collections/deleteAssetCollection.ts @@ -0,0 +1,54 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { eq } from "drizzle-orm" +import { userCollections } from "@/v2/db/schema" + +export async function deleteAssetCollection(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const collection = { + id: formData.get("collectionId") as string | null, + } + + if (!collection.id) { + return c.json( + { success: false, state: "no collection id entered" }, + 200 + ) + } + + // check if collection exists + const collectionExists = await drizzle.query.userCollections.findFirst({ + where: (userCollections, { eq }) => + eq(userCollections.id, collection.id), + }) + + if (!collectionExists) { + return c.json( + { success: false, state: "collection with ID doesn't exist" }, + 200 + ) + } + + // delete collection + await drizzle + .delete(userCollections) + .where(eq(userCollections.id, collection.id)) + .execute() + + return c.json({ success: true, state: "collection deleted" }, 200) +} diff --git a/src/v2/routes/auth/assets/collections/deleteAssetFromCollection.ts b/src/v2/routes/auth/assets/collections/deleteAssetFromCollection.ts new file mode 100644 index 00000000..5ebde0a0 --- /dev/null +++ b/src/v2/routes/auth/assets/collections/deleteAssetFromCollection.ts @@ -0,0 +1,92 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { userCollectionAssets } from "@/v2/db/schema" +import { eq } from "drizzle-orm" + +export async function deleteAssetFromCollection(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const collection = { + id: formData.get("collectionId") as string | null, + assetId: formData.get("assetId") as string | null, + } + + if (!collection.id) { + return c.json( + { success: false, state: "no collection id entered" }, + 200 + ) + } + + if (!collection.assetId) { + return c.json({ success: false, state: "no asset id entered" }, 200) + } + + // check if collection exists + const collectionExists = await drizzle.query.userCollections.findFirst({ + where: (userCollections, { eq }) => + eq(userCollections.id, collection.id), + }) + + if (!collectionExists) { + return c.json( + { success: false, state: "collection with ID doesn't exist" }, + 200 + ) + } + + // check if asset exists + const assetExists = await drizzle.query.assets.findFirst({ + where: (assets, { eq }) => eq(assets.id, parseInt(collection.assetId)), + }) + + if (!assetExists) { + return c.json({ success: false, state: "asset not found" }, 200) + } + + // check if userCollectionAssets exists + const userCollectionAssetsExists = + await drizzle.query.userCollectionAssets.findFirst({ + where: (userCollectionAssets, { eq }) => + eq(userCollectionAssets.collectionId, collection.id) && + eq(userCollectionAssets.assetId, parseInt(collection.assetId)), + }) + + if (!userCollectionAssetsExists) { + return c.json( + { success: false, state: "asset not found in collection" }, + 200 + ) + } + + try { + await drizzle + .delete(userCollectionAssets) + .where(eq(userCollectionAssets.id, userCollectionAssetsExists.id)) + .execute() + } catch (e) { + return c.json( + { success: false, state: "failed to delete asset from collection" }, + 200 + ) + } + + return c.json( + { success: true, state: "deleted asset from collection" }, + 200 + ) +} diff --git a/src/v2/routes/auth/assets/collections/viewAssetCollection.ts b/src/v2/routes/auth/assets/collections/viewAssetCollection.ts new file mode 100644 index 00000000..e322f6b3 --- /dev/null +++ b/src/v2/routes/auth/assets/collections/viewAssetCollection.ts @@ -0,0 +1,73 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" + +export async function viewAssetCollection(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const collection = { + id: formData.get("collectionId") as string | null, + } + + if (!collection.id) { + return c.json( + { success: false, state: "no collection id entered" }, + 200 + ) + } + + // check if the user owns the collection, or if the collection is public + try { + await drizzle.query.userCollections.findFirst({ + where: (userCollections, { eq, or, and }) => + and( + or( + eq(userCollections.id, collection.id), + eq(userCollections.isPublic, 1) + ), + eq(userCollections.userId, session.userId) + ), + }) + } catch (e) { + return c.json( + { success: false, state: "collection with ID doesn't exist" }, + 200 + ) + } + + const assetCollection = await drizzle.query.userCollections.findFirst({ + where: (userCollections, { eq, or, and }) => + and( + or( + eq(userCollections.id, collection.id), + eq(userCollections.isPublic, 1) + ), + eq(userCollections.userId, session.userId) + ), + with: { + userCollectionAssets: { + with: { + assets: true, + }, + }, + }, + }) + + return c.json( + { success: true, state: "found collection", assetCollection }, + 200 + ) +} diff --git a/src/v2/routes/auth/assets/collections/viewAssetCollections.ts b/src/v2/routes/auth/assets/collections/viewAssetCollections.ts new file mode 100644 index 00000000..e3d166ba --- /dev/null +++ b/src/v2/routes/auth/assets/collections/viewAssetCollections.ts @@ -0,0 +1,43 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" + +export async function viewAssetCollections(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + // check if userCollections exists + const userCollectionsExists = await drizzle.query.userCollections.findFirst( + { + where: (userCollections, { eq }) => + eq(userCollections.userId, session.userId), + } + ) + + if (!userCollectionsExists) { + return c.json({ success: false, state: "no collections found" }, 200) + } + + const assetCollection = await drizzle.query.userCollectionAssets.findMany({ + where: (userCollectionAssets, { eq }) => + eq(userCollectionAssets.collectionId, userCollectionsExists.id), + with: { + assets: true, + }, + }) + + return c.json( + { success: true, state: "found collections", assetCollection }, + 200 + ) +} diff --git a/src/v2/routes/auth/assets/favorite/addFavoriteAsset.ts b/src/v2/routes/auth/assets/favorite/addFavoriteAsset.ts new file mode 100644 index 00000000..86e7704b --- /dev/null +++ b/src/v2/routes/auth/assets/favorite/addFavoriteAsset.ts @@ -0,0 +1,83 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { userFavorites, userFavoritesAssets } from "@/v2/db/schema" + +export async function favoriteAsset(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const assetToFavorite = formData.get("assetIdToFavorite") as string | null + + if (!assetToFavorite) { + return c.json({ success: false, state: "no assetid entered" }, 200) + } + + // check if asset exists, and status is 1 (approved) + const assetExists = await drizzle.query.assets.findFirst({ + where: (assets, { eq, and }) => + and(eq(assets.id, parseInt(assetToFavorite)), eq(assets.status, 1)), + }) + + if (!assetExists) { + return c.json({ success: false, state: "asset not found" }, 200) + } + + // this should never happen, but just in case it does, UX over reads/writes to the database + const doesUserFavoritesExist = await drizzle.query.userFavorites.findFirst({ + where: (userFavorites, { eq }) => + eq(userFavorites.userId, session.userId), + }) + + if (!doesUserFavoritesExist) { + // create entry in userFavorites + await drizzle + .insert(userFavorites) + .values({ + id: `${session.userId}-${assetToFavorite}`, + userId: session.userId, + isPublic: 0, // default to private + }) + .execute() + } + + const isFavorited = await drizzle.query.userFavorites.findFirst({ + where: (userFavoritesAssets, { eq }) => + eq(userFavoritesAssets.id, `${session.userId}-${assetToFavorite}`), + }) + + if (isFavorited) { + return c.json( + { + success: false, + state: "asset is already favorited, therefore cannot be favorited", + assetToFavorite, + }, + 200 + ) + } + + // add asset to userFavorites... + await drizzle.insert(userFavoritesAssets).values({ + id: `${session.userId}-${assetToFavorite}`, + userFavoritesId: isFavorited.id, + assetId: parseInt(assetToFavorite), + }) + + return c.json( + { success: true, state: "favorited asset", assetToFavorite }, + 200 + ) +} diff --git a/src/v2/routes/auth/assets/favorite/removeFavoriteAsset.ts b/src/v2/routes/auth/assets/favorite/removeFavoriteAsset.ts new file mode 100644 index 00000000..260d0be4 --- /dev/null +++ b/src/v2/routes/auth/assets/favorite/removeFavoriteAsset.ts @@ -0,0 +1,78 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { eq } from "drizzle-orm" +import { APIContext as Context } from "@/worker-configuration" +import { userFavorites, userFavoritesAssets } from "@/v2/db/schema" + +export async function removeFavoriteAsset(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + const assetToRemove = formData.get("assetToRemove") as string | null + + if (!assetToRemove) { + return c.json({ success: false, state: "no assetid entered" }, 200) + } + + // check if asset exists + const asset = await drizzle.query.assets.findFirst({ + where: (assets, { eq }) => eq(assets.id, parseInt(assetToRemove)), + }) + + if (!asset) { + return c.json({ success: false, state: "asset not found" }, 200) + } + + // this should never happen, but just in case it does, UX over reads/writes to the database + const doesUserFavoritesExist = await drizzle.query.userFavorites.findFirst({ + where: (userFavorites, { eq }) => + eq(userFavorites.userId, session.userId), + }) + + if (!doesUserFavoritesExist) { + // create entry in userFavorites + await drizzle + .insert(userFavorites) + .values({ + id: `${session.userId}-${assetToRemove}`, + userId: session.userId, + isPublic: 0, // default to private + }) + .execute() + } + + const isFavorited = await drizzle.query.userFavorites.findFirst({ + where: (userFavoritesAssets, { eq }) => + eq(userFavoritesAssets.id, `${session.userId}-${assetToRemove}`), + }) + + if (!isFavorited) { + return c.json( + { + success: false, + state: "asset is not favorited, therefore cannot be removed", + assetToRemove, + }, + 200 + ) + } + + // remove asset from userFavorites... + await drizzle + .delete(userFavoritesAssets) + .where(eq(userFavoritesAssets.id, `${session.userId}-${assetToRemove}`)) + .execute() + + return c.json({ success: true, state: "removed asset", assetToRemove }, 200) +} diff --git a/src/v2/routes/auth/assets/favorite/viewFavoriteAssets.ts b/src/v2/routes/auth/assets/favorite/viewFavoriteAssets.ts new file mode 100644 index 00000000..a7a4d55a --- /dev/null +++ b/src/v2/routes/auth/assets/favorite/viewFavoriteAssets.ts @@ -0,0 +1,41 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" + +export async function viewFavoriteAssets(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + // check if userFavorites exists + const userFavoritesExists = await drizzle.query.userFavorites.findFirst({ + where: (userFavorites, { eq }) => + eq(userFavorites.userId, session.userId), + }) + + if (!userFavoritesExists) { + return c.json({ success: false, state: "no favorites found" }, 200) + } + + const favoriteAssets = await drizzle.query.userFavoritesAssets.findMany({ + where: (userFavoritesAssets, { eq }) => + eq(userFavoritesAssets.userFavoritesId, userFavoritesExists.id), + with: { + assets: true, + }, + }) + + return c.json( + { success: true, state: "found favorites", favoriteAssets }, + 200 + ) +} diff --git a/src/v2/routes/auth/assets/modifyAsset.ts b/src/v2/routes/auth/assets/modifyAsset.ts new file mode 100644 index 00000000..2699ee38 --- /dev/null +++ b/src/v2/routes/auth/assets/modifyAsset.ts @@ -0,0 +1,82 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { assets } from "@/v2/db/schema" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import { eq } from "drizzle-orm" +import type { APIContext as Context } from "@/worker-configuration" +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { SplitQueryByCommas } from "@/v2/lib/helpers/splitQueryByCommas" + +export async function modifyAssetData(c: Context): Promise { + const { assetIdToModify } = c.req.param() + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + // return unauthorized if user is not a contributor + if (session.user.is_contributor !== 1) + return c.json({ success: false, state: "unauthorized" }, 401) + + const drizzle = await getConnection(c.env).drizzle + + const asset = await drizzle.query.assets.findFirst({ + where: (assets, { eq }) => eq(assets.id, parseInt(assetIdToModify)), + }) + + if (!asset) { + return createNotFoundResponse(c, "Asset not found", responseHeaders) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if ( + asset.uploadedById !== session.userId || + !roleFlags.includes("CREATOR") + ) { + return c.json( + { success: false, state: "unauthorized to modify this asset" }, + 401 + ) + } + + const formData = await c.req.formData() + + const metadata = { + name: formData.get("name") as string | null, + game: formData.get("game") as string | null, + assetCategory: formData.get("assetCategory") as string | null, + tags: SplitQueryByCommas(formData.get("tags") as string | null), + } + + Object.keys(metadata).forEach( + (key) => metadata[key] === null && delete metadata[key] + ) + + const updatedAsset = await drizzle + .update(assets) + .set({ + ...metadata, + }) + .where(eq(assets.id, parseInt(assetIdToModify))) + .execute() + + const response = c.json( + { + success: true, + status: "ok", + updatedAsset, + }, + 200, + responseHeaders + ) + + return response +} diff --git a/src/v2/routes/auth/assets/uploadAsset.ts b/src/v2/routes/auth/assets/uploadAsset.ts new file mode 100644 index 00000000..fafa3a16 --- /dev/null +++ b/src/v2/routes/auth/assets/uploadAsset.ts @@ -0,0 +1,115 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { assets, assetTagsAssets } from "@/v2/db/schema" +import type { APIContext as Context } from "@/worker-configuration" +import { eq } from "drizzle-orm" +import { SplitQueryByCommas } from "@/v2/lib/helpers/splitQueryByCommas" + +export async function uploadAsset(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + // return unauthorized if user is not a contributor + if (session.user.is_contributor !== 1) + return c.json({ success: false, state: "unauthorized" }, 401) + + const bypassApproval = session.user.is_contributor === 1 + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + const asset = formData.get("asset") as unknown as File | null + + const metadata = { + name: formData.get("name`") as string, // e.g keqing + extension: formData.get("extension") as string, // e.g png + tags: SplitQueryByCommas(formData.get("tags") as string), // e.g no-background, fanmade, official => ["no-background", "fanmade", "official"] + category: formData.get("category") as string, // e.g splash-art + game: formData.get("game") as string, // e.g genshin-impact + size: formData.get("size") as unknown as number, // e.g 1024 + width: formData.get("width") as unknown as number, // e.g 1920 + height: formData.get("height") as unknown as number, // e.g 1080 + } + + if (metadata.tags.length > 5) + return c.json( + { + success: false, + state: `too many tags (${metadata.tags.length}). maximum is 5 tags per asset`, + }, + 400 + ) + + const newAsset = { + name: metadata.name, + extension: metadata.extension, + game: metadata.game, + assetCategory: metadata.category, + url: `/assets/${metadata.game}/${metadata.category}/${metadata.name}.${metadata.extension}`, + uploadedById: session.userId, + status: bypassApproval ? 1 : 2, + uploadedDate: new Date().getTime(), + fileSize: asset.size, // stored in bytes + width: metadata.width, + height: metadata.height, + } + + // rename file name to match metadata + const newAssetFile = new File([asset], newAsset.name, { + type: asset.type, + }) + + try { + await c.env.bucket.put( + `/assets/${metadata.game}/${metadata.category}/${metadata.name}.${metadata.extension}`, + newAssetFile + ) + + await drizzle.transaction(async (trx) => { + const newAssetDB = await trx + .insert(assets) + .values(newAsset) + .returning({ + assetId: assets.id, + }) + if (metadata.tags.length > 0) { + for (const tag of metadata.tags) { + const tagExists = await trx.query.assetTags.findFirst({ + where: (assetTags) => { + return eq(assetTags.name, tag) + }, + }) + if (tagExists) { + await trx + .insert(assetTagsAssets) + .values({ + id: crypto.randomUUID(), + assetId: newAssetDB[0].assetId, + assetTagId: tagExists[0].assetTagId, + }) + .returning({ + assetTagId: assetTagsAssets.assetTagId, + }) + } else { + continue + } + } + } + }) + } catch (e) { + await c.env.bucket.delete( + `/assets/${metadata.game}/${metadata.category}/${metadata.name}.${metadata.extension}` + ) + return c.json({ success: false, state: "failed to upload asset" }, 500) + } + + return c.json({ success: true, state: "uploaded asset" }, 200) +} diff --git a/src/v2/routes/auth/authRoute.ts b/src/v2/routes/auth/authRoute.ts new file mode 100644 index 00000000..52548ad9 --- /dev/null +++ b/src/v2/routes/auth/authRoute.ts @@ -0,0 +1,90 @@ +import { Hono } from "hono" +import { login } from "./login" +import { logout } from "./logout" +import { signup } from "./signup" +import { cors } from "hono/cors" +import { validate } from "./validate" +import { uploadProfileImage } from "./user-attributes/self-upload/uploadAvatar" +import { uploadBannerImage } from "./user-attributes/self-upload/uploadBanner" +import { saveOCGeneratorResponse } from "./oc-generators/saveOCGeneratorResponse" +import { updateUserAttributes } from "./user-attributes/updateUserAttributes" +import { uploadAsset } from "./assets/uploadAsset" +import { Bindings } from "@/worker-configuration" +import { modifyAssetData } from "./assets/modifyAsset" +import { approveAsset } from "./assets/approveAsset" +import { viewOCGeneratorResponses } from "./oc-generators/viewOCGeneratorResponses" +import { followUser } from "./user-attributes/user-relations/followUser" +import { unFollowUser } from "./user-attributes/user-relations/unfollowUser" +import { deleteOCGeneratorResponse } from "./oc-generators/deleteOCGeneratorResponse" + +const authRoute = new Hono<{ Bindings: Bindings }>() + +authRoute.use( + "*", + cors({ + credentials: true, + origin: ["https://next.wanderer.moe"], + }) +) + +authRoute.post("/login", async (c) => { + return login(c) +}) + +authRoute.post("/update/attributes", async (c) => { + return updateUserAttributes(c) +}) + +authRoute.post("/upload/asset", async (c) => { + return uploadAsset(c) +}) + +authRoute.post("/upload/avatar", async (c) => { + return uploadProfileImage(c) +}) + +authRoute.post("/approve/asset/:assetIdToApprove", async (c) => { + return approveAsset(c) +}) + +authRoute.post("/modify/asset/:assetIdToModify", async (c) => { + return modifyAssetData(c) +}) + +authRoute.post("/upload/banner", async (c) => { + return uploadBannerImage(c) +}) + +authRoute.post("/follow", async (c) => { + return followUser(c) +}) + +authRoute.post("/unfollow", async (c) => { + return unFollowUser(c) +}) + +authRoute.post("/signup", async (c) => { + return signup(c) +}) + +authRoute.post("/oc-generator/save", async (c) => { + return saveOCGeneratorResponse(c) +}) + +authRoute.get("/oc-generator/view/all", async (c) => { + return viewOCGeneratorResponses(c) +}) + +authRoute.post("/oc-generator/delete", async (c) => { + return deleteOCGeneratorResponse(c) +}) + +authRoute.get("/validate", async (c) => { + return validate(c) +}) + +authRoute.post("/logout", async (c) => { + return logout(c) +}) + +export default authRoute diff --git a/src/v2/routes/auth/games/createGame.ts b/src/v2/routes/auth/games/createGame.ts new file mode 100644 index 00000000..4c805e49 --- /dev/null +++ b/src/v2/routes/auth/games/createGame.ts @@ -0,0 +1,56 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { games } from "@/v2/db/schema" + +export async function createGame(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + const game = { + id: crypto.randomUUID(), + name: formData.get("name") as string, + formattedName: formData.get("formattedName") as string, + assetCount: 0, + lastUpdated: new Date().getTime(), // unix timestamp + } + + // check if game.name exists + const gameExists = await drizzle.query.assetCategories.findFirst({ + where: (assetCategories, { eq }) => eq(assetCategories.name, game.name), + }) + + if (gameExists) { + return c.json( + { success: false, state: "game with name already exists" }, + 200 + ) + } + + try { + await drizzle.insert(games).values(game).execute() + } catch (e) { + return c.json({ success: false, state: "failed to create game" }, 200) + } + + return c.json({ success: true, state: "created game", game }, 200) +} diff --git a/src/v2/routes/auth/games/deleteGame.ts b/src/v2/routes/auth/games/deleteGame.ts new file mode 100644 index 00000000..069f8dbe --- /dev/null +++ b/src/v2/routes/auth/games/deleteGame.ts @@ -0,0 +1,57 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { games } from "@/v2/db/schema" +import { eq } from "drizzle-orm" + +export async function deleteGame(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + const game = { + id: formData.get("id") as string | null, + } + + if (!game.id) { + return c.json({ success: false, state: "no id entered" }, 200) + } + + // check if game exists + const gameExists = await drizzle.query.games.findFirst({ + where: (games, { eq }) => eq(games.id, game.id), + }) + + if (!gameExists) { + return c.json( + { success: false, state: "game with ID doesn't exist" }, + 200 + ) + } + + try { + await drizzle.delete(games).where(eq(games.id, game.id)).execute() + } catch (e) { + return c.json({ success: false, state: "failed to delete game" }, 200) + } + + return c.json({ success: true, state: "deleted game", game }, 200) +} diff --git a/src/v2/routes/auth/login.ts b/src/v2/routes/auth/login.ts new file mode 100644 index 00000000..7def8cb7 --- /dev/null +++ b/src/v2/routes/auth/login.ts @@ -0,0 +1,79 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" + +const usernameThrottling = new Map< + string, + { + timeoutUntil: number + timeoutSeconds: number + } +>() + +export async function login(c: Context): Promise { + const formData = await c.req.formData() + + const username = formData.get("username") as string + const password = formData.get("password") as string + const validSession = await auth(c.env).handleRequest(c).validate() + + if (validSession) + return c.json({ success: false, state: "already logged in" }, 200) + + const storedThrottling = usernameThrottling.get(username) + const timeoutUntil = storedThrottling?.timeoutUntil ?? 0 + + if (timeoutUntil > Date.now()) { + return c.json( + { + success: false, + status: "error", + error: `Too many login attempts - wait ${ + (timeoutUntil - Date.now()) / 1000 + } seconds`, + }, + 400 + ) + } + + const user = await auth(c.env).useKey( + "username", + username.toLowerCase(), + password + ) + + if (!user) { + const timeoutSeconds = storedThrottling + ? storedThrottling.timeoutSeconds * 2 + : 1 + usernameThrottling.set(username, { + timeoutUntil: Date.now() + timeoutSeconds * 1000, + timeoutSeconds, + }) + return c.json( + { + success: false, + status: "error", + error: `Invalid credentials - wait ${timeoutSeconds} seconds`, + }, + 400 + ) + } + + const userAgent = c.req.headers.get("user-agent") ?? "" + const countryCode = c.req.headers.get("cf-ipcountry") ?? "" + + const newSession = await auth(c.env).createSession({ + userId: user.userId, + attributes: { + country_code: countryCode, + user_agent: userAgent, + }, + }) + + console.log("valid session created", countryCode, userAgent) + + const authRequest = await auth(c.env).handleRequest(c) + authRequest.setSession(newSession) + + return c.json({ success: true, state: "logged in" }, 200) +} diff --git a/src/v2/routes/auth/logout.ts b/src/v2/routes/auth/logout.ts new file mode 100644 index 00000000..4bd181b1 --- /dev/null +++ b/src/v2/routes/auth/logout.ts @@ -0,0 +1,18 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" + +export async function logout(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session) { + return c.json({ success: false, state: "invalid session" }, 200) + } + + // this is useful to clean up dead sessions that are still in the database + await auth(c.env).deleteDeadUserSessions(session.userId) + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + + return c.json({ success: true, state: "logged out" }, 200) +} diff --git a/src/v2/routes/auth/oc-generators/deleteOCGeneratorResponse.ts b/src/v2/routes/auth/oc-generators/deleteOCGeneratorResponse.ts new file mode 100644 index 00000000..bb2ad50b --- /dev/null +++ b/src/v2/routes/auth/oc-generators/deleteOCGeneratorResponse.ts @@ -0,0 +1,60 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" +import { savedOcGenerators } from "@/v2/db/schema" +import { getConnection } from "@/v2/db/turso" +import { eq, and } from "drizzle-orm" + +export async function deleteOCGeneratorResponse(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + const deleteID = (formData.get("deleteID") as string) || null + + if (!formData || !deleteID) + return c.json({ success: false, state: "no formdata provided" }) + + const ocGeneratorResponse = await drizzle + .select() + .from(savedOcGenerators) + .where( + and( + eq(savedOcGenerators.id, deleteID), + eq(savedOcGenerators.userId, session.userId) + ) + ) + + if (!ocGeneratorResponse) + return c.json({ + success: false, + state: "no generator found matching id", + }) + + await drizzle + .delete(savedOcGenerators) + .where( + and( + eq(savedOcGenerators.id, deleteID), + eq(savedOcGenerators.userId, session.userId) + ) + ) + + return c.json( + { + success: true, + state: `deleted saved oc generator with id ${deleteID}`, + ocGeneratorResponse, + }, + 200 + ) +} diff --git a/src/v2/routes/auth/oc-generators/saveOCGeneratorResponse.ts b/src/v2/routes/auth/oc-generators/saveOCGeneratorResponse.ts new file mode 100644 index 00000000..1c6848a9 --- /dev/null +++ b/src/v2/routes/auth/oc-generators/saveOCGeneratorResponse.ts @@ -0,0 +1,36 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" +import { savedOcGenerators } from "@/v2/db/schema" +import { getConnection } from "@/v2/db/turso" + +export async function saveOCGeneratorResponse(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + // TODO: make sure data is actually valid before inserting it into the database + const ocGeneratorResponse = { + id: crypto.randomUUID(), + userId: session.userId as string, + name: formData.get("name") as string, + game: formData.get("game") as string, + dateCreated: new Date().getTime(), + isPublic: parseInt(formData.get("isPublic") as string), // 1 = yes, 0 = no, default = 0 + content: formData.get("content") as string, // this is stored as json, which can then be parsed + } + + await drizzle.insert(savedOcGenerators).values(ocGeneratorResponse) + + return c.json({ success: true, state: "saved", ocGeneratorResponse }, 200) +} diff --git a/src/v2/routes/auth/oc-generators/viewOCGeneratorResponses.ts b/src/v2/routes/auth/oc-generators/viewOCGeneratorResponses.ts new file mode 100644 index 00000000..2835038c --- /dev/null +++ b/src/v2/routes/auth/oc-generators/viewOCGeneratorResponses.ts @@ -0,0 +1,30 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" +import { savedOcGenerators } from "@/v2/db/schema" +import { getConnection } from "@/v2/db/turso" +import { eq } from "drizzle-orm" + +export async function viewOCGeneratorResponses(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const drizzle = await getConnection(c.env).drizzle + + const ocGeneratorResponses = await drizzle + .select() + .from(savedOcGenerators) + .where(eq(savedOcGenerators.userId, session.userId)) + + return c.json( + { success: true, state: "valid session", ocGeneratorResponses }, + 200 + ) +} diff --git a/src/v2/routes/auth/signup.ts b/src/v2/routes/auth/signup.ts new file mode 100644 index 00000000..522540eb --- /dev/null +++ b/src/v2/routes/auth/signup.ts @@ -0,0 +1,86 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" +// import * as validate from "@/v2/lib/regex/accountValidation"; + +export async function signup(c: Context): Promise { + const formData = await c.req.formData() + + const secretKeyRequiredForSignup = c.env.VERY_SECRET_SIGNUP_KEY + + const username = formData.get("username") as string + const email = formData.get("email") as string + const password = formData.get("password") as string + const passwordConfirm = formData.get("passwordConfirm") as string + const secretKey = formData.get("secretKey") as string + + const validSession = await auth(c.env).handleRequest(c).validate() + if (validSession) + return c.json({ success: false, state: "already logged in" }, 200) + + if ( + secretKeyRequiredForSignup !== secretKey || + passwordConfirm !== password + ) { + return c.json( + { + success: false, + status: "error", + error: "Invalid credentials", + }, + 400 + ) + } + + console.log("creating user") + + try { + const user = await auth(c.env).createUser({ + key: { + providerId: "username", + providerUserId: username.toLowerCase(), + password, + }, + attributes: { + username, + email, + email_verified: 0, + date_joined: Date.now(), + verified: 0, + role_flags: 1, + is_contributor: 0, + self_assignable_role_flags: null, + username_colour: null, + avatar_url: null, + banner_url: null, + pronouns: null, // we can splice this into possesive, subject, and object pronouns by "/", e.g "he/him/his" => {subject: "he", object: "him", possesive: "his"} + bio: "No bio set", + }, + }) + + const userAgent = c.req.headers.get("user-agent") ?? "" + const countryCode = c.req.headers.get("cf-ipcountry") ?? "" + + // TODO: encrypt session attributes with sha256 + const newSession = await auth(c.env).createSession({ + userId: user.userId, + attributes: { + country_code: countryCode, + user_agent: userAgent, + }, + }) + + const authRequest = auth(c.env).handleRequest(c) + authRequest.setSession(newSession) + return c.json({ success: true, state: "logged in" }, 200) + } catch (e) { + console.log(e) + return c.json( + { + success: false, + status: "error", + error: "Error creating user", + }, + 500 + ) + } +} diff --git a/src/v2/routes/auth/tags/createTag.ts b/src/v2/routes/auth/tags/createTag.ts new file mode 100644 index 00000000..fb9aab8d --- /dev/null +++ b/src/v2/routes/auth/tags/createTag.ts @@ -0,0 +1,56 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { assetTags } from "@/v2/db/schema" + +export async function createTag(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + const tag = { + id: crypto.randomUUID(), + name: formData.get("name") as string, + formattedName: formData.get("formattedName") as string, + assetCount: 0, + lastUpdated: new Date().getTime(), // unix timestamp + } + + // check if tag.name exists + const tagExists = await drizzle.query.assetTags.findFirst({ + where: (assetTags, { eq }) => eq(assetTags.name, tag.name), + }) + + if (tagExists) { + return c.json( + { success: false, state: "tag with name already exists" }, + 200 + ) + } + + try { + await drizzle.insert(assetTags).values(tag).execute() + } catch (e) { + return c.json({ success: false, state: "failed to create tag" }, 200) + } + + return c.json({ success: true, state: "created tag", tag }, 200) +} diff --git a/src/v2/routes/auth/tags/deleteTag.ts b/src/v2/routes/auth/tags/deleteTag.ts new file mode 100644 index 00000000..5a1fdb64 --- /dev/null +++ b/src/v2/routes/auth/tags/deleteTag.ts @@ -0,0 +1,60 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { assetTags } from "@/v2/db/schema" +import { eq } from "drizzle-orm" + +export async function deleteTag(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const roleFlags = roleFlagsToArray(session.user.role_flags) + + if (!roleFlags.includes("CREATOR")) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const drizzle = await getConnection(c.env).drizzle + + const formData = await c.req.formData() + + const tag = { + id: formData.get("id") as string | null, + } + + if (!tag.id) { + return c.json({ success: false, state: "no id entered" }, 200) + } + + // check if tag exists + const tagExists = await drizzle.query.assetTags.findFirst({ + where: (assetTags, { eq }) => eq(assetTags.id, tag.id), + }) + + if (!tagExists) { + return c.json( + { success: false, state: "tag with ID doesn't exist" }, + 200 + ) + } + + try { + await drizzle + .delete(assetTags) + .where(eq(assetTags.id, tag.id)) + .execute() + } catch (e) { + return c.json({ success: false, state: "failed to delete tag" }, 200) + } + + return c.json({ success: true, state: "deleted tag", tag }, 200) +} diff --git a/src/v2/routes/auth/user-attributes/self-upload/uploadAvatar.ts b/src/v2/routes/auth/user-attributes/self-upload/uploadAvatar.ts new file mode 100644 index 00000000..ef4b1519 --- /dev/null +++ b/src/v2/routes/auth/user-attributes/self-upload/uploadAvatar.ts @@ -0,0 +1,45 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" + +// TODO: add support for animated avatars +export async function uploadProfileImage(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const avatar = formData.get("avatar") as unknown as File | null + + if (!avatar || avatar.type !== "image/png") { + return c.json({ success: false, state: "invalid avatar" }, 200) + } + + const newAvatar = new File([avatar], `${session.userId}.png`) + const newAvatarURL = `/avatars/${session.userId}.png` + + if (!session.user.avatar_url) { + await auth(c.env).updateUserAttributes(session.userId, { + avatar_url: newAvatarURL, + }) + } + + if (session.user.avatar_url) { + const oldAvatarObject = await c.env.bucket.get(session.user.avatar_url) + + if (oldAvatarObject) { + await c.env.bucket.delete(session.user.avatar_url) + } + } + + await c.env.bucket.put(newAvatarURL, newAvatar) + + return c.json({ success: true, state: "uploaded new profile image" }, 200) +} diff --git a/src/v2/routes/auth/user-attributes/self-upload/uploadBanner.ts b/src/v2/routes/auth/user-attributes/self-upload/uploadBanner.ts new file mode 100644 index 00000000..82387580 --- /dev/null +++ b/src/v2/routes/auth/user-attributes/self-upload/uploadBanner.ts @@ -0,0 +1,49 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" + +// TODO: add support for animated banners +export async function uploadBannerImage(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + if (session.user.is_contributor !== 1) { + return c.json({ success: false, state: "unauthorized" }, 401) + } + + const formData = await c.req.formData() + + const banner = formData.get("banner") as unknown as File | null + + if (!banner || banner.type !== "image/png") { + return c.json({ success: false, state: "invalid banner" }, 200) + } + + const newBanner = new File([banner], `${session.userId}.png`) + const newBannerURL = `/banners/${session.userId}.png` + + if (!session.user.banner_url) { + await auth(c.env).updateUserAttributes(session.userId, { + banner_url: newBannerURL, + }) + } + + if (session.user.banner_url) { + const oldBannerObject = await c.env.bucket.get(session.user.banner_url) + + if (oldBannerObject) { + await c.env.bucket.delete(session.user.banner_url) + } + } + + await c.env.bucket.put(newBannerURL, newBanner) + + return c.json({ success: true, state: "uploaded new banner" }, 200) +} diff --git a/src/v2/routes/auth/user-attributes/updateUserAttributes.ts b/src/v2/routes/auth/user-attributes/updateUserAttributes.ts new file mode 100644 index 00000000..726737b8 --- /dev/null +++ b/src/v2/routes/auth/user-attributes/updateUserAttributes.ts @@ -0,0 +1,38 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" + +export async function updateUserAttributes(c: Context): Promise { + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const attributes = { + username: formData.get("username") as string | null, + pronouns: formData.get("pronouns") as string | null, + self_assignable_role_flags: formData.get( + "self_assignable_roles" + ) as unknown as number | null, + bio: formData.get("bio") as string | null, + } + + // remove null values from attributes + Object.keys(attributes).forEach((key) => { + if (attributes[key] === null) delete attributes[key] + }) + + await auth(c.env).updateUserAttributes(session.userId, attributes) + + return c.json( + { success: true, state: "updated user attributes", session }, + 200 + ) +} diff --git a/src/v2/routes/auth/user-attributes/user-relations/followUser.ts b/src/v2/routes/auth/user-attributes/user-relations/followUser.ts new file mode 100644 index 00000000..188d899b --- /dev/null +++ b/src/v2/routes/auth/user-attributes/user-relations/followUser.ts @@ -0,0 +1,69 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { following, follower } from "@/v2/db/schema" + +export async function followUser(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const userToFollow = formData.get("userIdToFollow") as string | null + + if (!userToFollow) { + return c.json({ success: false, state: "no userid entered" }, 200) + } + + // check if user exists + const user = await drizzle.query.users.findFirst({ + where: (users, { eq }) => eq(users.id, userToFollow), + }) + + if (!user) { + return c.json({ success: false, state: "user not found" }, 200) + } + + const isFollowing = await drizzle.query.following.findFirst({ + where: (following, { eq }) => + eq(following.id, `${session.userId}-${userToFollow}`), + }) + + if (isFollowing) { + return c.json({ success: false, state: "already following" }, 200) + } + + await drizzle.transaction(async (transaction) => { + await transaction + .insert(follower) + .values({ + id: `${session.userId}-${userToFollow}`, + followerUserId: session.userId, + followingUserId: userToFollow, + }) + .execute() + + await transaction + .insert(following) + .values({ + id: `${userToFollow}-${session.userId}`, + followerUserId: userToFollow, + followingUserId: session.userId, + }) + .execute() + + return c.json({ success: true, state: "followed user" }, 200) + }) + + return c.json({ success: false, state: "failed to follow user" }, 500) +} diff --git a/src/v2/routes/auth/user-attributes/user-relations/unfollowUser.ts b/src/v2/routes/auth/user-attributes/user-relations/unfollowUser.ts new file mode 100644 index 00000000..5f3c3e73 --- /dev/null +++ b/src/v2/routes/auth/user-attributes/user-relations/unfollowUser.ts @@ -0,0 +1,62 @@ +import { auth } from "@/v2/lib/auth/lucia" +import { getConnection } from "@/v2/db/turso" +import { APIContext as Context } from "@/worker-configuration" +import { following, follower } from "@/v2/db/schema" +import { eq } from "drizzle-orm" + +export async function unFollowUser(c: Context): Promise { + const drizzle = getConnection(c.env).drizzle + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + return c.json({ success: false, state: "invalid session" }, 200) + } + + const formData = await c.req.formData() + + const userToUnFollow = formData.get("userIdToUnFollow") as string | null + + if (!userToUnFollow) { + return c.json({ success: false, state: "no userid entered" }, 200) + } + + // check if user exists + const user = await drizzle.query.users.findFirst({ + where: (users, { eq }) => eq(users.id, userToUnFollow), + }) + + if (!user) { + return c.json({ success: false, state: "user not found" }, 200) + } + + const isFollowing = await drizzle.query.following.findFirst({ + where: (following, { eq }) => + eq(following.id, `${session.userId}-${userToUnFollow}`), + }) + + if (!isFollowing) { + return c.json({ success: false, state: "not following" }, 200) + } + + await drizzle.transaction(async (transaction) => { + await transaction + .delete(follower) + .where(eq(follower.id, `${session.userId}-${userToUnFollow}`)) + .execute() + + await transaction + .delete(following) + .where(eq(following.id, `${session.userId}-${userToUnFollow}`)) + .execute() + + return c.json({ success: true, state: "unfollowed user" }, 200) + }) + + return c.json({ success: false, state: "failed to unfollow user" }, 200) +} diff --git a/src/v2/routes/auth/validate.ts b/src/v2/routes/auth/validate.ts new file mode 100644 index 00000000..a0c4a665 --- /dev/null +++ b/src/v2/routes/auth/validate.ts @@ -0,0 +1,34 @@ +import { auth } from "@/v2/lib/auth/lucia" +import type { APIContext as Context } from "@/worker-configuration" + +export async function validate(c: Context): Promise { + console.log(c) + const authRequest = auth(c.env).handleRequest(c) + + const session = await authRequest.validate() + + const countryCode = c.req.headers.get("cf-ipcountry") ?? "" + const userAgent = c.req.headers.get("user-agent") ?? "" + + if (!session) { + authRequest.setSession(null) + return c.json({ success: false, state: "invalid session" }, 200) + } + + if ( + session.userAgent !== userAgent || + session.countryCode !== countryCode + ) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + return c.json({ success: false, state: "invalid session" }, 200) + } + + if (session.state === "idle") { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + return c.json({ success: false, state: "invalid session" }, 200) + } + + return c.json({ success: true, state: "valid session", session }, 200) +} diff --git a/src/v2/routes/discord/contributors.ts b/src/v2/routes/discord/contributors.ts new file mode 100644 index 00000000..3c09951b --- /dev/null +++ b/src/v2/routes/discord/contributors.ts @@ -0,0 +1,69 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { roles, guildId } from "@/v2/lib/discord" +import type { Contributor, GuildMember } from "@/v2/lib/types/discord" +import type { APIContext as Context } from "@/worker-configuration" + +// TODO: replace discord contributors with roles on the site +export async function contributors(c: Context): Promise { + const members: Contributor[] = [] + + let after: string | null = null + let fetchUsers = true + + while (fetchUsers) { + const response = await fetch( + `https://discord.com/api/guilds/${guildId}/members?limit=1000${ + after ? `&after=${after}` : "" + }`, + { + headers: { + Authorization: `Bot ${c.env.DISCORD_TOKEN}`, + }, + } + ) + + const guildMembers: GuildMember[] = await response.json() + + const filteredMembers: GuildMember[] = guildMembers.filter((member) => { + return member.roles.some((role) => + Object.keys(roles).includes(role) + ) + }) + + const contributors: Contributor[] = filteredMembers.map((member) => { + const rolesArray = member.roles + .map((role) => roles[role]) + .filter((role) => role) + + // TODO: support animated avatars + return { + id: member.user.id, + username: member.user.username, + globalname: member.user.global_name || null, + avatar: `https://cdn.discordapp.com/avatars/${member.user.id}/${member.user.avatar}.webp`, + roles: rolesArray, + } + }) + + members.push(...contributors) + + if ( + !guildMembers.length || + !guildMembers[guildMembers.length - 1].user + ) { + fetchUsers = false + } + + after = guildMembers[guildMembers.length - 1]?.user?.id + } + + return c.json( + { + success: true, + status: "ok", + contributors: members, + }, + 200, + responseHeaders + ) +} diff --git a/src/v2/routes/discord/discordRoute.ts b/src/v2/routes/discord/discordRoute.ts new file mode 100644 index 00000000..05b9bd5b --- /dev/null +++ b/src/v2/routes/discord/discordRoute.ts @@ -0,0 +1,11 @@ +import { Hono } from "hono" +import { contributors } from "./contributors" +import { Bindings } from "@/worker-configuration" + +const discordRoute = new Hono<{ Bindings: Bindings }>() + +discordRoute.get("/contributors", async (c) => { + return contributors(c) +}) + +export default discordRoute diff --git a/src/v2/routes/games/allGames.ts b/src/v2/routes/games/allGames.ts new file mode 100644 index 00000000..fad06081 --- /dev/null +++ b/src/v2/routes/games/allGames.ts @@ -0,0 +1,54 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { listBucket } from "@/v2/lib/listBucket" +import { games } from "@/v2/db/schema" +import type { APIContext as Context } from "@/worker-configuration" + +export async function getAllGames(c: Context): Promise { + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + if (response) return response + + const files = await listBucket(c.env.bucket, { + prefix: "oc-generators/", + delimiter: "/", + }) + + const results = files.delimitedPrefixes.map((file) => { + return { + name: file.replace("oc-generators/", "").replace("/", ""), + } + }) + + const drizzle = await getConnection(c.env).drizzle + + const gamesList = await drizzle + .select() + .from(games) + .execute() + .then((row) => + row.map((game) => ({ + ...game, + has_generator: results.some( + (generator) => generator.name === game.name + ), + })) + ) + + response = c.json( + { + success: true, + status: "ok", + results: gamesList, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=1200") + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/v2/routes/games/gamesRoute.ts b/src/v2/routes/games/gamesRoute.ts new file mode 100644 index 00000000..d11f0f8e --- /dev/null +++ b/src/v2/routes/games/gamesRoute.ts @@ -0,0 +1,11 @@ +import { Hono } from "hono" +import { getAllGames } from "./allGames" +import { Bindings } from "@/worker-configuration" + +const gamesRoute = new Hono<{ Bindings: Bindings }>() + +gamesRoute.get("/all", async (c) => { + return getAllGames(c) +}) + +export default gamesRoute diff --git a/src/v2/routes/oc-generators/getGenerator.ts b/src/v2/routes/oc-generators/getGenerator.ts new file mode 100644 index 00000000..b19dafc7 --- /dev/null +++ b/src/v2/routes/oc-generators/getGenerator.ts @@ -0,0 +1,40 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { listBucket } from "@/v2/lib/listBucket" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import type { APIContext as Context } from "@/worker-configuration" + +export async function getGeneratorFromName(c: Context): Promise { + const { gameName } = c.req.param() + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + if (response) return response + + const files = await listBucket(c.env.bucket, { + prefix: `oc-generators/${gameName}/list.json`, + }) + + if (files.objects.length === 0) + return createNotFoundResponse(c, "Generator not found", responseHeaders) + + const data = await fetch( + `https://files.wanderer.moe/${files.objects[0].key}` + ) + + const generatorData = await data.json() + + response = c.json( + { + status: "ok", + data: generatorData, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=604800") // the content of this file is unlikely to change, so caching is fine + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/v2/routes/oc-generators/getGenerators.ts b/src/v2/routes/oc-generators/getGenerators.ts new file mode 100644 index 00000000..8c50d6b6 --- /dev/null +++ b/src/v2/routes/oc-generators/getGenerators.ts @@ -0,0 +1,43 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { listBucket } from "@/v2/lib/listBucket" +import type { APIContext as Context } from "@/worker-configuration" + +export async function getGenerators(c: Context): Promise { + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + if (response) return response + + // listing all files inside of oc-generators subfolder, as they can't be manually inputted + // by users but instead stored on the oc-generators repo which is synced with R2 bucket + const files = await listBucket(c.env.bucket, { + prefix: "oc-generators/", + delimiter: "/", + }) + + // console.log(files); + + const results = files.delimitedPrefixes.map((file) => { + return { + name: file.replace("oc-generators/", "").replace("/", ""), + path: `/oc-generators/${file + .replace("oc-generators/", "") + .replace("/", "")}`, + } + }) + + response = c.json( + { + status: "ok", + data: results, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=28800") + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/v2/routes/oc-generators/ocGeneratorRoutes.ts b/src/v2/routes/oc-generators/ocGeneratorRoutes.ts new file mode 100644 index 00000000..c61daf10 --- /dev/null +++ b/src/v2/routes/oc-generators/ocGeneratorRoutes.ts @@ -0,0 +1,16 @@ +import { Hono } from "hono" +import { getGeneratorFromName } from "./getGenerator" +import { getGenerators } from "./getGenerators" +import { Bindings } from "@/worker-configuration" + +const ocGeneratorRoute = new Hono<{ Bindings: Bindings }>() + +ocGeneratorRoute.get("/", async (c) => { + return getGenerators(c) +}) + +ocGeneratorRoute.get("/:gameName", async (c) => { + return getGeneratorFromName(c) +}) + +export default ocGeneratorRoute diff --git a/src/v2/routes/search/all/searchAll.ts b/src/v2/routes/search/all/searchAll.ts new file mode 100644 index 00000000..14917aca --- /dev/null +++ b/src/v2/routes/search/all/searchAll.ts @@ -0,0 +1,111 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import type { APIContext as Context } from "@/worker-configuration" +import { like, eq } from "drizzle-orm" +import { auth } from "@/v2/lib/auth/lucia" + +export async function searchAll(c: Context): Promise { + const { query } = c.req.param() + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + } + + if (response) return response + const drizzle = await getConnection(c.env).drizzle + + // https://cdn.discordapp.com/attachments/1102306276832202813/1147291827699986572/F.gif + const usersResponse = await drizzle.query.users.findMany({ + where: (users) => { + return like(users.username, `%${query}%`) + }, + }) + + const assetCategoryResponse = await drizzle.query.assetCategories.findMany({ + where: (assetCategories) => { + return like(assetCategories.name, `%${query}%`) + }, + }) + + const assetTagsResponse = await drizzle.query.assetTags.findMany({ + where: (assetTags) => { + return like(assetTags.name, `%${query}%`) + }, + }) + + const assetsResponse = await drizzle.query.assets.findMany({ + where: (assets) => { + return like(assets.name, `%${query}%`) + }, + }) + + const gamesResponse = await drizzle.query.games.findMany({ + where: (games) => { + return like(games.name, `%${query}%`) + }, + }) + + const savedOcGeneratorsResponse = + await drizzle.query.savedOcGenerators.findMany({ + where: (savedOcGenerators, { or, and }) => { + return or( + and( + eq(savedOcGenerators.isPublic, 1), + eq(savedOcGenerators.userId, session.userId) + ), + like(savedOcGenerators.name, `%${query}%`) + ) + }, + }) + + const collectionsResponse = await drizzle.query.userCollections.findMany({ + where: (userCollections, { or, and }) => { + return and( + or( + eq(userCollections.userId, session.userId), + eq(userCollections.isPublic, 1) + ), + like(userCollections.name, `%${query}%`) + ) + }, + }) + + response = c.json( + { + success: true, + status: "ok", + query, + isAuthed: session.userId ? true : false, + results: { + usersResponse: usersResponse ? usersResponse : [], + assetsResponse: assetsResponse ? assetsResponse : [], + assetCategoryResponse: assetCategoryResponse + ? assetCategoryResponse + : [], + assetTagsResponse: assetTagsResponse ? assetTagsResponse : [], + savedOcGeneratorsResponse: savedOcGeneratorsResponse + ? savedOcGeneratorsResponse + : [], + gamesResponse: gamesResponse ? gamesResponse : [], + collectionsResponse: collectionsResponse + ? collectionsResponse + : [], + }, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=300") + await cache.put(cacheKey, response.clone()) + return response +} diff --git a/src/v2/routes/search/asset/recentAssets.ts b/src/v2/routes/search/asset/recentAssets.ts new file mode 100644 index 00000000..a2418061 --- /dev/null +++ b/src/v2/routes/search/asset/recentAssets.ts @@ -0,0 +1,36 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { desc } from "drizzle-orm" +import type { APIContext as Context } from "@/worker-configuration" +import { assets } from "@/v2/db/schema" + +// get 100 most recent assets, sorted by asset.uploadedDate +export async function recentAssets(c: Context): Promise { + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + if (response) return response + + const drizzle = await getConnection(c.env).drizzle + + const assetResponse = await drizzle.query.assets.findMany({ + orderBy: desc(assets.uploadedDate), + limit: 100, + where: (assets, { eq }) => eq(assets.status, 1), + }) + + response = c.json( + { + success: true, + status: "ok", + results: assetResponse, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=60") + await cache.put(cacheKey, response.clone()) + return response +} diff --git a/src/v2/routes/search/asset/searchAssets.ts b/src/v2/routes/search/asset/searchAssets.ts new file mode 100644 index 00000000..022f836b --- /dev/null +++ b/src/v2/routes/search/asset/searchAssets.ts @@ -0,0 +1,90 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { like } from "drizzle-orm" +import type { APIContext as Context } from "@/worker-configuration" +import { assets } from "@/v2/db/schema" +import { desc } from "drizzle-orm" +import { SplitQueryByCommas } from "@/v2/lib/helpers/splitQueryByCommas" + +export async function searchForAssets(c: Context): Promise { + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + if (response) return response + + const { query, game, assetCategory, assetTags } = c.req.query() + + // search parameters can include optional search params: query, game, assetCategory, assetTags + // query?: string => ?query=keqing + // game?: comma separated list of game names => ?game=genshin-impact,honkai-impact-3rd + // assetCategory?: comma separated list of asset category names => ?assetCategory=splash-art,character-sheets + // assetTags?: comma separated list of asset tag names => ?assetTags=no-background,fanmade,official + + const drizzle = await getConnection(c.env).drizzle + + // check if certian search parameters are present, if not, set them to null + const searchQuery = query ?? null + const gameList = game ? SplitQueryByCommas(game) : null + const assetCategoryList = assetCategory + ? SplitQueryByCommas(assetCategory) + : null + const assetTagsList = assetTags ? SplitQueryByCommas(assetTags) : null + + // query the database for assets that match the search parameters + const assetResponse = await drizzle.query.assets.findMany({ + where: (assets, { and, or, eq }) => { + return and( + searchQuery ? like(assets.name, `%${searchQuery}%`) : null, + gameList + ? or(...gameList.map((game) => eq(assets.game, game))) + : null, + assetCategoryList + ? or( + ...assetCategoryList.map((assetCategory) => + eq(assets.assetCategory, assetCategory) + ) + ) + : null, + eq(assets.status, 1) + ) + }, + // i think i am overcomplicating this + ...((assetTagsList?.length ?? 0) > 0 && { + with: { + assetTagsAssets: { + with: { + assetTags: { + where: (assetTags, { and, eq }) => { + return and( + ...assetTagsList.map((assetTag) => + eq(assetTags.name, assetTag) + ) + ) + }, + }, + }, + }, + }, + }), + orderBy: desc(assets.id), + limit: 1000, + }) + + response = c.json( + { + success: true, + status: "ok", + query, + game, + assetCategory, + assetTags, + results: assetResponse ? assetResponse : [], + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=600") + await cache.put(cacheKey, response.clone()) + return response +} diff --git a/src/v2/routes/search/searchRoute.ts b/src/v2/routes/search/searchRoute.ts new file mode 100644 index 00000000..2446733a --- /dev/null +++ b/src/v2/routes/search/searchRoute.ts @@ -0,0 +1,49 @@ +import { Hono } from "hono" +import { searchAll } from "./all/searchAll" +import { Bindings } from "@/worker-configuration" +import { getUserByUsername } from "./user/getUserByUsername" +import { getUsersBySearch } from "./user/getUsersBySearch" +import authRoute from "../auth/authRoute" +import { searchForAssets } from "./asset/searchAssets" +import { recentAssets } from "./asset/recentAssets" +import { cors } from "hono/cors" + +const searchRoute = new Hono<{ Bindings: Bindings }>() + +searchRoute.get("/all", async (c) => { + return searchAll(c) +}) + +authRoute.use( + "/all", + cors({ + credentials: true, + origin: ["https://next.wanderer.moe"], + }) +) + +authRoute.use( + "/users/user/:username", + cors({ + credentials: true, + origin: ["https://next.wanderer.moe"], + }) +) + +searchRoute.get("/assets/query", async (c) => { + return searchForAssets(c) +}) + +searchRoute.get("/assets/recent", async (c) => { + return recentAssets(c) +}) + +searchRoute.get("/users/user/:username", async (c) => { + return getUserByUsername(c) +}) + +searchRoute.get("/users/query/:query", async (c) => { + return getUsersBySearch(c) +}) + +export default searchRoute diff --git a/src/v2/routes/search/user/getUserByUsername.ts b/src/v2/routes/search/user/getUserByUsername.ts new file mode 100644 index 00000000..88a35c6d --- /dev/null +++ b/src/v2/routes/search/user/getUserByUsername.ts @@ -0,0 +1,101 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { getConnection } from "@/v2/db/turso" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import type { APIContext as Context } from "@/worker-configuration" +import { desc } from "drizzle-orm" +import { assets, userCollections } from "@/v2/db/schema" +import { auth } from "@/v2/lib/auth/lucia" +import { roleFlagsToArray } from "@/v2/lib/helpers/roleFlags" + +export async function getUserByUsername(c: Context): Promise { + const { username } = c.req.param() + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + const authRequest = auth(c.env).handleRequest(c) + const session = await authRequest.validate() + + if (!session || session.state === "idle" || session.state === "invalid") { + if (session) { + await auth(c.env).invalidateSession(session.sessionId) + authRequest.setSession(null) + } + } + + if (response) return response + const drizzle = await getConnection(c.env).drizzle + + const user = await drizzle.query.users.findFirst({ + where: (users, { and, eq }) => and(eq(users.username, username)), + with: { + assets: { + orderBy: desc(assets.uploadedDate), + limit: 100, + where: (assets, { eq }) => eq(assets.status, 1), + }, + userCollections: { + orderBy: desc(userCollections.dateCreated), + where: (userCollections, { eq, or }) => + or( + eq(userCollections.isPublic, 1), + session && eq(userCollections.userId, session.userId) + ), + limit: 5, + with: { + assets: { + orderBy: desc(assets.uploadedDate), + limit: 100, + where: (assets, { eq }) => eq(assets.status, 1), + }, + }, + }, + userFavorites: { + where: (userFavorites, { eq }) => eq(userFavorites.isPublic, 1), + with: { + assets: { + orderBy: desc(assets.uploadedDate), + limit: 100, + where: (assets, { eq }) => eq(assets.status, 1), + }, + }, + }, + savedOcGenerators: { + where: (savedOcGenerators, { eq, or }) => + or( + eq(savedOcGenerators.isPublic, 1), + session && eq(savedOcGenerators.userId, session.userId) + ), + }, + }, + }) + + if (!user) { + response = createNotFoundResponse(c, "User not found", responseHeaders) + await cache.put(cacheKey, response.clone()) + return response + } + + // removing email-related fields + user.email = undefined + user.emailVerified = undefined + + response = c.json( + { + success: true, + status: "ok", + accountIsAuthed: session.userId ? true : false, + userIsQueryingOwnAccount: + session && session.userId === user.id ? true : false, + userRoleFlagsArray: roleFlagsToArray(user.roleFlags), + user, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=120") + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/v2/routes/search/user/getUsersBySearch.ts b/src/v2/routes/search/user/getUsersBySearch.ts new file mode 100644 index 00000000..f9738019 --- /dev/null +++ b/src/v2/routes/search/user/getUsersBySearch.ts @@ -0,0 +1,41 @@ +import { responseHeaders } from "@/v2/lib/responseHeaders" +import { createNotFoundResponse } from "@/v2/lib/helpers/responses/notFoundResponse" +import { getConnection } from "@/v2/db/turso" +import { like } from "drizzle-orm" +import type { APIContext as Context } from "@/worker-configuration" + +export async function getUsersBySearch(c: Context): Promise { + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + if (response) return response + + const { query } = c.req.param() + const drizzle = await getConnection(c.env).drizzle + + const userList = await drizzle.query.users.findMany({ + where: (users, { or }) => { + return or(like(users.username, `%${query}%`)) + }, + }) + + if (!userList) { + return createNotFoundResponse(c, "Users not found", responseHeaders) + } + + response = c.json( + { + success: true, + status: "ok", + query, + results: userList, + }, + 200, + responseHeaders + ) + + response.headers.set("Cache-Control", "s-maxage=60") + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/v2/routes/tags/allTags.ts b/src/v2/routes/tags/allTags.ts new file mode 100644 index 00000000..f8546973 --- /dev/null +++ b/src/v2/routes/tags/allTags.ts @@ -0,0 +1,37 @@ +import { getConnection } from "@/v2/db/turso" +import type { APIContext as Context } from "@/worker-configuration" + +export async function listAllAssetTags(c: Context): Promise { + const cacheKey = new Request(c.req.url.toString(), c.req) + const cache = caches.default + let response = await cache.match(cacheKey) + + if (response) return response + + const drizzle = await getConnection(c.env).drizzle + + const allAssetTags = await drizzle.query.assetTags.findMany({ + orderBy: (assetTags) => assetTags.name, + with: { + assetTagsAssets: { + with: { + assets: true, + }, + }, + }, + }) + + response = c.json( + { + success: true, + status: "ok", + allAssetTags, + }, + 200 + ) + + response.headers.set("Cache-Control", "s-maxage=604800") + await cache.put(cacheKey, response.clone()) + + return response +} diff --git a/src/worker-configuration.d.ts b/src/worker-configuration.d.ts index 54bbb4c5..12df54bb 100644 --- a/src/worker-configuration.d.ts +++ b/src/worker-configuration.d.ts @@ -1,10 +1,13 @@ -export interface Env { - DISCORD_TOKEN: string; - bucket: R2Bucket; - DATABASE_URL: string; - DATABASE_PASSWORD: string; - DATABASE_USERNAME: string; - DATABASE_HOST: string; - ENVIRONMENT: string; - VERY_SECRET_SIGNUP_KEY: string; +import type { Context } from "hono" + +export type Bindings = { + DISCORD_TOKEN: string + bucket: R2Bucket + ENVIRONMENT: string + VERY_SECRET_SIGNUP_KEY: string + TURSO_DATABASE_URL: string + TURSO_DATABASE_AUTH_TOKEN: string } + +// this is the onl way i could figure out how to pass bindings to all the routes +export type APIContext = Context<{ Bindings: Bindings }> diff --git a/tsconfig.json b/tsconfig.json index ee2b46af..bfffdefb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "skipLibCheck": true, - "target": "es2021", - "module": "es2022", - "moduleResolution": "node", - "lib": ["es2021"], - "baseUrl": "./", - "paths": { - "@/*": ["src/*"] - }, - "types": ["@cloudflare/workers-types"] - }, - "include": ["src/**/*.ts"] + "compilerOptions": { + "skipLibCheck": true, + "target": "es2021", + "module": "es2022", + "moduleResolution": "node", + "lib": ["es2021"], + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, + "types": ["@cloudflare/workers-types"] + }, + "include": ["src/**/*.ts"] }