From aad5552a6e0a7501e1407e937635f6caca19d902 Mon Sep 17 00:00:00 2001 From: Lucio Rubens Date: Wed, 22 Oct 2025 14:57:28 -0300 Subject: [PATCH 1/9] antfu --- eslint.config.mjs | 140 +- package.json | 26 +- packages/api-main/package.json | 95 +- packages/frontend-main/package.json | 103 +- packages/reader-main/package.json | 56 +- pnpm-lock.yaml | 1875 ++++++++++++++++++++++++--- 6 files changed, 1857 insertions(+), 438 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 7630157f..18716dc9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,123 +1,23 @@ -import { builtinModules } from 'module'; +import antfu from '@antfu/eslint-config'; -import jsLint from '@eslint/js'; -import stylistic from '@stylistic/eslint-plugin'; -import pluginSimpleImportSort from 'eslint-plugin-simple-import-sort'; -import vueLint from 'eslint-plugin-vue'; -import globals from 'globals'; -import tsLint from 'typescript-eslint'; - -export default [ - // config parsers - { - files: ['**/*.{js,mjs,cjs,ts,mts,jsx,tsx}'], - languageOptions: { - parser: tsLint.parser, - parserOptions: { - sourceType: 'module', - }, - }, - }, - { - files: ['*.vue', '**/*.vue'], - languageOptions: { - parser: vueLint.parser, - parserOptions: { - parser: '@typescript-eslint/parser', - sourceType: 'module', - }, - }, - }, - // config envs - { - languageOptions: { - globals: { ...globals.browser, ...globals.node }, - }, - }, - // rules - jsLint.configs.recommended, - ...tsLint.configs.recommended, - ...vueLint.configs['flat/essential'], - { - rules: { - '@typescript-eslint/consistent-type-imports': [ - 'error', - { - prefer: 'type-imports', - fixStyle: 'separate-type-imports', - }, - ], - '@typescript-eslint/no-explicit-any': [ - 'warn', - { ignoreRestArgs: true }, - ], - '@typescript-eslint/no-unused-vars': [ - 'error', - { - args: 'all', - argsIgnorePattern: '^_', - caughtErrors: 'all', - caughtErrorsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], - }, - }, - { - plugins: { - 'vue': vueLint, - '@typescript-eslint': tsLint.plugin, - 'simple-import-sort': pluginSimpleImportSort, - }, - rules: { - 'simple-import-sort/imports': [ - 'error', - { - groups: [ - [ - `node:`, - `^(${builtinModules.join('|')})(/|$)`, - ], - // style less,scss,css - ['^.+\\.less$', '^.+\\.s?css$'], - // Side effect imports. - ['^\\u0000'], - ['^@?\\w.*\\u0000$', '^[^.].*\\u0000$', '^\\..*\\u0000$'], - ['^vue', '^@vue', '^@?\\w', '^\\u0000'], - ['^@/utils'], - ['^@/composables'], - // Parent imports. Put `..` last. - ['^\\.\\.(?!/?$)', '^\\.\\./?$'], - // Other relative imports. Put same-folder imports and `.` last. - ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'], - ], - }, - ], - // see more vue rules: https://eslint.vuejs.org/rules/ - 'vue/no-mutating-props': 'error', - 'vue/multi-word-component-names': 'off', - 'vue/html-indent': ['error', 2], - }, +export default antfu( + { + typescript: true, + vue: true, + jsonc: true, + yaml: true, + markdown: true, + formatters: true, + stylistic: { + indent: 2, + quotes: 'single', + semi: true, }, - // see: https://eslint.style/guide/getting-started - // see: https://github.com/eslint-stylistic/eslint-stylistic/blob/main/packages/eslint-plugin/configs/disable-legacy.ts - stylistic.configs['disable-legacy'], - stylistic.configs.customize({ - indent: 4, - quotes: 'single', - semi: true, - jsx: true, - }), - { - // https://eslint.org/docs/latest/use/configure/ignore - ignores: [ - 'node_modules', - '**/node_modules', - '**/*.conf', - '**/nginx/**', - '**/data/postgres_data', - '**/dist/**', - ], + }, + { + files: ['tsconfig.json', 'package.json'], + rules: { + 'jsonc/sort-keys': 'off', }, -]; + }, +); diff --git a/package.json b/package.json index 4d823ba5..193a2653 100644 --- a/package.json +++ b/package.json @@ -7,26 +7,20 @@ "author": "", "license": "ISC", "scripts": { - "lint": "eslint . --ext .js,.ts,.jsx,.tsx,.vue", - "lint:fix": "eslint . --ext .js,.ts,.jsx,.tsx,.vue --fix", + "lint": "eslint", + "lint:fix": "eslint --fix", "docs:build": "retype build ./docs", "docs:start": "retype start ./docs" }, + "dependencies": { + "retypeapp": "^3.11.0" + }, "devDependencies": { - "@eslint/js": "^9.37.0", - "@stylistic/eslint-plugin": "^4.4.1", - "@typescript-eslint/eslint-plugin": "^8.46.1", + "@antfu/eslint-config": "^6.0.0", + "@types/node": "^22.18.10", "eslint": "^9.37.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-vue": "^10.5.0", - "globals": "^16.4.0", - "prettier": "^3.6.2", - "typescript-eslint": "^8.46.1" + "eslint-plugin-format": "^1.0.2", + "typescript": "^5.9.3" }, - "packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912", - "dependencies": { - "retypeapp": "^3.11.0" - } + "packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912" } diff --git a/packages/api-main/package.json b/packages/api-main/package.json index 467dcb2a..ad0d97af 100644 --- a/packages/api-main/package.json +++ b/packages/api-main/package.json @@ -1,52 +1,45 @@ { - "name": "api-feed", - "version": "1.0.0", - "description": "", - "author": "stuyk", - "main": "dist/index.js", - "type": "module", - "scripts": { - "start": "vite-node ./src/index.ts", - "build": "tsc", - "test": "vitest", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - "db:push": "drizzle-kit push", - "db:push:force": "drizzle-kit push --force", - "push-and-start": "drizzle-kit push --force && pnpm start" - }, - "dependencies": { - "@atomone/chronostate": "^2.3.0", - "@atomone/dither-api-types": "^1.8.0", - "@cosmjs/crypto": "^0.33.1", - "@cosmjs/encoding": "^0.33.1", - "@elysiajs/cors": "~1.2.0", - "@elysiajs/node": "~1.2.6", - "@keplr-wallet/cosmos": "^0.12.280", - "@sinclair/typebox": "^0.34.41", - "dotenv": "^16.6.1", - "drizzle-orm": "^0.43.1", - "drizzle-typebox": "^0.3.3", - "elysia": "~1.2.25", - "jsonwebtoken": "^9.0.2", - "pg": "^8.16.3", - "postgres": "^3.4.7", - "ws": "^8.18.3" - }, - "devDependencies": { - "@cosmjs/amino": "^0.33.1", - "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^22.18.10", - "@types/pg": "^8.15.5", - "drizzle-kit": "^0.31.5", - "tsx": "^4.20.6", - "vite-node": "^3.2.4", - "vitest": "^3.2.4" - }, - "prettier": { - "tabWidth": 4, - "singleQuote": true, - "semi": true, - "printWidth": 120 - } -} \ No newline at end of file + "name": "api-feed", + "type": "module", + "version": "1.0.0", + "description": "", + "author": "stuyk", + "main": "dist/index.js", + "scripts": { + "start": "vite-node ./src/index.ts", + "build": "tsc", + "test": "vitest", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push", + "db:push:force": "drizzle-kit push --force", + "push-and-start": "drizzle-kit push --force && pnpm start" + }, + "dependencies": { + "@atomone/chronostate": "^2.3.0", + "@atomone/dither-api-types": "^1.8.0", + "@cosmjs/crypto": "^0.33.1", + "@cosmjs/encoding": "^0.33.1", + "@elysiajs/cors": "~1.2.0", + "@elysiajs/node": "~1.2.6", + "@keplr-wallet/cosmos": "^0.12.280", + "@sinclair/typebox": "^0.34.41", + "dotenv": "^16.6.1", + "drizzle-orm": "^0.43.1", + "drizzle-typebox": "^0.3.3", + "elysia": "~1.2.25", + "jsonwebtoken": "^9.0.2", + "pg": "^8.16.3", + "postgres": "^3.4.7", + "ws": "^8.18.3" + }, + "devDependencies": { + "@cosmjs/amino": "^0.33.1", + "@types/jsonwebtoken": "^9.0.10", + "@types/pg": "^8.15.5", + "drizzle-kit": "^0.31.5", + "tsx": "^4.20.6", + "vite-node": "^3.2.4", + "vitest": "^3.2.4" + } +} diff --git a/packages/frontend-main/package.json b/packages/frontend-main/package.json index 4aff1e75..9f6a380b 100644 --- a/packages/frontend-main/package.json +++ b/packages/frontend-main/package.json @@ -1,57 +1,50 @@ { - "name": "frontend-main", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc -b && vite build", - "preview": "vite preview" - }, - "dependencies": { - "@cosmjs/math": "^0.33.1", - "@cosmjs/proto-signing": "^0.32.4", - "@cosmjs/stargate": "^0.32.4", - "@cosmostation/cosmos-client": "^0.0.5", - "@sinclair/typebox": "^0.34.41", - "@tailwindcss/vite": "^4.1.14", - "@tanstack/vue-query": "^5.90.3", - "@vueuse/core": "^13.9.0", - "bech32": "^2.0.0", - "bignumber.js": "^9.3.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cosmjs-types": "^0.9.0", - "json-editor-vue": "^0.18.1", - "lucide-vue-next": "^0.507.0", - "mitt": "^3.0.1", - "pinia": "^3.0.3", - "pinia-plugin-persistedstate": "^4.5.0", - "reka-ui": "^2.5.1", - "stint-signer": "^0.4.0", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.14", - "tw-animate-css": "^1.4.0", - "vanilla-jsoneditor": "^3.10.0", - "vue": "^3.5.22", - "vue-i18n": "^10.0.8", - "vue-router": "^4.6.0", - "vue-sonner": "^2.0.9" - }, - "devDependencies": { - "@keplr-wallet/types": "^0.12.280", - "@types/node": "^22.18.10", - "@vitejs/plugin-vue": "^5.2.4", - "@vue/tsconfig": "^0.7.0", - "typescript": "~5.7.3", - "vite": "^6.3.7", - "vite-plugin-node-polyfills": "^0.23.0", - "vue-tsc": "^2.2.12" - }, - "prettier": { - "tabWidth": 4, - "singleQuote": true, - "semi": true, - "printWidth": 120 - } + "name": "frontend-main", + "type": "module", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@cosmjs/math": "^0.33.1", + "@cosmjs/proto-signing": "^0.32.4", + "@cosmjs/stargate": "^0.32.4", + "@cosmostation/cosmos-client": "^0.0.5", + "@sinclair/typebox": "^0.34.41", + "@tailwindcss/vite": "^4.1.14", + "@tanstack/vue-query": "^5.90.3", + "@vueuse/core": "^13.9.0", + "bech32": "^2.0.0", + "bignumber.js": "^9.3.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cosmjs-types": "^0.9.0", + "json-editor-vue": "^0.18.1", + "lucide-vue-next": "^0.507.0", + "mitt": "^3.0.1", + "pinia": "^3.0.3", + "pinia-plugin-persistedstate": "^4.5.0", + "reka-ui": "^2.5.1", + "stint-signer": "^0.4.0", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.14", + "tw-animate-css": "^1.4.0", + "vanilla-jsoneditor": "^3.10.0", + "vue": "^3.5.22", + "vue-i18n": "^10.0.8", + "vue-router": "^4.6.0", + "vue-sonner": "^2.0.9" + }, + "devDependencies": { + "@keplr-wallet/types": "^0.12.280", + "@vitejs/plugin-vue": "^5.2.4", + "@vue/tsconfig": "^0.7.0", + "typescript": "~5.7.3", + "vite": "^6.3.7", + "vite-plugin-node-polyfills": "^0.23.0", + "vue-tsc": "^2.2.12" + } } diff --git a/packages/reader-main/package.json b/packages/reader-main/package.json index 01badb22..b393cf7a 100644 --- a/packages/reader-main/package.json +++ b/packages/reader-main/package.json @@ -1,33 +1,25 @@ { - "name": "@atomone/indexer-feed", - "version": "1.0.0", - "description": "", - "author": "stuyk", - "main": "dist/index.js", - "type": "module", - "scripts": { - "build": "tsc", - "start": "vite-node ./src/index.ts", - "test": "vitest" - }, - "dependencies": { - "@atomone/chronostate": "^2.3.0", - "@atomone/dither-api-types": "^1.8.0", - "graphql": "^16.11.0", - "graphql-request": "^6.1.0", - "tsx": "^4.20.6" - }, - "prettier": { - "tabWidth": 4, - "singleQuote": true, - "semi": true, - "printWidth": 120 - }, - "devDependencies": { - "@types/node": "^22.18.10", - "@types/pg": "^8.15.5", - "typescript": "^5.9.3", - "vite-node": "^3.2.4", - "vitest": "^3.2.4" - } -} \ No newline at end of file + "name": "@atomone/indexer-feed", + "type": "module", + "version": "1.0.0", + "description": "", + "author": "stuyk", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "vite-node ./src/index.ts", + "test": "vitest" + }, + "dependencies": { + "@atomone/chronostate": "^2.3.0", + "@atomone/dither-api-types": "^1.8.0", + "graphql": "^16.11.0", + "graphql-request": "^6.1.0", + "tsx": "^4.20.6" + }, + "devDependencies": { + "@types/pg": "^8.15.5", + "vite-node": "^3.2.4", + "vitest": "^3.2.4" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 954b3b3c..31f557f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,39 +12,21 @@ importers: specifier: ^3.11.0 version: 3.11.0 devDependencies: - '@eslint/js': - specifier: ^9.37.0 - version: 9.37.0 - '@stylistic/eslint-plugin': - specifier: ^4.4.1 - version: 4.4.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/eslint-plugin': - specifier: ^8.46.1 - version: 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@antfu/eslint-config': + specifier: ^6.0.0 + version: 6.0.0(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.2(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) + '@types/node': + specifier: ^22.18.10 + version: 22.18.10 eslint: specifier: ^9.37.0 version: 9.37.0(jiti@2.6.1) - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.37.0(jiti@2.6.1)) - eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(prettier@3.6.2) - eslint-plugin-simple-import-sort: - specifier: ^12.1.1 - version: 12.1.1(eslint@9.37.0(jiti@2.6.1)) - eslint-plugin-vue: - specifier: ^10.5.0 - version: 10.5.0(@stylistic/eslint-plugin@4.4.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(vue-eslint-parser@10.1.3(eslint@9.37.0(jiti@2.6.1))) - globals: - specifier: ^16.4.0 - version: 16.4.0 - prettier: - specifier: ^3.6.2 - version: 3.6.2 - typescript-eslint: - specifier: ^8.46.1 - version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-format: + specifier: ^1.0.2 + version: 1.0.2(eslint@9.37.0(jiti@2.6.1)) + typescript: + specifier: ^5.9.3 + version: 5.9.3 packages/api-main: dependencies: @@ -103,9 +85,6 @@ importers: '@types/jsonwebtoken': specifier: ^9.0.10 version: 9.0.10 - '@types/node': - specifier: ^22.18.10 - version: 22.18.10 '@types/pg': specifier: ^8.15.5 version: 8.15.5 @@ -117,10 +96,10 @@ importers: version: 4.20.6 vite-node: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) packages/frontend-main: dependencies: @@ -141,7 +120,7 @@ importers: version: 0.34.41 '@tailwindcss/vite': specifier: ^4.1.14 - version: 4.1.14(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)) + version: 4.1.14(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) '@tanstack/vue-query': specifier: ^5.90.3 version: 5.90.3(vue@3.5.22(typescript@5.7.3)) @@ -212,12 +191,9 @@ importers: '@keplr-wallet/types': specifier: ^0.12.280 version: 0.12.280(starknet@6.24.1) - '@types/node': - specifier: ^22.18.10 - version: 22.18.10 '@vitejs/plugin-vue': specifier: ^5.2.4 - version: 5.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6))(vue@3.5.22(typescript@5.7.3)) + version: 5.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.7.3)) '@vue/tsconfig': specifier: ^0.7.0 version: 0.7.0(typescript@5.7.3)(vue@3.5.22(typescript@5.7.3)) @@ -226,10 +202,10 @@ importers: version: 5.7.3 vite: specifier: ^6.3.7 - version: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + version: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) vite-plugin-node-polyfills: specifier: ^0.23.0 - version: 0.23.0(rollup@4.45.1)(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)) + version: 0.23.0(rollup@4.45.1)(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) vue-tsc: specifier: ^2.2.12 version: 2.2.12(typescript@5.7.3) @@ -262,21 +238,15 @@ importers: specifier: ^4.20.6 version: 4.20.6 devDependencies: - '@types/node': - specifier: ^22.18.10 - version: 22.18.10 '@types/pg': specifier: ^8.15.5 version: 8.15.5 - typescript: - specifier: ^5.9.3 - version: 5.9.3 vite-node: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) packages/tool-network-spammer: dependencies: @@ -306,6 +276,64 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@antfu/eslint-config@6.0.0': + resolution: {integrity: sha512-M2RM+x+hpxpASEZzQh4d5uaUEHn8sYNVlTB+CySpLkDs2rr3QFvRR7KqNdnox/OIPc6YWMsIEnM/XUbQP52nTA==} + hasBin: true + peerDependencies: + '@eslint-react/eslint-plugin': ^2.0.1 + '@next/eslint-plugin-next': ^15.4.0-canary.115 + '@prettier/plugin-xml': ^3.4.1 + '@unocss/eslint-plugin': '>=0.50.0' + astro-eslint-parser: ^1.0.2 + eslint: ^9.10.0 + eslint-plugin-astro: ^1.2.0 + eslint-plugin-format: '>=0.1.0' + eslint-plugin-jsx-a11y: '>=6.10.2' + eslint-plugin-react-hooks: ^7.0.0 + eslint-plugin-react-refresh: ^0.4.19 + eslint-plugin-solid: ^0.14.3 + eslint-plugin-svelte: '>=2.35.1' + eslint-plugin-vuejs-accessibility: ^2.4.1 + prettier-plugin-astro: ^0.14.0 + prettier-plugin-slidev: ^1.0.5 + svelte-eslint-parser: '>=0.37.0' + peerDependenciesMeta: + '@eslint-react/eslint-plugin': + optional: true + '@next/eslint-plugin-next': + optional: true + '@prettier/plugin-xml': + optional: true + '@unocss/eslint-plugin': + optional: true + astro-eslint-parser: + optional: true + eslint-plugin-astro: + optional: true + eslint-plugin-format: + optional: true + eslint-plugin-jsx-a11y: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-solid: + optional: true + eslint-plugin-svelte: + optional: true + eslint-plugin-vuejs-accessibility: + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-slidev: + optional: true + svelte-eslint-parser: + optional: true + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@atomone/chronostate@2.3.0': resolution: {integrity: sha512-rGlOUPtrW1qjqODM4iVECoR2GxueXWC0WXiglG1L5DE6CIEnPjSHsawLlwNPttEHU1Fx9CMPrPQgnaVwFlL86w==} @@ -338,6 +366,12 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@clack/core@0.5.0': + resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} + + '@clack/prompts@0.11.0': + resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} + '@codemirror/autocomplete@6.18.6': resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} @@ -380,12 +414,15 @@ packages: '@cosmjs/crypto@0.28.13': resolution: {integrity: sha512-ynKfM0q/tMBQMHJby6ad8lR3gkgBKaelQhIsCZTjClsnuC7oYT9y3ThSZCUWr7Pa9h0J8ahU2YV2oFWFVWJQzQ==} + deprecated: This uses elliptic for cryptographic operations, which contains several security-relevant bugs. To what degree this affects your application is something you need to carefully investigate. See https://github.com/cosmos/cosmjs/issues/1708 for further pointers. Starting with version 0.34.0 the cryptographic library has been replaced. However, private keys might still be at risk. '@cosmjs/crypto@0.32.4': resolution: {integrity: sha512-zicjGU051LF1V9v7bp8p7ovq+VyC91xlaHdsFOTo2oVry3KQikp8L/81RkXmUIT8FxMwdx1T7DmFwVQikcSDIw==} + deprecated: This uses elliptic for cryptographic operations, which contains several security-relevant bugs. To what degree this affects your application is something you need to carefully investigate. See https://github.com/cosmos/cosmjs/issues/1708 for further pointers. Starting with version 0.34.0 the cryptographic library has been replaced. However, private keys might still be at risk. '@cosmjs/crypto@0.33.1': resolution: {integrity: sha512-U4kGIj/SNBzlb2FGgA0sMR0MapVgJUg8N+oIAiN5+vl4GZ3aefmoL1RDyTrFS/7HrB+M+MtHsxC0tvEu4ic/zA==} + deprecated: This uses elliptic for cryptographic operations, which contains several security-relevant bugs. To what degree this affects your application is something you need to carefully investigate. See https://github.com/cosmos/cosmjs/issues/1708 for further pointers. Starting with version 0.34.0 the cryptographic library has been replaced. However, private keys might still be at risk. '@cosmjs/crypto@0.34.0': resolution: {integrity: sha512-hn8Z1RYS9bhT5mbitGhPYF5CMcln9r2BVZ7nXIpfpI7TdhUEmNnHVI2ddodxFun0uOg8kokOM6X/etD/MZ6NFA==} @@ -492,6 +529,15 @@ packages: '@cosmostation/wc-modal@0.0.4': resolution: {integrity: sha512-/SkbFKKQxUCMI8ZnULVTsWLFUx9pG+p7MFvv/Ifgz+xBAPe9nBCJlJ/LdvJ6HuAIPajc1vcXB2/OzGL9TjMpNw==} + '@dprint/formatter@0.3.0': + resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==} + + '@dprint/markdown@0.17.8': + resolution: {integrity: sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==} + + '@dprint/toml@0.6.4': + resolution: {integrity: sha512-bZXIUjxr0LIuHWshZr/5mtUkOrnh0NKVZEF6ACojW5z7zkJu7s9sV2mMXm8XQDqN4cJzdHYUYzUyEGdfciaLJA==} + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -511,6 +557,14 @@ packages: bufferutil: optional: true + '@es-joy/jsdoccomment@0.50.2': + resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} + engines: {node: '>=18'} + + '@es-joy/jsdoccomment@0.58.0': + resolution: {integrity: sha512-smMc5pDht/UVsCD3hhw/a/e/p8m0RdRYiluXToVfd+d4yaQQh7nn9bACjkk6nXJvat7EWPAxuFkMEFfrxeGa3Q==} + engines: {node: '>=20.11.0'} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -807,6 +861,12 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-plugin-eslint-comments@4.5.0': + resolution: {integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -823,6 +883,15 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/compat@1.4.0': + resolution: {integrity: sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.40 || 9 + peerDependenciesMeta: + eslint: + optional: true + '@eslint/config-array@0.21.0': resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -831,6 +900,10 @@ packages: resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.16.0': resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -843,10 +916,18 @@ packages: resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/markdown@7.5.0': + resolution: {integrity: sha512-reKloVSpytg4ene3yviPJcUO7zglpNn9kWNRiSQ/8gBbBFMKW5Q042LaCi3wv2vVtbPNnLrl6WvhRAHeBd43QA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.4.0': resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1058,6 +1139,10 @@ packages: '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@pkgr/core@0.1.2': + resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1232,8 +1317,8 @@ packages: '@starknet-io/types-js@0.7.10': resolution: {integrity: sha512-1VtCqX4AHWJlRRSYGSn+4X1mqolI1Tdq62IwzoU2vUuEE72S1OlEeGhpvd6XsdqXcfHmVzYfj8k1XtKBQqwo9w==} - '@stylistic/eslint-plugin@4.4.1': - resolution: {integrity: sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==} + '@stylistic/eslint-plugin@5.5.0': + resolution: {integrity: sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' @@ -1363,6 +1448,9 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -1378,6 +1466,9 @@ packages: '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -1390,6 +1481,9 @@ packages: '@types/pg@8.15.5': resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} @@ -1459,6 +1553,19 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 + '@vitest/eslint-plugin@1.3.23': + resolution: {integrity: sha512-kp1vjoJTdVf8jWdzr/JpHIPfh3HMR6JBr2p7XuH4YNx0UXmV4XWdgzvCpAmH8yb39Gry31LULiuBcuhyc/OqkQ==} + engines: {node: '>=18'} + peerDependencies: + eslint: '>= 8.57.0' + typescript: '>= 5.0.0' + vitest: '*' + peerDependenciesMeta: + typescript: + optional: true + vitest: + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -1675,6 +1782,14 @@ packages: ansicolors@0.3.2: resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1728,6 +1843,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.8.19: + resolution: {integrity: sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ==} + hasBin: true + bech32@1.1.4: resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} @@ -1810,6 +1929,11 @@ packages: browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bs58@4.0.1: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} @@ -1837,6 +1961,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + builtin-modules@5.0.0: + resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} + engines: {node: '>=18.20'} + builtin-status-codes@3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} @@ -1860,10 +1988,16 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001751: + resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} + cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} @@ -1872,6 +2006,12 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -1880,6 +2020,10 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + cipher-base@1.0.6: resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} engines: {node: '>= 0.10'} @@ -1887,6 +2031,10 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1917,9 +2065,19 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + console-browserify@1.2.0: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} @@ -1934,6 +2092,9 @@ packages: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} + core-js-compat@3.46.0: + resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -1995,6 +2156,18 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} @@ -2028,6 +2201,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + des.js@1.1.0: resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} @@ -2041,9 +2218,16 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + diff-sequences@27.5.1: + resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2169,6 +2353,9 @@ packages: resolution: {integrity: sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==} engines: {node: '>=8.0.0'} + electron-to-chromium@1.5.238: + resolution: {integrity: sha512-khBdc+w/Gv+cS8e/Pbnaw/FXcBUeKrRVik9IxfXtgREOWyJhR4tj43n3amkVogJ/yeQUqzkrZcFhtIxIdqmmcQ==} + elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -2187,6 +2374,10 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -2233,34 +2424,152 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-compat-utils@0.5.1: + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} peerDependencies: - eslint: '>=7.0.0' + eslint: '>=6.0.0' - eslint-plugin-prettier@5.5.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} - engines: {node: ^14.18.0 || >=16.0.0} + eslint-compat-utils@0.6.5: + resolution: {integrity: sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + + eslint-config-flat-gitignore@2.1.0: + resolution: {integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==} + peerDependencies: + eslint: ^9.5.0 + + eslint-flat-config-utils@2.1.4: + resolution: {integrity: sha512-bEnmU5gqzS+4O+id9vrbP43vByjF+8KOs+QuuV4OlqAuXmnRW2zfI/Rza1fQvdihQ5h4DUo0NqFAiViD4mSrzQ==} + + eslint-formatting-reporter@0.0.0: + resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' + eslint: '>=8.40.0' + + eslint-json-compat-utils@0.2.1: + resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 peerDependenciesMeta: - '@types/eslint': + '@eslint/json': optional: true - eslint-config-prettier: + + eslint-merge-processors@2.0.0: + resolution: {integrity: sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==} + peerDependencies: + eslint: '*' + + eslint-parser-plain@0.1.1: + resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==} + + eslint-plugin-antfu@3.1.1: + resolution: {integrity: sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg==} + peerDependencies: + eslint: '*' + + eslint-plugin-command@3.3.1: + resolution: {integrity: sha512-fBVTXQ2y48TVLT0+4A6PFINp7GcdIailHAXbvPBixE7x+YpYnNQhFZxTdvnb+aWk+COgNebQKen/7m4dmgyWAw==} + peerDependencies: + eslint: '*' + + eslint-plugin-es-x@7.8.0: + resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + + eslint-plugin-format@1.0.2: + resolution: {integrity: sha512-ySrDaLQZbreNAr/Betq6ocd5Hxy3+LBIfWNV2621EQQ6yGf/ZSLtN2MiM62WO2YQTX+nSFhv332Tpp51q+AkZQ==} + peerDependencies: + eslint: ^8.40.0 || ^9.0.0 + + eslint-plugin-import-lite@0.3.0: + resolution: {integrity: sha512-dkNBAL6jcoCsXZsQ/Tt2yXmMDoNt5NaBh/U7yvccjiK8cai6Ay+MK77bMykmqQA2bTF6lngaLCDij6MTO3KkvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=9.0.0' + typescript: '>=4.5' + peerDependenciesMeta: + typescript: optional: true - eslint-plugin-simple-import-sort@12.1.1: - resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==} + eslint-plugin-jsdoc@59.1.0: + resolution: {integrity: sha512-sg9mzjjzfnMynyY4W8FDiQv3i8eFcKVEHDt4Xh7MLskP3QkMt2z6p7FuzSw7jJSKFues6RaK2GWvmkB1FLPxXg==} + engines: {node: '>=20.11.0'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-jsonc@2.21.0: + resolution: {integrity: sha512-HttlxdNG5ly3YjP1cFMP62R4qKLxJURfBZo2gnMY+yQojZxkLyOpY1H1KRTKBmvQeSG9pIpSGEhDjE17vvYosg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-n@17.23.1: + resolution: {integrity: sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: '>=8.23.0' + + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} + engines: {node: '>=5.0.0'} + + eslint-plugin-perfectionist@4.15.1: + resolution: {integrity: sha512-MHF0cBoOG0XyBf7G0EAFCuJJu4I18wy0zAoT1OHfx2o6EOx1EFTIzr2HGeuZa1kDcusoX0xJ9V7oZmaeFd773Q==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + eslint: '>=8.45.0' + + eslint-plugin-pnpm@1.3.0: + resolution: {integrity: sha512-Lkdnj3afoeUIkDUu8X74z60nrzjQ2U55EbOeI+qz7H1He4IO4gmUKT2KQIl0It52iMHJeuyLDWWNgjr6UIK8nw==} + peerDependencies: + eslint: ^9.0.0 + + eslint-plugin-regexp@2.10.0: + resolution: {integrity: sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng==} + engines: {node: ^18 || >=20} + peerDependencies: + eslint: '>=8.44.0' + + eslint-plugin-toml@0.12.0: + resolution: {integrity: sha512-+/wVObA9DVhwZB1nG83D2OAQRrcQZXy+drqUnFJKymqnmbnbfg/UPmEMCKrJNcEboUGxUjYrJlgy+/Y930mURQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-plugin-unicorn@61.0.2: + resolution: {integrity: sha512-zLihukvneYT7f74GNbVJXfWIiNQmkc/a9vYBTE4qPkQZswolWNdu+Wsp9sIXno1JOzdn6OUwLPd19ekXVkahRA==} + engines: {node: ^20.10.0 || >=21.0.0} peerDependencies: - eslint: '>=5.0.0' + eslint: '>=9.29.0' + + eslint-plugin-unused-imports@4.3.0: + resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true eslint-plugin-vue@10.5.0: resolution: {integrity: sha512-7BZHsG3kC2vei8F2W8hnfDi9RK+cv5eKPMvzBdrl8Vuc0hR5odGQRli8VVzUkrmUHkxFEm4Iio1r5HOKslO0Aw==} @@ -2276,6 +2585,18 @@ packages: '@typescript-eslint/parser': optional: true + eslint-plugin-yml@1.19.0: + resolution: {integrity: sha512-S+4GbcCWksFKAvFJtf0vpdiCkZZvDJCV4Zsi9ahmYkYOYcf+LRqqzvzkb/ST7vTYV6sFwXOvawzYyL/jFT2nQA==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + + eslint-processor-vue-blocks@2.0.0: + resolution: {integrity: sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: '>=9.0.0' + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2305,6 +2626,10 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -2346,6 +2671,9 @@ packages: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2368,6 +2696,9 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -2376,6 +2707,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fetch-cookie@3.0.1: resolution: {integrity: sha512-ZGXe8Y5Z/1FWqQ9q/CrJhkUD73DyBU9VF0hBQmEO/wPHe4A9PKTjplFDLeFX8aOsYypZUcX5Ji/eByn3VCVO3Q==} @@ -2390,6 +2730,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -2422,6 +2766,10 @@ packages: resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} engines: {node: '>= 6'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + formidable@3.5.4: resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} engines: {node: '>=14.0.0'} @@ -2453,6 +2801,9 @@ packages: get-tsconfig@4.10.0: resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2465,6 +2816,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + globals@16.4.0: resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} engines: {node: '>=18'} @@ -2473,6 +2828,9 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -2556,6 +2914,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2563,6 +2925,10 @@ packages: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} + is-builtin-module@5.0.0: + resolution: {integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==} + engines: {node: '>=18.20'} + is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -2655,10 +3021,32 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} + + jsdoc-type-pratt-parser@4.8.0: + resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} + engines: {node: '>=12.0.0'} + + jsdoc-type-pratt-parser@5.4.0: + resolution: {integrity: sha512-F9GQ+F1ZU6qvSrZV8fNFpjDNf614YzR2eF6S0+XbDjAcUI28FSoXnYZFjQmb1kFx3rrJb5PnxUH3/Yti6fcM+g==} + engines: {node: '>=12.0.0'} + jsep@1.4.0: resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} engines: {node: '>= 10.16.0'} + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2683,6 +3071,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonc-eslint-parser@2.4.1: + resolution: {integrity: sha512-uuPNLJkKN8NXAlZlQ6kmUF9qO+T6Kyd7oV4+/7yy8Jz6+MZNyhPq8EdLpdfnPVzUC8qSf1b4j1azKaGnFsjmsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -2791,6 +3183,10 @@ packages: resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} engines: {node: '>= 12.0.0'} + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} @@ -2831,6 +3227,9 @@ packages: long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -2860,6 +3259,9 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2867,6 +3269,42 @@ packages: md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-frontmatter@2.0.1: + resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + memoirist@0.3.0: resolution: {integrity: sha512-wR+4chMgVPq+T6OOsk40u9Wlpw1Pjx66NMNiYxCQQ4EUJ7jDs3D9kTCeKdBOkvAiqXlHLVJlvYL01PvIJ1MPNg==} @@ -2877,46 +3315,136 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - miller-rabin@4.0.1: - resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} - hasBin: true + micromark-extension-frontmatter@2.0.0: + resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + miller-rabin@4.0.1: + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} + hasBin: true + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2937,6 +3465,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} + engines: {node: '>=18'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2946,6 +3478,9 @@ packages: encoding: optional: true + node-releases@2.0.26: + resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} + node-stdlib-browser@1.3.1: resolution: {integrity: sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==} engines: {node: '>=10'} @@ -2957,6 +3492,9 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-deep-merge@1.0.5: + resolution: {integrity: sha512-3DioFgOzetbxbeUq8pB2NunXo8V0n4EvqsWM/cJoI6IA9zghd7cl/2pBOuWRf4dlvA+fcg5ugFMZaN2/RuoaGg==} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -2997,6 +3535,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-manager-detector@1.5.0: + resolution: {integrity: sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -3011,6 +3552,16 @@ packages: resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==} engines: {node: '>= 0.10'} + parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + + parse-imports-exports@0.2.4: + resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} + + parse-statements@1.0.11: + resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -3114,6 +3665,19 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + pnpm-workspace-yaml@1.3.0: + resolution: {integrity: sha512-Krb5q8Totd5mVuLx7we+EFHq/AfxA75nbfTm25Q1pIf606+RlaKUG+PXH8SDihfe5b5k4H09gE+sL47L1t5lbw==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3201,6 +3765,9 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + query-string@6.13.5: resolution: {integrity: sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q==} engines: {node: '>=6'} @@ -3246,6 +3813,22 @@ packages: redeyed@2.1.1: resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regjsparser@0.12.0: + resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} + hasBin: true + reka-ui@2.5.1: resolution: {integrity: sha512-QJGB3q21wQ1Kw28HhhNDpjfFe8qpePX1gK4FTBRd68XTh9aEnhR5bTJnlV0jxi8FBPh0xivZBeNFUc3jiGx7mQ==} peerDependencies: @@ -3315,6 +3898,10 @@ packages: scheduler@0.19.1: resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==} + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -3367,6 +3954,9 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3378,6 +3968,15 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.22: + resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + speakingurl@14.0.1: resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} engines: {node: '>=0.10.0'} @@ -3426,6 +4025,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -3460,6 +4063,10 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + synckit@0.9.3: + resolution: {integrity: sha512-JJoOEKTfL1urb1mDoEblhD9NhEbWmq9jHEMEnxoC4ujUaZ4itA8vKgwkFAyNClgxplLi9tsUKX+EduK0p/l7sg==} + engines: {node: ^14.18.0 || >=16.0.0} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -3488,10 +4095,17 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3512,6 +4126,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toml-eslint-parser@0.10.0: + resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -3525,6 +4143,11 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-declaration-location@1.0.7: + resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} + peerDependencies: + typescript: '>=4.0.0' + ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} @@ -3549,6 +4172,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.2.0: + resolution: {integrity: sha512-5zknd7Dss75pMSED270A1RQS3KloqRJA9XbXLe0eCxyw7xXFb3rd+9B0UQ/0E+LQT6lnrLviEolYORlRWamn4w==} + engines: {node: '>=16'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -3559,13 +4186,6 @@ packages: typeforce@1.18.0: resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==} - typescript-eslint@8.46.1: - resolution: {integrity: sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - typescript@5.7.3: resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} @@ -3576,9 +4196,24 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} @@ -3587,6 +4222,12 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3751,8 +4392,8 @@ packages: '@vue/composition-api': optional: true - vue-eslint-parser@10.1.3: - resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==} + vue-eslint-parser@10.2.0: + resolution: {integrity: sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3891,6 +4532,15 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-eslint-parser@1.3.0: + resolution: {integrity: sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA==} + engines: {node: ^14.17.0 || >=16.0.0} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3906,6 +4556,9 @@ packages: zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@ampproject/remapping@2.3.0': @@ -3913,6 +4566,59 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 + '@antfu/eslint-config@6.0.0(@vue/compiler-sfc@3.5.22)(eslint-plugin-format@1.0.2(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@clack/prompts': 0.11.0 + '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.37.0(jiti@2.6.1)) + '@eslint/markdown': 7.5.0 + '@stylistic/eslint-plugin': 5.5.0(eslint@9.37.0(jiti@2.6.1)) + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@vitest/eslint-plugin': 1.3.23(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) + ansis: 4.2.0 + cac: 6.7.14 + eslint: 9.37.0(jiti@2.6.1) + eslint-config-flat-gitignore: 2.1.0(eslint@9.37.0(jiti@2.6.1)) + eslint-flat-config-utils: 2.1.4 + eslint-merge-processors: 2.0.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-antfu: 3.1.1(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-command: 3.3.1(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-import-lite: 0.3.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-jsdoc: 59.1.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-jsonc: 2.21.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-n: 17.23.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-perfectionist: 4.15.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-pnpm: 1.3.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-regexp: 2.10.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-toml: 0.12.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-unicorn: 61.0.2(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-unused-imports: 4.3.0(@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-vue: 10.5.0(@stylistic/eslint-plugin@5.5.0(eslint@9.37.0(jiti@2.6.1)))(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.37.0(jiti@2.6.1))) + eslint-plugin-yml: 1.19.0(eslint@9.37.0(jiti@2.6.1)) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.22)(eslint@9.37.0(jiti@2.6.1)) + globals: 16.4.0 + jsonc-eslint-parser: 2.4.1 + local-pkg: 1.1.2 + parse-gitignore: 2.0.0 + toml-eslint-parser: 0.10.0 + vue-eslint-parser: 10.2.0(eslint@9.37.0(jiti@2.6.1)) + yaml-eslint-parser: 1.3.0 + optionalDependencies: + eslint-plugin-format: 1.0.2(eslint@9.37.0(jiti@2.6.1)) + transitivePeerDependencies: + - '@eslint/json' + - '@vue/compiler-sfc' + - supports-color + - typescript + - vitest + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.5.0 + tinyexec: 1.0.1 + '@atomone/chronostate@2.3.0': {} '@atomone/dither-api-types@1.8.0(@sinclair/typebox@0.34.41)(openapi-types@12.1.3)(typescript@5.9.3)': @@ -3945,6 +4651,17 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@clack/core@0.5.0': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.11.0': + dependencies: + '@clack/core': 0.5.0 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@codemirror/autocomplete@6.18.6': dependencies: '@codemirror/language': 6.11.2 @@ -4332,6 +5049,12 @@ snapshots: react: 16.14.0 react-dom: 16.14.0(react@16.14.0) + '@dprint/formatter@0.3.0': {} + + '@dprint/markdown@0.17.8': {} + + '@dprint/toml@0.6.4': {} + '@drizzle-team/brocli@0.10.2': {} '@elysiajs/cors@1.2.0(elysia@1.2.25(@sinclair/typebox@0.34.41)(openapi-types@12.1.3)(typescript@5.9.3))': @@ -4344,6 +5067,22 @@ snapshots: formidable: 3.5.4 ws: 8.18.3 + '@es-joy/jsdoccomment@0.50.2': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.46.1 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + '@es-joy/jsdoccomment@0.58.0': + dependencies: + '@types/estree': 1.0.8 + '@typescript-eslint/types': 8.46.1 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 5.4.0 + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -4498,6 +5237,12 @@ snapshots: '@esbuild/win32-x64@0.25.6': optional: true + '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.37.0(jiti@2.6.1))': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.37.0(jiti@2.6.1) + ignore: 5.3.2 + '@eslint-community/eslint-utils@4.7.0(eslint@9.37.0(jiti@2.6.1))': dependencies: eslint: 9.37.0(jiti@2.6.1) @@ -4510,6 +5255,12 @@ snapshots: '@eslint-community/regexpp@4.12.1': {} + '@eslint/compat@1.4.0(eslint@9.37.0(jiti@2.6.1))': + dependencies: + '@eslint/core': 0.16.0 + optionalDependencies: + eslint: 9.37.0(jiti@2.6.1) + '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 @@ -4522,6 +5273,10 @@ snapshots: dependencies: '@eslint/core': 0.16.0 + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/core@0.16.0': dependencies: '@types/json-schema': 7.0.15 @@ -4542,8 +5297,27 @@ snapshots: '@eslint/js@9.37.0': {} + '@eslint/markdown@7.5.0': + dependencies: + '@eslint/core': 0.16.0 + '@eslint/plugin-kit': 0.4.0 + github-slugger: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-frontmatter: 2.0.1 + mdast-util-gfm: 3.1.0 + micromark-extension-frontmatter: 2.0.0 + micromark-extension-gfm: 3.0.0 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + '@eslint/object-schema@2.1.6': {} + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + '@eslint/plugin-kit@0.4.0': dependencies: '@eslint/core': 0.16.0 @@ -4792,6 +5566,8 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 + '@pkgr/core@0.1.2': {} + '@pkgr/core@0.2.9': {} '@protobufjs/aspromise@1.1.2': {} @@ -4912,17 +5688,15 @@ snapshots: '@starknet-io/types-js@0.7.10': {} - '@stylistic/eslint-plugin@4.4.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + '@stylistic/eslint-plugin@5.5.0(eslint@9.37.0(jiti@2.6.1))': dependencies: - '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@typescript-eslint/types': 8.46.1 eslint: 9.37.0(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.3 - transitivePeerDependencies: - - supports-color - - typescript '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': dependencies: @@ -4996,12 +5770,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 - '@tailwindcss/vite@4.1.14(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6))': + '@tailwindcss/vite@4.1.14(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.1.14 '@tailwindcss/oxide': 4.1.14 tailwindcss: 4.1.14 - vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) '@tanstack/match-sorter-utils@8.19.4': dependencies: @@ -5028,6 +5802,10 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.8': {} @@ -5041,6 +5819,10 @@ snapshots: '@types/long@4.0.2': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/ms@2.1.0': {} '@types/node@10.12.18': {} @@ -5055,6 +5837,8 @@ snapshots: pg-protocol: 1.9.5 pg-types: 2.2.0 + '@types/unist@3.0.3': {} + '@types/web-bluetooth@0.0.21': {} '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': @@ -5150,11 +5934,22 @@ snapshots: '@typescript-eslint/types': 8.46.1 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-vue@5.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6))(vue@3.5.22(typescript@5.7.3))': + '@vitejs/plugin-vue@5.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.7.3))': dependencies: - vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) vue: 3.5.22(typescript@5.7.3) + '@vitest/eslint-plugin@1.3.23(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': + dependencies: + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) + optionalDependencies: + typescript: 5.9.3 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 @@ -5163,13 +5958,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6))': + '@vitest/mocker@3.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -5487,6 +6282,10 @@ snapshots: ansicolors@0.3.2: {} + ansis@4.2.0: {} + + are-docs-informative@0.0.2: {} + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -5547,6 +6346,8 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.8.19: {} + bech32@1.1.4: {} bech32@2.0.0: {} @@ -5658,6 +6459,14 @@ snapshots: dependencies: pako: 1.0.11 + browserslist@4.27.0: + dependencies: + baseline-browser-mapping: 2.8.19 + caniuse-lite: 1.0.30001751 + electron-to-chromium: 1.5.238 + node-releases: 2.0.26 + update-browserslist-db: 1.1.4(browserslist@4.27.0) + bs58@4.0.1: dependencies: base-x: 3.0.11 @@ -5693,6 +6502,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + builtin-modules@5.0.0: {} + builtin-status-codes@3.0.0: {} cac@6.7.14: {} @@ -5716,11 +6527,15 @@ snapshots: callsites@3.1.0: {} + caniuse-lite@1.0.30001751: {} + cardinal@2.1.1: dependencies: ansicolors: 0.3.2 redeyed: 2.1.1 + ccount@2.0.1: {} + chai@5.2.0: dependencies: assertion-error: 2.0.1 @@ -5734,10 +6549,16 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + + character-entities@2.0.2: {} + check-error@2.1.1: {} chownr@3.0.0: {} + ci-info@4.3.1: {} + cipher-base@1.0.6: dependencies: inherits: 2.0.4 @@ -5747,6 +6568,10 @@ snapshots: dependencies: clsx: 2.1.1 + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -5773,8 +6598,14 @@ snapshots: commander@9.5.0: {} + comment-parser@1.4.1: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} + + confbox@0.2.2: {} + console-browserify@1.2.0: {} constants-browserify@1.0.0: {} @@ -5785,6 +6616,10 @@ snapshots: dependencies: is-what: 4.1.16 + core-js-compat@3.46.0: + dependencies: + browserslist: 4.27.0 + core-util-is@1.0.3: {} cosmjs-types@0.4.1: @@ -5870,6 +6705,14 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + decode-uri-component@0.2.2: {} deep-eql@5.0.2: {} @@ -5896,6 +6739,8 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + des.js@1.1.0: dependencies: inherits: 2.0.4 @@ -5907,11 +6752,17 @@ snapshots: detect-libc@2.0.4: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dezalgo@1.0.4: dependencies: asap: 2.0.6 wrappy: 1.0.2 + diff-sequences@27.5.1: {} + diff-sequences@29.6.3: {} diffie-hellman@5.0.3: @@ -5960,6 +6811,8 @@ snapshots: typeforce: 1.18.0 wif: 2.0.6 + electron-to-chromium@1.5.238: {} + elliptic@6.6.1: dependencies: bn.js: 4.12.2 @@ -5981,6 +6834,8 @@ snapshots: emoji-regex@8.0.0: {} + empathic@2.0.0: {} + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -6068,54 +6923,247 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)): + escape-string-regexp@5.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.37.0(jiti@2.6.1)): dependencies: eslint: 9.37.0(jiti@2.6.1) + semver: 7.7.2 - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(prettier@3.6.2): + eslint-compat-utils@0.6.5(eslint@9.37.0(jiti@2.6.1)): dependencies: eslint: 9.37.0(jiti@2.6.1) - prettier: 3.6.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 - optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.37.0(jiti@2.6.1)) + semver: 7.7.2 - eslint-plugin-simple-import-sort@12.1.1(eslint@9.37.0(jiti@2.6.1)): + eslint-config-flat-gitignore@2.1.0(eslint@9.37.0(jiti@2.6.1)): dependencies: + '@eslint/compat': 1.4.0(eslint@9.37.0(jiti@2.6.1)) eslint: 9.37.0(jiti@2.6.1) - eslint-plugin-vue@10.5.0(@stylistic/eslint-plugin@4.4.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(vue-eslint-parser@10.1.3(eslint@9.37.0(jiti@2.6.1))): + eslint-flat-config-utils@2.1.4: + dependencies: + pathe: 2.0.3 + + eslint-formatting-reporter@0.0.0(eslint@9.37.0(jiti@2.6.1)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.37.0(jiti@2.6.1)) eslint: 9.37.0(jiti@2.6.1) - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 - semver: 7.7.2 - vue-eslint-parser: 10.1.3(eslint@9.37.0(jiti@2.6.1)) - xml-name-validator: 4.0.0 - optionalDependencies: - '@stylistic/eslint-plugin': 4.4.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + prettier-linter-helpers: 1.0.0 - eslint-scope@8.4.0: + eslint-json-compat-utils@0.2.1(eslint@9.37.0(jiti@2.6.1))(jsonc-eslint-parser@2.4.1): dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 + eslint: 9.37.0(jiti@2.6.1) + esquery: 1.6.0 + jsonc-eslint-parser: 2.4.1 - eslint-visitor-keys@3.4.3: {} + eslint-merge-processors@2.0.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + eslint: 9.37.0(jiti@2.6.1) - eslint-visitor-keys@4.2.1: {} + eslint-parser-plain@0.1.1: {} - eslint@9.37.0(jiti@2.6.1): + eslint-plugin-antfu@3.1.1(eslint@9.37.0(jiti@2.6.1)): + dependencies: + eslint: 9.37.0(jiti@2.6.1) + + eslint-plugin-command@3.3.1(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@es-joy/jsdoccomment': 0.50.2 + eslint: 9.37.0(jiti@2.6.1) + + eslint-plugin-es-x@7.8.0(eslint@9.37.0(jiti@2.6.1)): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.4.0 + eslint: 9.37.0(jiti@2.6.1) + eslint-compat-utils: 0.5.1(eslint@9.37.0(jiti@2.6.1)) + + eslint-plugin-format@1.0.2(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@dprint/formatter': 0.3.0 + '@dprint/markdown': 0.17.8 + '@dprint/toml': 0.6.4 + eslint: 9.37.0(jiti@2.6.1) + eslint-formatting-reporter: 0.0.0(eslint@9.37.0(jiti@2.6.1)) + eslint-parser-plain: 0.1.1 + prettier: 3.6.2 + synckit: 0.9.3 + + eslint-plugin-import-lite@0.3.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@typescript-eslint/types': 8.46.1 + eslint: 9.37.0(jiti@2.6.1) + optionalDependencies: + typescript: 5.9.3 + + eslint-plugin-jsdoc@59.1.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@es-joy/jsdoccomment': 0.58.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint: 9.37.0(jiti@2.6.1) + espree: 10.4.0 + esquery: 1.6.0 + object-deep-merge: 1.0.5 + parse-imports-exports: 0.2.4 + semver: 7.7.2 + spdx-expression-parse: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsonc@2.21.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + diff-sequences: 27.5.1 + eslint: 9.37.0(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.37.0(jiti@2.6.1)) + eslint-json-compat-utils: 0.2.1(eslint@9.37.0(jiti@2.6.1))(jsonc-eslint-parser@2.4.1) + espree: 10.4.0 + graphemer: 1.4.0 + jsonc-eslint-parser: 2.4.1 + natural-compare: 1.4.0 + synckit: 0.11.11 + transitivePeerDependencies: + - '@eslint/json' + + eslint-plugin-n@17.23.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + enhanced-resolve: 5.18.3 + eslint: 9.37.0(jiti@2.6.1) + eslint-plugin-es-x: 7.8.0(eslint@9.37.0(jiti@2.6.1)) + get-tsconfig: 4.10.0 + globals: 15.15.0 + globrex: 0.1.2 + ignore: 5.3.2 + semver: 7.7.2 + ts-declaration-location: 1.0.7(typescript@5.9.3) + transitivePeerDependencies: + - typescript + + eslint-plugin-no-only-tests@3.3.0: {} + + eslint-plugin-perfectionist@4.15.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) + natural-orderby: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-pnpm@1.3.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + empathic: 2.0.0 + eslint: 9.37.0(jiti@2.6.1) + jsonc-eslint-parser: 2.4.1 + pathe: 2.0.3 + pnpm-workspace-yaml: 1.3.0 + tinyglobby: 0.2.15 + yaml-eslint-parser: 1.3.0 + + eslint-plugin-regexp@2.10.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.1 + comment-parser: 1.4.1 + eslint: 9.37.0(jiti@2.6.1) + jsdoc-type-pratt-parser: 4.8.0 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-toml@0.12.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + debug: 4.4.1 + eslint: 9.37.0(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.37.0(jiti@2.6.1)) + lodash: 4.17.21 + toml-eslint-parser: 0.10.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-unicorn@61.0.2(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@eslint/plugin-kit': 0.3.5 + change-case: 5.4.4 + ci-info: 4.3.1 + clean-regexp: 1.0.0 + core-js-compat: 3.46.0 + eslint: 9.37.0(jiti@2.6.1) + esquery: 1.6.0 + find-up-simple: 1.0.1 + globals: 16.4.0 + indent-string: 5.0.0 + is-builtin-module: 5.0.0 + jsesc: 3.1.0 + pluralize: 8.0.0 + regexp-tree: 0.1.27 + regjsparser: 0.12.0 + semver: 7.7.2 + strip-indent: 4.1.1 + + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1)): + dependencies: + eslint: 9.37.0(jiti@2.6.1) + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + + eslint-plugin-vue@10.5.0(@stylistic/eslint-plugin@5.5.0(eslint@9.37.0(jiti@2.6.1)))(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(vue-eslint-parser@10.2.0(eslint@9.37.0(jiti@2.6.1))): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.37.0(jiti@2.6.1)) + eslint: 9.37.0(jiti@2.6.1) + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.2 + vue-eslint-parser: 10.2.0(eslint@9.37.0(jiti@2.6.1)) + xml-name-validator: 4.0.0 + optionalDependencies: + '@stylistic/eslint-plugin': 5.5.0(eslint@9.37.0(jiti@2.6.1)) + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + + eslint-plugin-yml@1.19.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + debug: 4.4.1 + diff-sequences: 27.5.1 + escape-string-regexp: 4.0.0 + eslint: 9.37.0(jiti@2.6.1) + eslint-compat-utils: 0.6.5(eslint@9.37.0(jiti@2.6.1)) + natural-compare: 1.4.0 + yaml-eslint-parser: 1.3.0 + transitivePeerDependencies: + - supports-color + + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.22)(eslint@9.37.0(jiti@2.6.1)): + dependencies: + '@vue/compiler-sfc': 3.5.22 + eslint: 9.37.0(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.37.0(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.4.0 '@eslint/core': 0.16.0 '@eslint/eslintrc': 3.3.1 '@eslint/js': 9.37.0 @@ -6160,6 +7208,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} esquery@1.6.0: @@ -6193,6 +7247,8 @@ snapshots: expect-type@1.2.1: {} + exsolve@1.0.7: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -6215,10 +7271,18 @@ snapshots: dependencies: reusify: 1.1.0 + fault@2.0.1: + dependencies: + format: 0.2.2 + fdir@6.4.6(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fetch-cookie@3.0.1: dependencies: set-cookie-parser: 2.7.1 @@ -6234,6 +7298,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up-simple@1.0.1: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -6267,6 +7333,8 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + format@0.2.2: {} + formidable@3.5.4: dependencies: '@paralleldrive/cuid2': 2.2.2 @@ -6308,6 +7376,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -6318,6 +7388,8 @@ snapshots: globals@14.0.0: {} + globals@15.15.0: {} + globals@16.4.0: {} globalthis@1.0.4: @@ -6325,6 +7397,8 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + globrex@0.1.2: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -6398,6 +7472,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@5.0.0: {} + inherits@2.0.4: {} is-arguments@1.2.0: @@ -6405,6 +7481,10 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-builtin-module@5.0.0: + dependencies: + builtin-modules: 5.0.0 + is-callable@1.2.7: {} is-core-module@2.16.1: @@ -6485,8 +7565,18 @@ snapshots: dependencies: argparse: 2.0.1 + jsdoc-type-pratt-parser@4.1.0: {} + + jsdoc-type-pratt-parser@4.8.0: {} + + jsdoc-type-pratt-parser@5.4.0: {} + jsep@1.4.0: {} + jsesc@3.0.2: {} + + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-editor-vue@0.18.1(vue@3.5.22(typescript@5.7.3)): @@ -6503,6 +7593,13 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonc-eslint-parser@2.4.1: + dependencies: + acorn: 8.15.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.7.2 + jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -6609,6 +7706,12 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + locate-character@3.0.0: {} locate-path@6.0.0: @@ -6637,6 +7740,8 @@ snapshots: long@4.0.0: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -6663,6 +7768,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-table@3.0.4: {} + math-intrinsics@1.1.0: {} md5.js@1.3.5: @@ -6671,12 +7778,323 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + memoirist@0.3.0: {} memoize-one@6.0.0: {} merge2@1.4.1: {} + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -6713,6 +8131,13 @@ snapshots: mitt@3.0.1: {} + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -6725,10 +8150,14 @@ snapshots: natural-compare@1.4.0: {} + natural-orderby@5.0.0: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-releases@2.0.26: {} + node-stdlib-browser@1.3.1: dependencies: assert: 2.1.0 @@ -6765,6 +8194,10 @@ snapshots: object-assign@4.1.1: {} + object-deep-merge@1.0.5: + dependencies: + type-fest: 4.2.0 + object-inspect@1.13.4: {} object-is@1.1.6: @@ -6811,6 +8244,8 @@ snapshots: dependencies: p-limit: 3.1.0 + package-manager-detector@1.5.0: {} + pako@1.0.11: {} pako@2.1.0: {} @@ -6828,6 +8263,14 @@ snapshots: pbkdf2: 3.1.3 safe-buffer: 5.2.1 + parse-gitignore@2.0.0: {} + + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 + + parse-statements@1.0.11: {} + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -6913,6 +8356,24 @@ snapshots: dependencies: find-up: 5.0.0 + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + pluralize@8.0.0: {} + + pnpm-workspace-yaml@1.3.0: + dependencies: + yaml: 2.8.1 + possible-typed-array-names@1.1.0: {} postcss-selector-parser@6.1.2: @@ -7004,6 +8465,8 @@ snapshots: dependencies: side-channel: 1.1.0 + quansync@0.2.11: {} + query-string@6.13.5: dependencies: decode-uri-component: 0.2.2 @@ -7063,6 +8526,21 @@ snapshots: dependencies: esprima: 4.0.1 + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regjsparser@0.12.0: + dependencies: + jsesc: 3.0.2 + reka-ui@2.5.1(typescript@5.7.3)(vue@3.5.22(typescript@5.7.3)): dependencies: '@floating-ui/dom': 1.7.2 @@ -7159,6 +8637,12 @@ snapshots: loose-envify: 1.4.0 object-assign: 4.1.1 + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + semver@7.7.1: {} semver@7.7.2: {} @@ -7218,6 +8702,8 @@ snapshots: siginfo@2.0.0: {} + sisteransi@1.0.5: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -7227,6 +8713,15 @@ snapshots: source-map@0.6.1: {} + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.22 + + spdx-license-ids@3.0.22: {} + speakingurl@14.0.1: {} split-on-first@1.1.0: {} @@ -7297,6 +8792,8 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-indent@4.1.1: {} + strip-json-comments@3.1.1: {} strip-literal@3.0.0: @@ -7338,6 +8835,11 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + synckit@0.9.3: + dependencies: + '@pkgr/core': 0.1.2 + tslib: 2.8.1 + tailwind-merge@3.3.1: {} tailwindcss@4.1.14: {} @@ -7368,11 +8870,18 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.1: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} @@ -7389,6 +8898,10 @@ snapshots: dependencies: is-number: 7.0.0 + toml-eslint-parser@0.10.0: + dependencies: + eslint-visitor-keys: 3.4.3 + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -7402,6 +8915,11 @@ snapshots: dependencies: typescript: 5.9.3 + ts-declaration-location@1.0.7(typescript@5.9.3): + dependencies: + picomatch: 4.0.3 + typescript: 5.9.3 + ts-mixer@6.0.4: {} tslib@1.14.1: {} @@ -7423,6 +8941,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@4.2.0: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -7435,27 +8955,43 @@ snapshots: typeforce@1.18.0: {} - typescript-eslint@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.37.0(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - typescript@5.7.3: {} typescript@5.9.3: {} + ufo@1.6.1: {} + undici-types@6.21.0: {} + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@0.2.0: {} universalify@2.0.1: {} + update-browserslist-db@1.1.4(browserslist@4.27.0): + dependencies: + browserslist: 4.27.0 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -7519,13 +9055,13 @@ snapshots: dependencies: safe-buffer: 5.2.1 - vite-node@3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6): + vite-node@3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + vite: 6.3.5(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - jiti @@ -7540,15 +9076,15 @@ snapshots: - tsx - yaml - vite-plugin-node-polyfills@0.23.0(rollup@4.45.1)(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)): + vite-plugin-node-polyfills@0.23.0(rollup@4.45.1)(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.45.1) node-stdlib-browser: 1.3.1 - vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - rollup - vite@6.3.5(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6): + vite@6.3.5(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.6 fdir: 6.4.6(picomatch@4.0.3) @@ -7562,8 +9098,9 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.1 tsx: 4.20.6 + yaml: 2.8.1 - vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6): + vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.6 fdir: 6.4.6(picomatch@4.0.3) @@ -7577,12 +9114,13 @@ snapshots: jiti: 2.6.1 lightningcss: 1.30.1 tsx: 4.20.6 + yaml: 2.8.1 - vitest@3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)) + '@vitest/mocker': 3.2.4(vite@6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -7600,10 +9138,11 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) - vite-node: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6) + vite: 6.3.7(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.18.10)(jiti@2.6.1)(lightningcss@1.30.1)(tsx@4.20.6)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.12 '@types/node': 22.18.10 transitivePeerDependencies: - jiti @@ -7627,7 +9166,7 @@ snapshots: dependencies: vue: 3.5.22(typescript@5.7.3) - vue-eslint-parser@10.1.3(eslint@9.37.0(jiti@2.6.1)): + vue-eslint-parser@10.2.0(eslint@9.37.0(jiti@2.6.1)): dependencies: debug: 4.4.1 eslint: 9.37.0(jiti@2.6.1) @@ -7635,7 +9174,6 @@ snapshots: eslint-visitor-keys: 4.2.1 espree: 10.4.0 esquery: 1.6.0 - lodash: 4.17.21 semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -7733,6 +9271,13 @@ snapshots: yallist@5.0.0: {} + yaml-eslint-parser@1.3.0: + dependencies: + eslint-visitor-keys: 3.4.3 + yaml: 2.8.1 + + yaml@2.8.1: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -7748,3 +9293,5 @@ snapshots: yocto-queue@0.1.0: {} zimmerframe@1.1.2: {} + + zwitch@2.0.4: {} From 631b7f64da9dcc56b0848106ee114e508c63e02e Mon Sep 17 00:00:00 2001 From: Lucio Rubens Date: Wed, 22 Oct 2025 17:14:49 -0300 Subject: [PATCH 2/9] lint --- .github/workflows/frontend-build.yml | 4 +- .github/workflows/lint.yml | 4 +- .github/workflows/npm-publish.yml | 6 +- .github/workflows/retype-action.yml | 2 +- .github/workflows/test.yml | 6 +- .github/workflows/testnet-api-main.yml | 2 +- docker-compose.yml | 29 +- eslint.config.mjs | 41 +- netlify.toml | 3 +- package.json | 1 + packages/api-main/README.md | 8 +- packages/api-main/docker-compose-github.yml | 2 +- packages/api-main/docker-compose.yml | 62 +- packages/api-main/drizzle.config.ts | 19 +- packages/api-main/drizzle/db.ts | 21 +- packages/api-main/drizzle/schema.ts | 252 ++-- packages/api-main/fly.mainnet.toml | 2 +- packages/api-main/fly.testnet.toml | 3 +- packages/api-main/src/config.ts | 91 +- packages/api-main/src/gets/authVerify.ts | 21 +- packages/api-main/src/gets/dislikes.ts | 79 +- packages/api-main/src/gets/feed.ts | 90 +- packages/api-main/src/gets/flags.ts | 79 +- packages/api-main/src/gets/followers.ts | 53 +- packages/api-main/src/gets/following.ts | 53 +- packages/api-main/src/gets/health.ts | 2 +- packages/api-main/src/gets/index.ts | 2 +- packages/api-main/src/gets/isFollowing.ts | 43 +- packages/api-main/src/gets/lastBlock.ts | 31 +- packages/api-main/src/gets/likes.ts | 79 +- packages/api-main/src/gets/notifications.ts | 114 +- .../api-main/src/gets/notificationsCount.ts | 45 +- packages/api-main/src/gets/post.ts | 41 +- packages/api-main/src/gets/posts.ts | 136 ++- packages/api-main/src/gets/replies.ts | 132 +-- packages/api-main/src/gets/search.ts | 79 +- packages/api-main/src/index.ts | 119 +- packages/api-main/src/posts/auth.ts | 51 +- packages/api-main/src/posts/authCreate.ts | 29 +- packages/api-main/src/posts/dislike.ts | 111 +- packages/api-main/src/posts/flag.ts | 109 +- packages/api-main/src/posts/follow.ts | 123 +- packages/api-main/src/posts/like.ts | 111 +- packages/api-main/src/posts/logout.ts | 15 +- packages/api-main/src/posts/mod.ts | 400 ++++--- packages/api-main/src/posts/post.ts | 103 +- packages/api-main/src/posts/postRemove.ts | 57 +- packages/api-main/src/posts/reply.ts | 113 +- packages/api-main/src/posts/unfollow.ts | 41 +- packages/api-main/src/posts/updateState.ts | 40 +- packages/api-main/src/shared/jwt.ts | 37 +- packages/api-main/src/shared/notify.ts | 102 +- .../api-main/src/shared/useRateLimiter.ts | 187 ++- .../api-main/src/shared/useSharedQueries.ts | 25 +- packages/api-main/src/shared/useUserAuth.ts | 177 +-- packages/api-main/src/types/feed.ts | 8 +- packages/api-main/src/types/follows.ts | 7 +- packages/api-main/src/types/index.ts | 14 +- packages/api-main/src/utility/index.ts | 186 ++- packages/api-main/tests/auth.test.ts | 48 +- packages/api-main/tests/feed.test.ts | 438 +++---- packages/api-main/tests/follow.test.ts | 192 +-- packages/api-main/tests/moderator.test.ts | 604 +++++----- packages/api-main/tests/notifications.test.ts | 278 ++--- packages/api-main/tests/search.test.ts | 94 +- packages/api-main/tests/setup.ts | 27 +- packages/api-main/tests/shared.ts | 255 ++-- packages/api-main/tests/state.test.ts | 20 +- packages/api-main/tests/utility.test.ts | 42 +- packages/api-main/tests/v1.test.ts | 604 +++++----- packages/api-main/tsconfig.json | 34 +- packages/api-main/vitest.config.ts | 42 +- packages/cli/README.md | 2 +- packages/cli/config.json | 2 +- packages/frontend-main/STYLEGUIDE.md | 299 ++--- packages/frontend-main/components.json | 2 +- packages/frontend-main/index.html | 61 +- packages/frontend-main/src/App.vue | 25 +- .../src/chain-config.devnet.json | 84 +- .../src/chain-config.mainnet.json | 2 +- .../src/chain-config.testnet.json | 84 +- .../notifications/DislikeNotification.vue | 1 - .../notifications/FlagNotification.vue | 1 - .../notifications/FollowNotification.vue | 3 +- .../notifications/LikeNotification.vue | 1 - .../notifications/NotificationType.vue | 12 +- .../notifications/NotificationWrapper.vue | 55 +- .../notifications/NotificationsCount.vue | 2 +- .../notifications/ReplyNotification.vue | 1 - .../src/components/popups/ConfirmDialog.vue | 8 +- .../components/popups/DislikePostDialog.vue | 41 +- .../src/components/popups/FlagPostDialog.vue | 53 +- .../components/popups/FollowUserDialog.vue | 58 +- .../popups/InvalidDefaultAmountDialog.vue | 23 +- .../src/components/popups/LikePostDialog.vue | 45 +- .../src/components/popups/NewPostDialog.vue | 89 +- .../src/components/popups/ReplyDialog.vue | 56 +- .../src/components/popups/TipUserDialog.vue | 52 +- .../components/popups/UnfollowUserDialog.vue | 61 +- .../src/components/posts/PostActions.vue | 131 +-- .../src/components/posts/PostContent.vue | 40 +- .../src/components/posts/PostItem.vue | 17 +- .../src/components/posts/PostMessage.vue | 56 +- .../src/components/posts/PostMoreActions.vue | 32 +- .../src/components/posts/PostsList.vue | 15 +- .../src/components/posts/PrettyTimestamp.vue | 72 +- .../src/components/posts/RepliesGroupItem.vue | 6 +- .../components/posts/RepliesGroupsList.vue | 37 +- .../components/selects/NetworkSelector.vue | 17 +- .../ui/alert-dialog/AlertDialog.vue | 3 +- .../ui/alert-dialog/AlertDialogAction.vue | 5 +- .../ui/alert-dialog/AlertDialogCancel.vue | 5 +- .../ui/alert-dialog/AlertDialogContent.vue | 14 +- .../alert-dialog/AlertDialogDescription.vue | 7 +- .../ui/alert-dialog/AlertDialogFooter.vue | 2 +- .../ui/alert-dialog/AlertDialogHeader.vue | 2 +- .../ui/alert-dialog/AlertDialogTitle.vue | 5 +- .../ui/alert-dialog/AlertDialogTrigger.vue | 3 +- .../src/components/ui/avatar/Avatar.vue | 2 +- .../components/ui/avatar/AvatarFallback.vue | 10 +- .../src/components/ui/button/Button.vue | 16 +- .../src/components/ui/button/index.ts | 49 +- .../src/components/ui/dialog/Dialog.vue | 4 +- .../src/components/ui/dialog/DialogClose.vue | 3 +- .../components/ui/dialog/DialogContent.vue | 23 +- .../ui/dialog/DialogDescription.vue | 10 +- .../src/components/ui/dialog/DialogHeader.vue | 2 +- .../components/ui/dialog/DialogOverlay.vue | 10 +- .../ui/dialog/DialogScrollContent.vue | 21 +- .../src/components/ui/dialog/DialogTitle.vue | 10 +- .../components/ui/dialog/DialogTrigger.vue | 3 +- .../src/components/ui/filter/FilterPhoton.vue | 37 +- .../src/components/ui/icon/Icon.vue | 83 +- .../src/components/ui/icon/svg/Mintscan.vue | 6 +- .../src/components/ui/icon/svg/Pingpub.vue | 12 +- .../src/components/ui/input/Input.vue | 12 +- .../src/components/ui/input/InputPhoton.vue | 63 +- .../components/ui/popover/PopoverContent.vue | 29 +- .../components/ui/popover/PopoverTrigger.vue | 3 +- .../src/components/ui/search/SearchInput.vue | 45 +- .../components/ui/select/SelectContent.vue | 30 +- .../src/components/ui/select/SelectGroup.vue | 3 +- .../src/components/ui/select/SelectItem.vue | 13 +- .../components/ui/select/SelectItemText.vue | 3 +- .../src/components/ui/select/SelectLabel.vue | 5 +- .../ui/select/SelectScrollDownButton.vue | 5 +- .../ui/select/SelectScrollUpButton.vue | 5 +- .../components/ui/select/SelectSeparator.vue | 5 +- .../components/ui/select/SelectTrigger.vue | 9 +- .../src/components/ui/select/SelectValue.vue | 3 +- .../src/components/ui/sonner/Sonner.vue | 5 +- .../ui/sonner/ToastBroadcasting.vue | 10 +- .../src/components/ui/sonner/ToastError.vue | 10 +- .../src/components/ui/sonner/ToastInfo.vue | 6 +- .../src/components/ui/sonner/ToastSuccess.vue | 20 +- .../src/components/ui/sonner/ToastWrapper.vue | 2 +- .../components/ui/switch/ColorModeSwitch.vue | 9 +- .../src/components/ui/switch/Switch.vue | 2 +- .../src/components/ui/tabs/RouterLinkTab.vue | 5 +- .../src/components/ui/textarea/Textarea.vue | 12 +- .../src/components/users/UserAvatar.vue | 8 +- .../components/users/UserAvatarUsername.vue | 17 +- .../src/components/users/UserBalance.vue | 9 +- .../WalletConnectButton.vue | 20 +- .../WalletConnectButtonMobile.vue | 22 +- .../WalletConnectPopoverContent.vue | 8 +- .../WalletConnectDialog.vue | 193 +-- .../WalletExtensionButton.vue | 1 + .../src/composables/useBalanceFetcher.ts | 32 +- .../src/composables/useConfirmDialog.ts | 58 +- .../src/composables/useCreatePost.ts | 164 +-- .../src/composables/useCreateReply.ts | 235 ++-- .../src/composables/useDefaultAmount.ts | 43 +- .../src/composables/useDislikePost.ts | 125 +- .../frontend-main/src/composables/useFeed.ts | 82 +- .../src/composables/useFlagPost.ts | 131 ++- .../src/composables/useFollowUser.ts | 154 +-- .../src/composables/useFollowing.ts | 56 +- .../src/composables/useFollowingPosts.ts | 77 +- .../src/composables/useIsFollowing.ts | 58 +- .../src/composables/useLikePost.ts | 125 +- .../src/composables/useNotifications.ts | 60 +- .../src/composables/useNotificationsCount.ts | 42 +- .../src/composables/usePopups.ts | 68 +- .../frontend-main/src/composables/usePost.ts | 75 +- .../src/composables/useReadNotification.ts | 134 +-- .../src/composables/useReplies.ts | 84 +- .../src/composables/useSearchPosts.ts | 99 +- .../src/composables/useSessionWallet.ts | 223 ++-- .../frontend-main/src/composables/useTabs.ts | 26 +- .../src/composables/useTipUser.ts | 71 +- .../src/composables/useTxDialog.ts | 64 +- .../src/composables/useTxNotification.ts | 71 +- .../src/composables/useUnfollowUser.ts | 152 +-- .../src/composables/useUserPosts.ts | 83 +- .../src/composables/useUserReplies.ts | 102 +- .../src/composables/useWallet.ts | 1046 ++++++++--------- packages/frontend-main/src/env-config.ts | 36 +- .../frontend-main/src/layouts/MainLayout.vue | 12 +- .../src/layouts/MainLayoutMobile.vue | 1 - .../src/layouts/panels/BottomPanel.vue | 16 +- .../src/layouts/panels/LeftPanel.vue | 28 +- .../src/layouts/panels/LeftPanelTablet.vue | 27 +- .../src/layouts/panels/RightPanel.vue | 1 - .../src/layouts/panels/TopPanel.vue | 9 +- .../frontend-main/src/localization/index.ts | 326 ++--- packages/frontend-main/src/main.ts | 28 +- packages/frontend-main/src/router.ts | 125 +- .../src/stores/useConfigStore.ts | 46 +- .../src/stores/useFiltersStore.ts | 28 +- .../src/stores/useWalletDialogStore.ts | 22 +- .../src/stores/useWalletStateStore.ts | 36 +- packages/frontend-main/src/style.css | 4 +- packages/frontend-main/src/types/index.ts | 32 +- packages/frontend-main/src/utility/atomics.ts | 10 +- .../frontend-main/src/utility/breakpoints.ts | 10 +- .../src/utility/getChainConfigLazy.ts | 8 +- packages/frontend-main/src/utility/index.ts | 5 +- .../src/utility/optimisticBuilders.ts | 63 +- .../frontend-main/src/utility/sanitize.ts | 49 +- packages/frontend-main/src/utility/text.ts | 36 +- packages/frontend-main/src/utility/toast.ts | 54 +- .../frontend-main/src/views/AboutView.vue | 50 +- .../frontend-main/src/views/EnvConfigView.vue | 19 +- .../frontend-main/src/views/ExploreView.vue | 29 +- .../src/views/Home/HomeFeedView.vue | 6 +- .../src/views/Home/HomeFollowingView.vue | 6 +- .../src/views/Home/HomeViewWrapper.vue | 13 +- .../src/views/ManageFollowingView.vue | 59 +- .../frontend-main/src/views/NotFoundView.vue | 14 +- .../src/views/NotificationsView.vue | 30 +- packages/frontend-main/src/views/PostView.vue | 76 +- .../src/views/Profile/ProfilePostsView.vue | 9 +- .../src/views/Profile/ProfileRepliesView.vue | 7 +- .../src/views/Profile/ProfileViewWrapper.vue | 133 +-- .../src/views/SettingsDefaultAmount.vue | 30 +- .../src/views/SettingsSingleSession.vue | 16 +- .../frontend-main/src/views/SettingsView.vue | 4 +- .../src/views/UnauthorizedView.vue | 13 +- .../frontend-main/src/views/ViewHeading.vue | 6 +- packages/frontend-main/src/vite-env.d.ts | 16 +- packages/frontend-main/tailwind.config.ts | 16 +- packages/frontend-main/tsconfig.app.json | 14 +- packages/frontend-main/tsconfig.json | 16 +- packages/frontend-main/tsconfig.node.json | 40 +- packages/frontend-main/vite.config.ts | 44 +- packages/lib-api-types/package.json | 10 +- packages/lib-api-types/src/gets/index.ts | 153 +-- packages/lib-api-types/src/posts/index.ts | 176 ++- packages/lib-api-types/tsconfig.json | 34 +- packages/reader-main/README.md | 2 +- packages/reader-main/docker-compose.yml | 24 +- packages/reader-main/fly.mainnet.toml | 1 + packages/reader-main/fly.testnet.toml | 1 + packages/reader-main/src/config/index.ts | 45 +- packages/reader-main/src/eclesia/client.ts | 121 +- packages/reader-main/src/index.ts | 356 +++--- packages/reader-main/src/messages/dislike.ts | 103 +- packages/reader-main/src/messages/flag.ts | 104 +- packages/reader-main/src/messages/follow.ts | 93 +- packages/reader-main/src/messages/index.ts | 16 +- packages/reader-main/src/messages/like.ts | 103 +- packages/reader-main/src/messages/post.ts | 103 +- packages/reader-main/src/messages/remove.ts | 93 +- packages/reader-main/src/messages/reply.ts | 105 +- packages/reader-main/src/messages/unfollow.ts | 93 +- packages/reader-main/src/queue/index.ts | 108 +- packages/reader-main/src/types.ts | 22 +- packages/reader-main/tests/queue.test.ts | 66 +- packages/reader-main/tsconfig.json | 36 +- packages/tool-network-spammer/README.md | 11 +- packages/tool-network-spammer/package.json | 36 +- packages/tool-network-spammer/src/client.ts | 78 +- packages/tool-network-spammer/src/index.ts | 22 +- packages/tool-network-spammer/src/logic.ts | 132 ++- packages/tool-network-spammer/tsconfig.json | 18 +- pnpm-lock.yaml | 32 + 277 files changed, 8349 insertions(+), 8331 deletions(-) diff --git a/.github/workflows/frontend-build.yml b/.github/workflows/frontend-build.yml index fe0bc6db..746dd4bb 100644 --- a/.github/workflows/frontend-build.yml +++ b/.github/workflows/frontend-build.yml @@ -1,4 +1,4 @@ -name: "Frontend Build Check" +name: Frontend Build Check on: pull_request: @@ -37,7 +37,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: "pnpm" + cache: pnpm - name: Install dependencies with pnpm run: pnpm install diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ed42d9c2..da0f4b2c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: 'pnpm' + cache: pnpm - name: Install dependencies with pnpm run: pnpm install @@ -40,4 +40,4 @@ jobs: echo $mrdiff git diff exit 1 - fi \ No newline at end of file + fi diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index ffdde40d..b0920b58 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: package: - description: "Package name to publish" + description: Package name to publish required: true type: choice options: @@ -22,8 +22,8 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - registry-url: "https://registry.npmjs.org/" - scope: "@atomone" + registry-url: 'https://registry.npmjs.org/' + scope: '@atomone' - name: Install pnpm run: npm install -g pnpm diff --git a/.github/workflows/retype-action.yml b/.github/workflows/retype-action.yml index 88881d03..e3a420bf 100644 --- a/.github/workflows/retype-action.yml +++ b/.github/workflows/retype-action.yml @@ -21,4 +21,4 @@ jobs: - uses: retypeapp/action-github-pages@latest with: - update-branch: true \ No newline at end of file + update-branch: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15137f2a..3034f03e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: jobs: api-main-test: runs-on: ubuntu-latest - + services: postgres: image: postgres:15 @@ -38,7 +38,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: 'pnpm' + cache: pnpm - name: Install dependencies with pnpm run: pnpm install @@ -55,4 +55,4 @@ jobs: PG_URI: postgresql://default:password@localhost:5432/postgres JWT: default_jwt_secret JWT_STRICTNESS: lax - AUTH: whatever \ No newline at end of file + AUTH: whatever diff --git a/.github/workflows/testnet-api-main.yml b/.github/workflows/testnet-api-main.yml index 5877462f..733121b8 100644 --- a/.github/workflows/testnet-api-main.yml +++ b/.github/workflows/testnet-api-main.yml @@ -45,7 +45,7 @@ jobs: uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: "pnpm" + cache: pnpm - name: Install dependencies with pnpm run: pnpm install diff --git a/docker-compose.yml b/docker-compose.yml index 00e1dc76..a5e30e2f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,11 +10,11 @@ services: POSTGRES_USER: default POSTGRES_PASSWORD: password ports: - - "5432:5432" + - '5432:5432' volumes: - ./data/postgres_data:/var/lib/postgresql/18/data healthcheck: - test: ["CMD", "pg_isready", "-U", "default"] + test: [CMD, pg_isready, -U, default] interval: 10s timeout: 5s retries: 3 @@ -24,7 +24,7 @@ services: # REST Services # ================================ api-main: - container_name: "api-main" + container_name: api-main build: ./packages/api-main image: ditherchat/api-main restart: always @@ -32,12 +32,12 @@ services: postgres: condition: service_healthy environment: - PG_URI: "postgresql://default:password@postgres:5432/postgres" - AUTH: "dev" + PG_URI: 'postgresql://default:password@postgres:5432/postgres' + AUTH: dev ports: - 3000:3000 healthcheck: - test: "curl http://localhost:3000/v1/health || exit 1" + test: 'curl http://localhost:3000/v1/health || exit 1' interval: 5s timeout: 3s retries: 30 @@ -47,24 +47,24 @@ services: # ChronoSync Service # ================================ reader-main: - container_name: "reader-main" + container_name: reader-main build: ./packages/reader-main image: ditherchat/reader-main restart: always - command: ["pnpm", "start"] + command: [pnpm, start] depends_on: postgres: condition: service_healthy api-main: condition: service_healthy environment: - API_URLS: "https://atomone-testnet-1-api.allinbits.services" - START_BLOCK: "1979480" + API_URLS: 'https://atomone-testnet-1-api.allinbits.services' + START_BLOCK: '1979480' BATCH_SIZE: 500 - MEMO_PREFIX: "dither." - RECEIVER: "atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep" - API_ROOT: "http://api-main:3000/v1" - AUTH: "dev" + MEMO_PREFIX: dither. + RECEIVER: atone1uq6zjslvsa29cy6uu75y8txnl52mw06j6fzlep + API_ROOT: 'http://api-main:3000/v1' + AUTH: dev # Uncomment to enable fast sync # ECLESIA_GRAPHQL_ENDPOINT: "https://graphql-atomone-testnet-1.allinbits.services/v1/graphql" # ECLESIA_GRAPHQL_SECRET: "" @@ -84,4 +84,3 @@ services: # depends_on: # - postgres # - api-main - diff --git a/eslint.config.mjs b/eslint.config.mjs index 18716dc9..0799064a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,23 +1,26 @@ import antfu from '@antfu/eslint-config'; -export default antfu( - { - typescript: true, - vue: true, - jsonc: true, - yaml: true, - markdown: true, - formatters: true, - stylistic: { - indent: 2, - quotes: 'single', - semi: true, +export default antfu({ + typescript: true, + vue: true, + jsonc: true, + yaml: true, + markdown: true, + formatters: true, + stylistic: { + indent: 2, + quotes: 'single', + semi: true, + jsx: true, + overrides: { + 'style/brace-style': ['error', '1tbs'], }, }, - { - files: ['tsconfig.json', 'package.json'], - rules: { - 'jsonc/sort-keys': 'off', - }, - }, -); + ignores: ['docs/**'], +}, { + files: ['tsconfig.json', 'package.json'], + rules: { 'jsonc/sort-keys': 'off' }, +}).overrideRules({ + 'no-console': 'off', + 'antfu/if-newline': 'off', +}); diff --git a/netlify.toml b/netlify.toml index c06fe038..78868309 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,7 +1,8 @@ [build] publish = "./packages/frontend-main/dist" command = "pnpm -F ./packages/frontend-main build" + [[redirects]] from = "/*" to = "/index.html" -status = 200 \ No newline at end of file +status = 200 diff --git a/package.json b/package.json index 193a2653..99c0554b 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@types/node": "^22.18.10", "eslint": "^9.37.0", "eslint-plugin-format": "^1.0.2", + "tailwindcss": "^4.1.14", "typescript": "^5.9.3" }, "packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912" diff --git a/packages/api-main/README.md b/packages/api-main/README.md index f17c23a3..921f3e77 100644 --- a/packages/api-main/README.md +++ b/packages/api-main/README.md @@ -4,9 +4,9 @@ This uses elysia to serve data from the relevant database. ## Tech Stack -- [REST API - ElysiaJS](https://elysiajs.com) -- [Database - PostgreSQL](https://www.postgresql.org) -- [SQL ORM - Drizzle](https://orm.drizzle.team) +- [REST API - ElysiaJS](https://elysiajs.com) +- [Database - PostgreSQL](https://www.postgresql.org) +- [SQL ORM - Drizzle](https://orm.drizzle.team) ## Usage @@ -35,7 +35,7 @@ All data for postgres is stored in the `data` directory. ## Testing -> [!Caution] +> [!Caution] > When running tests all tables will be cleaned out for a clean test environment. You've been warned. Start the PostgreSQL Database diff --git a/packages/api-main/docker-compose-github.yml b/packages/api-main/docker-compose-github.yml index 86fbc915..dbfb64d6 100644 --- a/packages/api-main/docker-compose-github.yml +++ b/packages/api-main/docker-compose-github.yml @@ -8,6 +8,6 @@ services: POSTGRES_USER: default POSTGRES_PASSWORD: password ports: - - "5432:5432" + - '5432:5432' volumes: - ./data/postgres_data:/var/lib/postgresql/18/docker diff --git a/packages/api-main/docker-compose.yml b/packages/api-main/docker-compose.yml index 59818684..14b631ad 100644 --- a/packages/api-main/docker-compose.yml +++ b/packages/api-main/docker-compose.yml @@ -1,32 +1,32 @@ services: - postgres: - # network_mode: host - image: postgres:18.0-alpine3.22 - container_name: postgres_db - restart: always - environment: - POSTGRES_USER: default - POSTGRES_PASSWORD: password - ports: - - '5432:5432' - volumes: - - ./data/postgres_data:/var/lib/postgresql/18/docker - # command: - # [ - # '-c', - # 'shared_buffers=4GB', - # '-c', - # 'work_mem=128MB', - # '-c', - # 'maintenance_work_mem=2GB', - # '-c', - # 'effective_cache_size=12GB', - # '-c', - # 'max_connections=150', - # '-c', - # 'max_worker_processes=8', - # '-c', - # 'max_parallel_workers_per_gather=4', - # '-c', - # 'max_parallel_workers=8', - # ] \ No newline at end of file + postgres: + # network_mode: host + image: postgres:18.0-alpine3.22 + container_name: postgres_db + restart: always + environment: + POSTGRES_USER: default + POSTGRES_PASSWORD: password + ports: + - '5432:5432' + volumes: + - ./data/postgres_data:/var/lib/postgresql/18/docker + # command: + # [ + # '-c', + # 'shared_buffers=4GB', + # '-c', + # 'work_mem=128MB', + # '-c', + # 'maintenance_work_mem=2GB', + # '-c', + # 'effective_cache_size=12GB', + # '-c', + # 'max_connections=150', + # '-c', + # 'max_worker_processes=8', + # '-c', + # 'max_parallel_workers_per_gather=4', + # '-c', + # 'max_parallel_workers=8', + # ] diff --git a/packages/api-main/drizzle.config.ts b/packages/api-main/drizzle.config.ts index 04f9d965..c6260491 100644 --- a/packages/api-main/drizzle.config.ts +++ b/packages/api-main/drizzle.config.ts @@ -1,16 +1,17 @@ +import process from 'node:process'; import dotenv from 'dotenv'; import { defineConfig } from 'drizzle-kit'; dotenv.config(); export default defineConfig({ - out: './drizzle/migrations', - schema: './drizzle/schema.ts', - dialect: 'postgresql', - verbose: true, - strict: true, - dbCredentials: { - url: process.env.PG_URI!, - }, - tablesFilter: ['!pg_*', '!information_schema.*'], // Exclude system tables + out: './drizzle/migrations', + schema: './drizzle/schema.ts', + dialect: 'postgresql', + verbose: true, + strict: true, + dbCredentials: { + url: process.env.PG_URI!, + }, + tablesFilter: ['!pg_*', '!information_schema.*'], // Exclude system tables }); diff --git a/packages/api-main/drizzle/db.ts b/packages/api-main/drizzle/db.ts index aa5af1da..eb679621 100644 --- a/packages/api-main/drizzle/db.ts +++ b/packages/api-main/drizzle/db.ts @@ -1,3 +1,4 @@ +import process from 'node:process'; import dotenv from 'dotenv'; import { drizzle } from 'drizzle-orm/node-postgres'; import pg from 'pg'; @@ -9,15 +10,15 @@ const { Pool } = pg; let db: ReturnType; export function getDatabase() { - if (!db) { - const client = new Pool({ - connectionString: process.env.PG_URI!, - max: 150, - connectionTimeoutMillis: 0, - idleTimeoutMillis: 1000, - }); - db = drizzle(client); - } + if (!db) { + const client = new Pool({ + connectionString: process.env.PG_URI!, + max: 150, + connectionTimeoutMillis: 0, + idleTimeoutMillis: 1000, + }); + db = drizzle(client); + } - return db; + return db; } diff --git a/packages/api-main/drizzle/schema.ts b/packages/api-main/drizzle/schema.ts index 96355f33..80cde5ba 100644 --- a/packages/api-main/drizzle/schema.ts +++ b/packages/api-main/drizzle/schema.ts @@ -4,166 +4,166 @@ import { bigint, boolean, index, integer, pgEnum, pgTable, primaryKey, serial, t const MEMO_LENGTH = 512; export const FeedTable = pgTable( - 'feed', - { - hash: varchar({ length: 64 }).primaryKey(), // Main hash from the transaction - post_hash: varchar({ length: 64 }), // Optional, this makes a post a reply, provided through memo - author: varchar({ length: 44 }).notNull(), // Address of user, usually in the transfer message - timestamp: timestamp({ withTimezone: true }).notNull(), // Timestamp parsed with new Date() - message: varchar({ length: MEMO_LENGTH }).notNull(), // The message inside of the memo - quantity: text().default('0'), // The total amount of tokens the user spent to post this message - replies: integer().default(0), // The amount of replies this post / reply has - likes: integer().default(0), // The amount of likes this post / reply has - dislikes: integer().default(0), // The amount of dislikes this post / reply has - flags: integer().default(0), // The amount of flags this post / reply has - likes_burnt: text().default('0'), // The amount of tokens burnt from each user who liked this post / reply - dislikes_burnt: text().default('0'), // The amount of tokens burnt from each user who disliked this post / reply - flags_burnt: text().default('0'), // The amount of tokens burnt from each user who wante dto flag this post / reply - removed_hash: varchar({ length: 64 }), // The hash that corresponds with the soft delete request - removed_at: timestamp({ withTimezone: true }), // When this post was removed - removed_by: varchar({ length: 44 }), // Who removed this post - }, - t => [ - index('feed_hash_index').on(t.hash), - index('post_hash_index').on(t.post_hash), - index('message_search_index').using('gin', sql`to_tsvector('english', ${t.message})`), - ], + 'feed', + { + hash: varchar({ length: 64 }).primaryKey(), // Main hash from the transaction + post_hash: varchar({ length: 64 }), // Optional, this makes a post a reply, provided through memo + author: varchar({ length: 44 }).notNull(), // Address of user, usually in the transfer message + timestamp: timestamp({ withTimezone: true }).notNull(), // Timestamp parsed with new Date() + message: varchar({ length: MEMO_LENGTH }).notNull(), // The message inside of the memo + quantity: text().default('0'), // The total amount of tokens the user spent to post this message + replies: integer().default(0), // The amount of replies this post / reply has + likes: integer().default(0), // The amount of likes this post / reply has + dislikes: integer().default(0), // The amount of dislikes this post / reply has + flags: integer().default(0), // The amount of flags this post / reply has + likes_burnt: text().default('0'), // The amount of tokens burnt from each user who liked this post / reply + dislikes_burnt: text().default('0'), // The amount of tokens burnt from each user who disliked this post / reply + flags_burnt: text().default('0'), // The amount of tokens burnt from each user who wante dto flag this post / reply + removed_hash: varchar({ length: 64 }), // The hash that corresponds with the soft delete request + removed_at: timestamp({ withTimezone: true }), // When this post was removed + removed_by: varchar({ length: 44 }), // Who removed this post + }, + t => [ + index('feed_hash_index').on(t.hash), + index('post_hash_index').on(t.post_hash), + index('message_search_index').using('gin', sql`to_tsvector('english', ${t.message})`), + ], ); export const DislikesTable = pgTable( - 'dislikes', - { - hash: varchar({ length: 64 }).primaryKey(), - post_hash: varchar({ length: 64 }).notNull(), - author: varchar({ length: 44 }).notNull(), - quantity: text().default('0'), - timestamp: timestamp({ withTimezone: true }).notNull(), - }, - t => [ - index('dislike_post_hash_idx').on(t.post_hash), - index('dislike_author_idx').on(t.author), - ], + 'dislikes', + { + hash: varchar({ length: 64 }).primaryKey(), + post_hash: varchar({ length: 64 }).notNull(), + author: varchar({ length: 44 }).notNull(), + quantity: text().default('0'), + timestamp: timestamp({ withTimezone: true }).notNull(), + }, + t => [ + index('dislike_post_hash_idx').on(t.post_hash), + index('dislike_author_idx').on(t.author), + ], ); export const LikesTable = pgTable( - 'likes', - { - hash: varchar({ length: 64 }).primaryKey(), - post_hash: varchar({ length: 64 }).notNull(), - author: varchar({ length: 44 }).notNull(), - quantity: text().default('0'), - timestamp: timestamp({ withTimezone: true }).notNull(), - }, - t => [ - index('like_post_hash_idx').on(t.post_hash), - index('like_author_idx').on(t.author), - ], + 'likes', + { + hash: varchar({ length: 64 }).primaryKey(), + post_hash: varchar({ length: 64 }).notNull(), + author: varchar({ length: 44 }).notNull(), + quantity: text().default('0'), + timestamp: timestamp({ withTimezone: true }).notNull(), + }, + t => [ + index('like_post_hash_idx').on(t.post_hash), + index('like_author_idx').on(t.author), + ], ); export const FlagsTable = pgTable( - 'flags', - { - hash: varchar({ length: 64 }).primaryKey(), - post_hash: varchar({ length: 64 }).notNull(), - author: varchar({ length: 44 }).notNull(), - quantity: text().default('0'), - timestamp: timestamp({ withTimezone: true }).notNull(), - }, - t => [ - index('flags_post_hash_idx').on(t.post_hash), - index('flag_author_idx').on(t.author), - ], + 'flags', + { + hash: varchar({ length: 64 }).primaryKey(), + post_hash: varchar({ length: 64 }).notNull(), + author: varchar({ length: 44 }).notNull(), + quantity: text().default('0'), + timestamp: timestamp({ withTimezone: true }).notNull(), + }, + t => [ + index('flags_post_hash_idx').on(t.post_hash), + index('flag_author_idx').on(t.author), + ], ); export const FollowsTable = pgTable( - 'follows', - { - follower: varchar({ length: 44 }).notNull(), - following: varchar({ length: 44 }).notNull(), - hash: varchar({ length: 64 }).notNull(), - timestamp: timestamp({ withTimezone: true }).notNull(), - removed_at: timestamp({ withTimezone: true }), - }, - t => [ - primaryKey({ columns: [t.follower, t.following] }), - index('follower_id_idx').on(t.follower), - index('following_id_idx').on(t.following), - ], + 'follows', + { + follower: varchar({ length: 44 }).notNull(), + following: varchar({ length: 44 }).notNull(), + hash: varchar({ length: 64 }).notNull(), + timestamp: timestamp({ withTimezone: true }).notNull(), + removed_at: timestamp({ withTimezone: true }), + }, + t => [ + primaryKey({ columns: [t.follower, t.following] }), + index('follower_id_idx').on(t.follower), + index('following_id_idx').on(t.following), + ], ); export const AuthRequests = pgTable( - 'authrequests', - { - id: serial('id').primaryKey(), - msg: varchar().notNull(), - timestamp: timestamp({ withTimezone: true }).notNull(), - }, + 'authrequests', + { + id: serial('id').primaryKey(), + msg: varchar().notNull(), + timestamp: timestamp({ withTimezone: true }).notNull(), + }, ); export const rateLimits = pgTable( - 'ratelimits', - { - id: serial('id').primaryKey(), - ip: text().notNull().unique(), - requests: integer().notNull().default(0), - lastRequest: bigint({ mode: 'number' }).notNull(), - }, + 'ratelimits', + { + id: serial('id').primaryKey(), + ip: text().notNull().unique(), + requests: integer().notNull().default(0), + lastRequest: bigint({ mode: 'number' }).notNull(), + }, ); // Audits are append only export const AuditTable = pgTable('audits', { - id: serial('id').primaryKey(), - hash: varchar({ length: 64 }).notNull(), - post_hash: varchar({ length: 64 }), // This is a post removal - user_address: varchar({ length: 44 }), // This is a user removal - created_by: varchar({ length: 44 }), - created_at: timestamp({ withTimezone: true }), - reason: text(), - restored_by: varchar({ length: 44 }), // the post or user that was restored - restored_at: timestamp({ withTimezone: true }), // the time the post or user was restored + id: serial('id').primaryKey(), + hash: varchar({ length: 64 }).notNull(), + post_hash: varchar({ length: 64 }), // This is a post removal + user_address: varchar({ length: 44 }), // This is a user removal + created_by: varchar({ length: 44 }), + created_at: timestamp({ withTimezone: true }), + reason: text(), + restored_by: varchar({ length: 44 }), // the post or user that was restored + restored_at: timestamp({ withTimezone: true }), // the time the post or user was restored }); export const ModeratorTable = pgTable('moderators', { - address: varchar({ length: 44 }).primaryKey(), - alias: varchar({ length: 16 }), // Optional short name - deleted_at: timestamp({ withTimezone: true }), + address: varchar({ length: 44 }).primaryKey(), + alias: varchar({ length: 16 }), // Optional short name + deleted_at: timestamp({ withTimezone: true }), }); export const notificationTypeEnum = pgEnum('notification_type', ['like', 'dislike', 'flag', 'follow', 'reply']); export const NotificationTable = pgTable( - 'notifications', - { - hash: varchar({ length: 64 }).notNull(), - post_hash: varchar({ length: 64 }), - owner: varchar({ length: 44 }).notNull(), - actor: varchar({ length: 44 }).notNull(), - type: notificationTypeEnum().notNull(), - subcontext: varchar({ length: 64 }), - timestamp: timestamp({ withTimezone: true }), - was_read: boolean().default(false), - }, - t => [ - primaryKey({ columns: [t.hash, t.owner] }), - index('notification_owner_idx').on(t.owner), - ], + 'notifications', + { + hash: varchar({ length: 64 }).notNull(), + post_hash: varchar({ length: 64 }), + owner: varchar({ length: 44 }).notNull(), + actor: varchar({ length: 44 }).notNull(), + type: notificationTypeEnum().notNull(), + subcontext: varchar({ length: 64 }), + timestamp: timestamp({ withTimezone: true }), + was_read: boolean().default(false), + }, + t => [ + primaryKey({ columns: [t.hash, t.owner] }), + index('notification_owner_idx').on(t.owner), + ], ); export const ReaderState = pgTable('state', { - id: serial().primaryKey(), - last_block: varchar().notNull(), + id: serial().primaryKey(), + last_block: varchar().notNull(), }); export const tables = [ - 'feed', - 'likes', - 'dislikes', - 'flags', - 'follows', - 'audits', - 'moderators', - 'notifications', - 'state', - 'authrequests', - 'ratelimits', + 'feed', + 'likes', + 'dislikes', + 'flags', + 'follows', + 'audits', + 'moderators', + 'notifications', + 'state', + 'authrequests', + 'ratelimits', ]; diff --git a/packages/api-main/fly.mainnet.toml b/packages/api-main/fly.mainnet.toml index 971f2246..d47d1101 100644 --- a/packages/api-main/fly.mainnet.toml +++ b/packages/api-main/fly.mainnet.toml @@ -8,7 +8,7 @@ internal_port = 3000 force_https = true auto_start_machines = true min_machines_running = 1 -processes = ['app'] +processes = [ 'app' ] sticky_machines = true [http_service.http_options] diff --git a/packages/api-main/fly.testnet.toml b/packages/api-main/fly.testnet.toml index c74f291a..aac01937 100644 --- a/packages/api-main/fly.testnet.toml +++ b/packages/api-main/fly.testnet.toml @@ -2,12 +2,13 @@ app = 'dither-testnet-api-main' primary_region = 'iad' [build] + [http_service] internal_port = 3000 force_https = true auto_start_machines = true min_machines_running = 1 -processes = ['app'] +processes = [ 'app' ] sticky_machines = true [http_service.http_options] diff --git a/packages/api-main/src/config.ts b/packages/api-main/src/config.ts index 9f3ee247..78e881fb 100644 --- a/packages/api-main/src/config.ts +++ b/packages/api-main/src/config.ts @@ -1,59 +1,60 @@ import crypto from 'node:crypto'; +import process from 'node:process'; import dotenv from 'dotenv'; dotenv.config(); type JWT_STRICTNESS = boolean | 'lax' | 'strict' | 'none' | undefined; -type Config = { - PORT: number; - PG_URI: string; - AUTH: string; - JWT: string; - JWT_STRICTNESS: JWT_STRICTNESS; - DISCORD_WEBHOOK_URL: string; -}; +interface Config { + PORT: number; + PG_URI: string; + AUTH: string; + JWT: string; + JWT_STRICTNESS: JWT_STRICTNESS; + DISCORD_WEBHOOK_URL: string; +} let config: Config; export function useConfig(): Config { - if (typeof config !== 'undefined') { - return config; - } - - if (typeof process.env.PG_URI === 'undefined') { - console.error(`Failed to specify PG_URI, no database uri provided`); - process.exit(1); - } - - if (!process.env.AUTH || process.env.AUTH === 'default') { - throw new Error(`AUTH must be set to a strong secret`); - } - - if (!process.env.JWT) { - console.log(`JWT was not set, defaulting to a randomized byte hex string.`); - process.env.JWT = crypto.randomBytes(128).toString('hex'); - } - - if (typeof process.env.JWT_STRICTNESS === 'undefined') { - console.warn(`JWT_STRICTNESS not set, defaulting to lax`); - process.env.JWT_STRICTNESS = 'lax'; - } - - if (typeof process.env.DISCORD_WEBHOOK_URL === 'undefined') { - console.warn(`DISCORD_WEBHOOK_URL not set, defaulting to empty`); - process.env.DISCORD_WEBHOOK_URL = ''; - } - - config = { - PORT: process.env.PORT ? parseInt(process.env.PORT) : 3000, - PG_URI: process.env.PG_URI, - AUTH: process.env.AUTH ?? 'default', - JWT: process.env.JWT ?? 'default-secret-key', - JWT_STRICTNESS: process.env.JWT_STRICTNESS as JWT_STRICTNESS, - DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL, - }; - + if (typeof config !== 'undefined') { return config; + } + + if (typeof process.env.PG_URI === 'undefined') { + console.error(`Failed to specify PG_URI, no database uri provided`); + process.exit(1); + } + + if (!process.env.AUTH || process.env.AUTH === 'default') { + throw new Error(`AUTH must be set to a strong secret`); + } + + if (!process.env.JWT) { + console.log(`JWT was not set, defaulting to a randomized byte hex string.`); + process.env.JWT = crypto.randomBytes(128).toString('hex'); + } + + if (typeof process.env.JWT_STRICTNESS === 'undefined') { + console.warn(`JWT_STRICTNESS not set, defaulting to lax`); + process.env.JWT_STRICTNESS = 'lax'; + } + + if (typeof process.env.DISCORD_WEBHOOK_URL === 'undefined') { + console.warn(`DISCORD_WEBHOOK_URL not set, defaulting to empty`); + process.env.DISCORD_WEBHOOK_URL = ''; + } + + config = { + PORT: process.env.PORT ? Number.parseInt(process.env.PORT) : 3000, + PG_URI: process.env.PG_URI, + AUTH: process.env.AUTH ?? 'default', + JWT: process.env.JWT ?? 'default-secret-key', + JWT_STRICTNESS: process.env.JWT_STRICTNESS as JWT_STRICTNESS, + DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL, + }; + + return config; } diff --git a/packages/api-main/src/gets/authVerify.ts b/packages/api-main/src/gets/authVerify.ts index a32a4302..919c5f80 100644 --- a/packages/api-main/src/gets/authVerify.ts +++ b/packages/api-main/src/gets/authVerify.ts @@ -3,16 +3,15 @@ import type { Cookie } from 'elysia'; import { verifyJWT } from '../shared/jwt'; export async function AuthVerify(auth: Cookie) { - try { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; - } - - return typeof response === 'undefined' ? { status: 401, error: 'token expired' } : { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 401, error: 'unauthorized signature or key provided, failed to verify' }; + try { + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; } + + return typeof response === 'undefined' ? { status: 401, error: 'token expired' } : { status: 200 }; + } catch (err) { + console.error(err); + return { status: 401, error: 'unauthorized signature or key provided, failed to verify' }; + } } diff --git a/packages/api-main/src/gets/dislikes.ts b/packages/api-main/src/gets/dislikes.ts index f569f3f1..0d88dd7f 100644 --- a/packages/api-main/src/gets/dislikes.ts +++ b/packages/api-main/src/gets/dislikes.ts @@ -1,4 +1,4 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { desc, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -6,47 +6,46 @@ import { DislikesTable } from '../../drizzle/schema'; import { getJsonbArrayCount } from '../utility'; const statement = getDatabase() - .select() - .from(DislikesTable) - .where(eq(DislikesTable.post_hash, sql.placeholder('post_hash'))) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(DislikesTable.timestamp)) - .prepare('stmnt_get_dislikes'); + .select() + .from(DislikesTable) + .where(eq(DislikesTable.post_hash, sql.placeholder('post_hash'))) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(DislikesTable.timestamp)) + .prepare('stmnt_get_dislikes'); export async function Dislikes(query: typeof Gets.DislikesQuery.static) { - if (!query.hash) { - return { - status: 400, - error: 'malformed query, no hash provided', - }; + if (!query.hash) { + return { + status: 400, + error: 'malformed query, no hash provided', + }; + } + + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + + if (limit > 100) { + limit = 100; + } + + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } + + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } + + try { + if (query.count) { + return await getJsonbArrayCount(query.hash, DislikesTable._.name); } - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - - if (limit > 100) { - limit = 100; - } - - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } - - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } - - try { - if (query.count) { - return await getJsonbArrayCount(query.hash, DislikesTable._.name); - } - - const results = await statement.execute({ post_hash: query.hash, limit, offset }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { error: 'failed to read data from database' }; - } + const results = await statement.execute({ post_hash: query.hash, limit, offset }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/gets/feed.ts b/packages/api-main/src/gets/feed.ts index 8af19137..76bca719 100644 --- a/packages/api-main/src/gets/feed.ts +++ b/packages/api-main/src/gets/feed.ts @@ -1,57 +1,55 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, count, desc, gte, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable } from '../../drizzle/schema'; const statement = getDatabase() - .select() - .from(FeedTable) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .where( - and( - isNull(FeedTable.removed_at), - isNull(FeedTable.post_hash), - gte(FeedTable.quantity, sql.placeholder('minQuantity')), - ), - ) - .orderBy(desc(FeedTable.timestamp)) - .prepare('stmnt_get_feed'); + .select() + .from(FeedTable) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .where( + and( + isNull(FeedTable.removed_at), + isNull(FeedTable.post_hash), + gte(FeedTable.quantity, sql.placeholder('minQuantity')), + ), + ) + .orderBy(desc(FeedTable.timestamp)) + .prepare('stmnt_get_feed'); export async function Feed(query: typeof Gets.FeedQuery.static) { - if (query.count) { - try { - return await getDatabase().select({ count: count() }).from(FeedTable); - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to read data from database' }; - } - } - - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; - - if (limit > 100) { - limit = 100; - } - - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } - - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } - + if (query.count) { try { - const results = await statement.execute({ offset, limit, minQuantity }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 400, error: 'failed to read data from database' }; + return await getDatabase().select({ count: count() }).from(FeedTable); + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to read data from database' }; } + } + + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; + + if (limit > 100) { + limit = 100; + } + + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } + + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } + + try { + const results = await statement.execute({ offset, limit, minQuantity }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 400, error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/gets/flags.ts b/packages/api-main/src/gets/flags.ts index b16e9f5e..df4b45aa 100644 --- a/packages/api-main/src/gets/flags.ts +++ b/packages/api-main/src/gets/flags.ts @@ -1,4 +1,4 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { desc, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -6,47 +6,46 @@ import { FlagsTable } from '../../drizzle/schema'; import { getJsonbArrayCount } from '../utility'; const statement = getDatabase() - .select() - .from(FlagsTable) - .where(eq(FlagsTable.post_hash, sql.placeholder('post_hash'))) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(FlagsTable.timestamp)) - .prepare('stmnt_get_flags'); + .select() + .from(FlagsTable) + .where(eq(FlagsTable.post_hash, sql.placeholder('post_hash'))) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(FlagsTable.timestamp)) + .prepare('stmnt_get_flags'); export async function Flags(query: typeof Gets.FlagsQuery.static) { - if (!query.hash) { - return { - status: 400, - error: 'malformed query, no hash provided', - }; + if (!query.hash) { + return { + status: 400, + error: 'malformed query, no hash provided', + }; + } + + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + + if (limit > 100) { + limit = 100; + } + + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } + + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } + + try { + if (query.count) { + return await getJsonbArrayCount(query.hash, FlagsTable._.name); } - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - - if (limit > 100) { - limit = 100; - } - - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } - - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } - - try { - if (query.count) { - return await getJsonbArrayCount(query.hash, FlagsTable._.name); - } - - const results = await statement.execute({ post_hash: query.hash, limit, offset }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { error: 'failed to read data from database' }; - } + const results = await statement.execute({ post_hash: query.hash, limit, offset }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/gets/followers.ts b/packages/api-main/src/gets/followers.ts index b57a5345..43356ff7 100644 --- a/packages/api-main/src/gets/followers.ts +++ b/packages/api-main/src/gets/followers.ts @@ -1,40 +1,39 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, desc, eq, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FollowsTable } from '../../drizzle/schema'; const statementGetFollowers = getDatabase() - .select({ address: FollowsTable.follower, hash: FollowsTable.hash }) - .from(FollowsTable) - .where(and(eq(FollowsTable.following, sql.placeholder('following')), isNull(FollowsTable.removed_at))) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(FollowsTable.timestamp)) - .prepare('stmnt_get_followers'); + .select({ address: FollowsTable.follower, hash: FollowsTable.hash }) + .from(FollowsTable) + .where(and(eq(FollowsTable.following, sql.placeholder('following')), isNull(FollowsTable.removed_at))) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(FollowsTable.timestamp)) + .prepare('stmnt_get_followers'); export async function Followers(query: typeof Gets.FollowersQuery.static) { - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await statementGetFollowers.execute({ limit, offset, following: query.address }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find matching followers' }; - } + try { + const results = await statementGetFollowers.execute({ limit, offset, following: query.address }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find matching followers' }; + } } diff --git a/packages/api-main/src/gets/following.ts b/packages/api-main/src/gets/following.ts index a66d5abd..3622088f 100644 --- a/packages/api-main/src/gets/following.ts +++ b/packages/api-main/src/gets/following.ts @@ -1,40 +1,39 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, desc, eq, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FollowsTable } from '../../drizzle/schema'; const statementGetFollowing = getDatabase() - .select({ address: FollowsTable.following, hash: FollowsTable.hash }) - .from(FollowsTable) - .where(and(eq(FollowsTable.follower, sql.placeholder('follower')), isNull(FollowsTable.removed_at))) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(FollowsTable.timestamp)) - .prepare('stmnt_get_following'); + .select({ address: FollowsTable.following, hash: FollowsTable.hash }) + .from(FollowsTable) + .where(and(eq(FollowsTable.follower, sql.placeholder('follower')), isNull(FollowsTable.removed_at))) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(FollowsTable.timestamp)) + .prepare('stmnt_get_following'); export async function Following(query: typeof Gets.FollowingQuery.static) { - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await statementGetFollowing.execute({ follower: query.address, limit, offset }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find matching following' }; - } + try { + const results = await statementGetFollowing.execute({ follower: query.address, limit, offset }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find matching following' }; + } } diff --git a/packages/api-main/src/gets/health.ts b/packages/api-main/src/gets/health.ts index 9ec136ee..1ac97ba1 100644 --- a/packages/api-main/src/gets/health.ts +++ b/packages/api-main/src/gets/health.ts @@ -1,3 +1,3 @@ export function health() { - return { status: 'ok' }; + return { status: 'ok' }; } diff --git a/packages/api-main/src/gets/index.ts b/packages/api-main/src/gets/index.ts index f29ef636..5425689a 100644 --- a/packages/api-main/src/gets/index.ts +++ b/packages/api-main/src/gets/index.ts @@ -5,6 +5,7 @@ export * from './flags'; export * from './followers'; export * from './following'; export * from './health'; +export * from './isFollowing'; export * from './lastBlock'; export * from './likes'; export * from './notifications'; @@ -13,4 +14,3 @@ export * from './post'; export * from './posts'; export * from './replies'; export * from './search'; -export * from './isFollowing'; diff --git a/packages/api-main/src/gets/isFollowing.ts b/packages/api-main/src/gets/isFollowing.ts index dd0ee8ed..e90b9bb2 100644 --- a/packages/api-main/src/gets/isFollowing.ts +++ b/packages/api-main/src/gets/isFollowing.ts @@ -1,32 +1,31 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, eq, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FollowsTable } from '../../drizzle/schema'; const statementIsFollowing = getDatabase() - .select() - .from(FollowsTable) - .where( - and( - eq(FollowsTable.following, sql.placeholder('following')), - eq(FollowsTable.follower, sql.placeholder('follower')), - isNull(FollowsTable.removed_at), - ), - ) - .limit(1) - .prepare('stmnt_is_following'); + .select() + .from(FollowsTable) + .where( + and( + eq(FollowsTable.following, sql.placeholder('following')), + eq(FollowsTable.follower, sql.placeholder('follower')), + isNull(FollowsTable.removed_at), + ), + ) + .limit(1) + .prepare('stmnt_is_following'); export async function IsFollowing(query: typeof Gets.IsFollowingQuery.static) { - try { - const results = await statementIsFollowing.execute({ following: query.following, follower: query.follower }); - if (results.length <= 0) { - return { status: 404, error: 'user is not following' }; - } - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'user is not following' }; + try { + const results = await statementIsFollowing.execute({ following: query.following, follower: query.follower }); + if (results.length <= 0) { + return { status: 404, error: 'user is not following' }; } + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'user is not following' }; + } } diff --git a/packages/api-main/src/gets/lastBlock.ts b/packages/api-main/src/gets/lastBlock.ts index 80520950..8ccbc1ba 100644 --- a/packages/api-main/src/gets/lastBlock.ts +++ b/packages/api-main/src/gets/lastBlock.ts @@ -4,23 +4,22 @@ import { getDatabase } from '../../drizzle/db'; import { ReaderState } from '../../drizzle/schema'; const statement = getDatabase() - .select() - .from(ReaderState) - .where(eq(ReaderState.id, 0)) - .limit(1) - .prepare('stmnt_get_state'); + .select() + .from(ReaderState) + .where(eq(ReaderState.id, 0)) + .limit(1) + .prepare('stmnt_get_state'); export async function LastBlock() { - try { - const [state] = await statement.execute(); - if (!state) { - return { status: 404, rows: [] }; - } - - return { status: 200, rows: [state] }; - } - catch (error) { - console.error(error); - return { error: 'failed to read data from database' }; + try { + const [state] = await statement.execute(); + if (!state) { + return { status: 404, rows: [] }; } + + return { status: 200, rows: [state] }; + } catch (error) { + console.error(error); + return { error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/gets/likes.ts b/packages/api-main/src/gets/likes.ts index 846ac8fd..1fddbdb7 100644 --- a/packages/api-main/src/gets/likes.ts +++ b/packages/api-main/src/gets/likes.ts @@ -1,4 +1,4 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { desc, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -6,47 +6,46 @@ import { LikesTable } from '../../drizzle/schema'; import { getJsonbArrayCount } from '../utility'; const statement = getDatabase() - .select() - .from(LikesTable) - .where(eq(LikesTable.post_hash, sql.placeholder('post_hash'))) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(LikesTable.timestamp)) - .prepare('stmnt_get_likes'); + .select() + .from(LikesTable) + .where(eq(LikesTable.post_hash, sql.placeholder('post_hash'))) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(LikesTable.timestamp)) + .prepare('stmnt_get_likes'); export async function Likes(query: typeof Gets.LikesQuery.static) { - if (!query.hash) { - return { - status: 400, - error: 'malformed query, no hash provided', - }; + if (!query.hash) { + return { + status: 400, + error: 'malformed query, no hash provided', + }; + } + + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + + if (limit > 100) { + limit = 100; + } + + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } + + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } + + try { + if (query.count) { + return await getJsonbArrayCount(query.hash, LikesTable._.name); } - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - - if (limit > 100) { - limit = 100; - } - - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } - - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } - - try { - if (query.count) { - return await getJsonbArrayCount(query.hash, LikesTable._.name); - } - - const results = await statement.execute({ post_hash: query.hash, limit, offset }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { error: 'failed to read data from database' }; - } + const results = await statement.execute({ post_hash: query.hash, limit, offset }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/gets/notifications.ts b/packages/api-main/src/gets/notifications.ts index 2ba1b3b1..f045d9ad 100644 --- a/packages/api-main/src/gets/notifications.ts +++ b/packages/api-main/src/gets/notifications.ts @@ -1,6 +1,6 @@ -import type { Cookie } from 'elysia'; +import type { Gets } from '@atomone/dither-api-types'; -import { type Gets } from '@atomone/dither-api-types'; +import type { Cookie } from 'elysia'; import { and, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -8,74 +8,72 @@ import { NotificationTable } from '../../drizzle/schema'; import { verifyJWT } from '../shared/jwt'; const getNotificationsStatement = getDatabase() - .select() - .from(NotificationTable) - .where(and(eq(NotificationTable.owner, sql.placeholder('owner')), eq(NotificationTable.was_read, false))) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .prepare('stmnt_get_notifications'); + .select() + .from(NotificationTable) + .where(and(eq(NotificationTable.owner, sql.placeholder('owner')), eq(NotificationTable.was_read, false))) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .prepare('stmnt_get_notifications'); export async function Notifications(query: typeof Gets.NotificationsQuery.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; - } + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await getNotificationsStatement.execute({ owner: response, limit, offset }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find matching reply' }; - } + try { + const results = await getNotificationsStatement.execute({ owner: response, limit, offset }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find matching reply' }; + } } const statementReadNotification = getDatabase() - .update(NotificationTable) - .set({ - was_read: true, - }) - .where( - and(eq(NotificationTable.hash, sql.placeholder('hash')), eq(NotificationTable.owner, sql.placeholder('owner'))), - ) - .prepare('stmnt_read_notification'); + .update(NotificationTable) + .set({ + was_read: true, + }) + .where( + and(eq(NotificationTable.hash, sql.placeholder('hash')), eq(NotificationTable.owner, sql.placeholder('owner'))), + ) + .prepare('stmnt_read_notification'); export async function ReadNotification(query: typeof Gets.ReadNotificationQuery.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; - } + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } - try { - const [notification] = await getDatabase() - .select() - .from(NotificationTable) - .where(and(eq(NotificationTable.hash, query.hash), eq(NotificationTable.owner, response))) - .limit(1); - if (!notification) { - return { status: 404, error: 'notification not found' }; - } - const results = await statementReadNotification.execute({ owner: response, hash: query.hash }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find matching reply' }; + try { + const [notification] = await getDatabase() + .select() + .from(NotificationTable) + .where(and(eq(NotificationTable.hash, query.hash), eq(NotificationTable.owner, response))) + .limit(1); + if (!notification) { + return { status: 404, error: 'notification not found' }; } + const results = await statementReadNotification.execute({ owner: response, hash: query.hash }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find matching reply' }; + } } diff --git a/packages/api-main/src/gets/notificationsCount.ts b/packages/api-main/src/gets/notificationsCount.ts index c7191588..fff3f7b8 100644 --- a/packages/api-main/src/gets/notificationsCount.ts +++ b/packages/api-main/src/gets/notificationsCount.ts @@ -1,6 +1,6 @@ -import type { Cookie } from 'elysia'; +import type { Gets } from '@atomone/dither-api-types'; -import { type Gets } from '@atomone/dither-api-types'; +import type { Cookie } from 'elysia'; import { and, count, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -8,28 +8,27 @@ import { NotificationTable } from '../../drizzle/schema'; import { verifyJWT } from '../shared/jwt'; const getNotificationsCountStatement = getDatabase() - .select({ count: count() }) - .from(NotificationTable) - .where( - and( - eq(NotificationTable.owner, sql.placeholder('owner')), - eq(NotificationTable.was_read, false), - ), - ) - .prepare('stmnt_get_notifications_count'); + .select({ count: count() }) + .from(NotificationTable) + .where( + and( + eq(NotificationTable.owner, sql.placeholder('owner')), + eq(NotificationTable.was_read, false), + ), + ) + .prepare('stmnt_get_notifications_count'); export async function NotificationsCount(_query: typeof Gets.NotificationsCountQuery.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; - } + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } - try { - const [result] = await getNotificationsCountStatement.execute({ owner: response }); - return { status: 200, count: result?.count ?? 0 }; - } - catch (error) { - console.error(error); - return { status: 500, error: 'failed to count notifications' }; - } + try { + const [result] = await getNotificationsCountStatement.execute({ owner: response }); + return { status: 200, count: result?.count ?? 0 }; + } catch (error) { + console.error(error); + return { status: 500, error: 'failed to count notifications' }; + } } diff --git a/packages/api-main/src/gets/post.ts b/packages/api-main/src/gets/post.ts index 3fda0cf2..a73de311 100644 --- a/packages/api-main/src/gets/post.ts +++ b/packages/api-main/src/gets/post.ts @@ -1,33 +1,32 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, eq, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable } from '../../drizzle/schema'; const statementGetPost = getDatabase() - .select() - .from(FeedTable) - .where(and(isNull(FeedTable.removed_at), eq(FeedTable.hash, sql.placeholder('hash')))) - .prepare('stmnt_get_post'); + .select() + .from(FeedTable) + .where(and(isNull(FeedTable.removed_at), eq(FeedTable.hash, sql.placeholder('hash')))) + .prepare('stmnt_get_post'); const statementGetReply = getDatabase() - .select() - .from(FeedTable) - .where(and(isNull(FeedTable.removed_at), eq(FeedTable.hash, sql.placeholder('hash')), eq(FeedTable.post_hash, sql.placeholder('post_hash')))) - .prepare('stmnt_get_reply'); + .select() + .from(FeedTable) + .where(and(isNull(FeedTable.removed_at), eq(FeedTable.hash, sql.placeholder('hash')), eq(FeedTable.post_hash, sql.placeholder('post_hash')))) + .prepare('stmnt_get_reply'); export async function Post(query: typeof Gets.PostQuery.static) { - try { - if (query.post_hash) { - const results = await statementGetReply.execute({ hash: query.hash, post_hash: query.post_hash }); - return results.length <= 0 ? { status: 404, rows: [] } : { status: 200, rows: results }; - } - - const results = await statementGetPost.execute({ hash: query.hash }); - return results.length <= 0 ? { status: 404, rows: [] } : { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 400, error: 'failed to read data from database' }; + try { + if (query.post_hash) { + const results = await statementGetReply.execute({ hash: query.hash, post_hash: query.post_hash }); + return results.length <= 0 ? { status: 404, rows: [] } : { status: 200, rows: results }; } + + const results = await statementGetPost.execute({ hash: query.hash }); + return results.length <= 0 ? { status: 404, rows: [] } : { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 400, error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/gets/posts.ts b/packages/api-main/src/gets/posts.ts index 3e7dec9b..d5db0358 100644 --- a/packages/api-main/src/gets/posts.ts +++ b/packages/api-main/src/gets/posts.ts @@ -1,92 +1,88 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, desc, eq, gte, inArray, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable, FollowsTable } from '../../drizzle/schema'; const statement = getDatabase() - .select() - .from(FeedTable) - .where(and( - eq(FeedTable.author, sql.placeholder('author')), - isNull(FeedTable.removed_at), - isNull(FeedTable.post_hash), // Do not return replies - gte(FeedTable.quantity, sql.placeholder('minQuantity')), - )) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(FeedTable.timestamp)) - .prepare('stmnt_get_posts'); + .select() + .from(FeedTable) + .where(and( + eq(FeedTable.author, sql.placeholder('author')), + isNull(FeedTable.removed_at), + isNull(FeedTable.post_hash), // Do not return replies + gte(FeedTable.quantity, sql.placeholder('minQuantity')), + )) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(FeedTable.timestamp)) + .prepare('stmnt_get_posts'); export async function Posts(query: typeof Gets.PostsQuery.static) { - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await statement.execute({ author: query.address, limit, offset, minQuantity }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find matching reply' }; - } + try { + const results = await statement.execute({ author: query.address, limit, offset, minQuantity }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find matching reply' }; + } } const followingPostsStatement = getDatabase() - .select() - .from(FeedTable) - .where(and( - inArray(FeedTable.author, - getDatabase() - .select({ following: FollowsTable.following }) - .from(FollowsTable) - .where(and(eq(FollowsTable.follower, sql.placeholder('address')), isNull(FollowsTable.removed_at))), - ), - isNull(FeedTable.post_hash), - isNull(FeedTable.removed_at), - gte(FeedTable.quantity, sql.placeholder('minQuantity')), - )) - .orderBy(desc(FeedTable.timestamp)) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .prepare('stmnt_posts_from_following'); + .select() + .from(FeedTable) + .where(and( + inArray(FeedTable.author, getDatabase() + .select({ following: FollowsTable.following }) + .from(FollowsTable) + .where(and(eq(FollowsTable.follower, sql.placeholder('address')), isNull(FollowsTable.removed_at)))), + isNull(FeedTable.post_hash), + isNull(FeedTable.removed_at), + gte(FeedTable.quantity, sql.placeholder('minQuantity')), + )) + .orderBy(desc(FeedTable.timestamp)) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .prepare('stmnt_posts_from_following'); export async function FollowingPosts(query: typeof Gets.PostsQuery.static) { - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await followingPostsStatement.execute({ address: query.address, limit, offset, minQuantity }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to posts from followed users' }; - } + try { + const results = await followingPostsStatement.execute({ address: query.address, limit, offset, minQuantity }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to posts from followed users' }; + } } diff --git a/packages/api-main/src/gets/replies.ts b/packages/api-main/src/gets/replies.ts index 03b67f86..5f036ed5 100644 --- a/packages/api-main/src/gets/replies.ts +++ b/packages/api-main/src/gets/replies.ts @@ -1,4 +1,4 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, desc, eq, gte, isNotNull, isNull, sql } from 'drizzle-orm'; import { alias } from 'drizzle-orm/pg-core'; @@ -6,88 +6,86 @@ import { getDatabase } from '../../drizzle/db'; import { FeedTable } from '../../drizzle/schema'; const statement = getDatabase() - .select() - .from(FeedTable) - .where(and( - eq(FeedTable.post_hash, sql.placeholder('hash')), - isNull(FeedTable.removed_at), - gte(FeedTable.quantity, sql.placeholder('minQuantity')), - )) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .orderBy(desc(FeedTable.timestamp)) - .prepare('stmnt_get_replies'); + .select() + .from(FeedTable) + .where(and( + eq(FeedTable.post_hash, sql.placeholder('hash')), + isNull(FeedTable.removed_at), + gte(FeedTable.quantity, sql.placeholder('minQuantity')), + )) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .orderBy(desc(FeedTable.timestamp)) + .prepare('stmnt_get_replies'); export async function Replies(query: typeof Gets.RepliesQuery.static) { - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await statement.execute({ hash: query.hash, limit, offset, minQuantity }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find matching reply' }; - } + try { + const results = await statement.execute({ hash: query.hash, limit, offset, minQuantity }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find matching reply' }; + } } const feed = FeedTable; const parentFeed = alias(FeedTable, 'parent'); const getUserRepliesWithParent = getDatabase() - .select({ - reply: feed, - parent: parentFeed, - }) - .from(feed) - .innerJoin(parentFeed, eq(feed.post_hash, parentFeed.hash)) - .where(and( - eq(feed.author, sql.placeholder('author')), - isNotNull(feed.post_hash), - gte(feed.quantity, sql.placeholder('minQuantity')), - )) - .orderBy(desc(feed.timestamp)) - .limit(sql.placeholder('limit')) - .offset(sql.placeholder('offset')) - .prepare('stmnt_get_user_replies_with_parents'); + .select({ + reply: feed, + parent: parentFeed, + }) + .from(feed) + .innerJoin(parentFeed, eq(feed.post_hash, parentFeed.hash)) + .where(and( + eq(feed.author, sql.placeholder('author')), + isNotNull(feed.post_hash), + gte(feed.quantity, sql.placeholder('minQuantity')), + )) + .orderBy(desc(feed.timestamp)) + .limit(sql.placeholder('limit')) + .offset(sql.placeholder('offset')) + .prepare('stmnt_get_user_replies_with_parents'); export async function UserReplies(query: typeof Gets.UserRepliesQuery.static) { - let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; - const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; - const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; + let limit = typeof query.limit !== 'undefined' ? Number(query.limit) : 100; + const offset = typeof query.offset !== 'undefined' ? Number(query.offset) : 0; + const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; - if (limit > 100) { - limit = 100; - } + if (limit > 100) { + limit = 100; + } - if (limit <= 0) { - return { status: 400, error: 'limit must be at least 1' }; - } + if (limit <= 0) { + return { status: 400, error: 'limit must be at least 1' }; + } - if (offset < 0) { - return { status: 400, error: 'offset must be at least 0' }; - } + if (offset < 0) { + return { status: 400, error: 'offset must be at least 0' }; + } - try { - const results = await getUserRepliesWithParent.execute({ author: query.address, limit, offset, minQuantity }); - return { status: 200, rows: results }; - } - catch (error) { - console.error(error); - return { status: 404, error: 'failed to find user replies' }; - } + try { + const results = await getUserRepliesWithParent.execute({ author: query.address, limit, offset, minQuantity }); + return { status: 200, rows: results }; + } catch (error) { + console.error(error); + return { status: 404, error: 'failed to find user replies' }; + } } diff --git a/packages/api-main/src/gets/search.ts b/packages/api-main/src/gets/search.ts index 6cf7a112..ce4a0a56 100644 --- a/packages/api-main/src/gets/search.ts +++ b/packages/api-main/src/gets/search.ts @@ -1,50 +1,49 @@ -import { type Gets } from '@atomone/dither-api-types'; +import type { Gets } from '@atomone/dither-api-types'; import { and, desc, gte, ilike, inArray, isNull, or, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable } from '../../drizzle/schema'; export async function Search(query: typeof Gets.SearchQuery.static) { - try { - const processedQuery = query.text - .trim() - .split(/\s+/) - .filter((w: string) => w.length > 0) - .map((w: string) => `${w}:*`) - .join(' & '); + try { + const processedQuery = query.text + .trim() + .split(/\s+/) + .filter((w: string) => w.length > 0) + .map((w: string) => `${w}:*`) + .join(' & '); - if (!processedQuery) { - return []; - } + if (!processedQuery) { + return []; + } - const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; - const matchedAuthors = await getDatabase() - .selectDistinct({ author: FeedTable.author }) - .from(FeedTable) - .where(and(ilike(FeedTable.author, `%${query.text}%`), isNull(FeedTable.removed_at))); - const matchedAuthorAddresses = matchedAuthors.map(a => a.author); + const minQuantity = typeof query.minQuantity !== 'undefined' ? query.minQuantity : '0'; + const matchedAuthors = await getDatabase() + .selectDistinct({ author: FeedTable.author }) + .from(FeedTable) + .where(and(ilike(FeedTable.author, `%${query.text}%`), isNull(FeedTable.removed_at))); + const matchedAuthorAddresses = matchedAuthors.map(a => a.author); - const matchedPosts = await getDatabase() - .select() - .from(FeedTable) - .where( - and( - or( - sql`to_tsvector('english', ${FeedTable.message}) @@ to_tsquery('english', ${processedQuery})`, - inArray(FeedTable.author, matchedAuthorAddresses), - ), - gte(FeedTable.quantity, minQuantity), - isNull(FeedTable.removed_at), - ), - ) - .limit(100) - .offset(0) - .orderBy(desc(FeedTable.timestamp)) - .execute(); - return { status: 200, rows: [...matchedPosts], users: matchedAuthorAddresses }; - } - catch (error) { - console.error(error); - return { status: 400, error: 'failed to read data from database' }; - } + const matchedPosts = await getDatabase() + .select() + .from(FeedTable) + .where( + and( + or( + sql`to_tsvector('english', ${FeedTable.message}) @@ to_tsquery('english', ${processedQuery})`, + inArray(FeedTable.author, matchedAuthorAddresses), + ), + gte(FeedTable.quantity, minQuantity), + isNull(FeedTable.removed_at), + ), + ) + .limit(100) + .offset(0) + .orderBy(desc(FeedTable.timestamp)) + .execute(); + return { status: 200, rows: [...matchedPosts], users: matchedAuthorAddresses }; + } catch (error) { + console.error(error); + return { status: 400, error: 'failed to read data from database' }; + } } diff --git a/packages/api-main/src/index.ts b/packages/api-main/src/index.ts index e0c6e514..1f5660bd 100644 --- a/packages/api-main/src/index.ts +++ b/packages/api-main/src/index.ts @@ -1,83 +1,84 @@ +import process from 'node:process'; import { Gets, Posts } from '@atomone/dither-api-types'; import { cors } from '@elysiajs/cors'; import node from '@elysiajs/node'; import { Elysia, t } from 'elysia'; +import { useConfig } from './config'; import * as GetRequests from './gets/index'; import * as PostRequests from './posts/index'; -import { useConfig } from './config'; const config = useConfig(); const app = new Elysia({ adapter: node(), prefix: '/v1' }); export function start() { - app.use(cors()); - app.get('/health', GetRequests.health); - app.get('/dislikes', ({ query }) => GetRequests.Dislikes(query), { query: Gets.DislikesQuery }); - app.get('/feed', ({ query }) => GetRequests.Feed(query), { query: Gets.FeedQuery }); - app.get('/flags', ({ query }) => GetRequests.Flags(query), { query: Gets.FlagsQuery }); - app.get('/is-following', ({ query }) => GetRequests.IsFollowing(query), { query: Gets.IsFollowingQuery }); - app.get('/followers', ({ query }) => GetRequests.Followers(query), { query: Gets.FollowersQuery }); - app.get('/following', ({ query }) => GetRequests.Following(query), { query: Gets.FollowingQuery }); - app.get('/likes', ({ query }) => GetRequests.Likes(query), { query: Gets.LikesQuery }); - app.get('/posts', ({ query }) => GetRequests.Posts(query), { query: Gets.PostsQuery }); - app.get('/post', ({ query }) => GetRequests.Post(query), { query: Gets.PostQuery }); - app.get('/replies', ({ query }) => GetRequests.Replies(query), { query: Gets.RepliesQuery }); - app.get('/search', ({ query }) => GetRequests.Search(query), { query: Gets.SearchQuery }); - app.get('/user-replies', ({ query }) => GetRequests.UserReplies(query), { query: Gets.UserRepliesQuery }); - app.get('/following-posts', ({ query }) => GetRequests.FollowingPosts(query), { query: Gets.PostsQuery }); - app.get('/last-block', GetRequests.LastBlock); - app.get('/auth-verify', ({ cookie: { auth } }) => GetRequests.AuthVerify(auth)); - app.get('/notifications', ({ query, cookie: { auth } }) => GetRequests.Notifications(query, auth), { - query: Gets.NotificationsQuery, - }); - app.get('/notifications-count', ({ query, cookie: { auth } }) => GetRequests.NotificationsCount(query, auth), { - query: Gets.NotificationsCountQuery, - }); + app.use(cors()); + app.get('/health', GetRequests.health); + app.get('/dislikes', ({ query }) => GetRequests.Dislikes(query), { query: Gets.DislikesQuery }); + app.get('/feed', ({ query }) => GetRequests.Feed(query), { query: Gets.FeedQuery }); + app.get('/flags', ({ query }) => GetRequests.Flags(query), { query: Gets.FlagsQuery }); + app.get('/is-following', ({ query }) => GetRequests.IsFollowing(query), { query: Gets.IsFollowingQuery }); + app.get('/followers', ({ query }) => GetRequests.Followers(query), { query: Gets.FollowersQuery }); + app.get('/following', ({ query }) => GetRequests.Following(query), { query: Gets.FollowingQuery }); + app.get('/likes', ({ query }) => GetRequests.Likes(query), { query: Gets.LikesQuery }); + app.get('/posts', ({ query }) => GetRequests.Posts(query), { query: Gets.PostsQuery }); + app.get('/post', ({ query }) => GetRequests.Post(query), { query: Gets.PostQuery }); + app.get('/replies', ({ query }) => GetRequests.Replies(query), { query: Gets.RepliesQuery }); + app.get('/search', ({ query }) => GetRequests.Search(query), { query: Gets.SearchQuery }); + app.get('/user-replies', ({ query }) => GetRequests.UserReplies(query), { query: Gets.UserRepliesQuery }); + app.get('/following-posts', ({ query }) => GetRequests.FollowingPosts(query), { query: Gets.PostsQuery }); + app.get('/last-block', GetRequests.LastBlock); + app.get('/auth-verify', ({ cookie: { auth } }) => GetRequests.AuthVerify(auth)); + app.get('/notifications', ({ query, cookie: { auth } }) => GetRequests.Notifications(query, auth), { + query: Gets.NotificationsQuery, + }); + app.get('/notifications-count', ({ query, cookie: { auth } }) => GetRequests.NotificationsCount(query, auth), { + query: Gets.NotificationsCountQuery, + }); - app.post('/auth-create', ({ body, request }) => PostRequests.AuthCreate(body, request), { body: Posts.AuthCreateBody }); - app.post('/auth', ({ body, cookie: { auth }, request }) => PostRequests.Auth(body, auth, request), { body: t.Object({ - id: t.Number(), - pub_key: t.Object({ type: t.String(), value: t.String() }), - signature: t.String(), - json: t.Optional(t.Boolean()), - }) }); + app.post('/auth-create', ({ body, request }) => PostRequests.AuthCreate(body, request), { body: Posts.AuthCreateBody }); + app.post('/auth', ({ body, cookie: { auth }, request }) => PostRequests.Auth(body, auth, request), { body: t.Object({ + id: t.Number(), + pub_key: t.Object({ type: t.String(), value: t.String() }), + signature: t.String(), + json: t.Optional(t.Boolean()), + }) }); - app.post('/post', ({ body, headers }) => PostRequests.Post(body, headers), { body: Posts.PostBody }); - app.post('/reply', ({ body, headers }) => PostRequests.Reply(body, headers), { body: Posts.ReplyBody }); - app.post('/follow', ({ body, headers }) => PostRequests.Follow(body, headers), { body: Posts.FollowBody }); - app.post('/unfollow', ({ body, headers }) => PostRequests.Unfollow(body, headers), { body: Posts.UnfollowBody }); - app.post('/like', ({ body, headers }) => PostRequests.Like(body, headers), { body: Posts.LikeBody }); - app.post('/dislike', ({ body, headers }) => PostRequests.Dislike(body, headers), { body: Posts.DislikeBody }); - app.post('/flag', ({ body, headers }) => PostRequests.Flag(body, headers), { body: Posts.FlagBody }); - app.post('/post-remove', ({ body, headers }) => PostRequests.PostRemove(body, headers), { body: Posts.PostRemoveBody }); - app.post('/update-state', ({ body, headers }) => PostRequests.UpdateState(body, headers), { body: t.Object({ last_block: t.String() }) }); - app.post('/logout', ({ cookie: { auth } }) => PostRequests.Logout(auth)); + app.post('/post', ({ body, headers }) => PostRequests.Post(body, headers), { body: Posts.PostBody }); + app.post('/reply', ({ body, headers }) => PostRequests.Reply(body, headers), { body: Posts.ReplyBody }); + app.post('/follow', ({ body, headers }) => PostRequests.Follow(body, headers), { body: Posts.FollowBody }); + app.post('/unfollow', ({ body, headers }) => PostRequests.Unfollow(body, headers), { body: Posts.UnfollowBody }); + app.post('/like', ({ body, headers }) => PostRequests.Like(body, headers), { body: Posts.LikeBody }); + app.post('/dislike', ({ body, headers }) => PostRequests.Dislike(body, headers), { body: Posts.DislikeBody }); + app.post('/flag', ({ body, headers }) => PostRequests.Flag(body, headers), { body: Posts.FlagBody }); + app.post('/post-remove', ({ body, headers }) => PostRequests.PostRemove(body, headers), { body: Posts.PostRemoveBody }); + app.post('/update-state', ({ body, headers }) => PostRequests.UpdateState(body, headers), { body: t.Object({ last_block: t.String() }) }); + app.post('/logout', ({ cookie: { auth } }) => PostRequests.Logout(auth)); - app.post('/notification-read', ({ query, cookie: { auth } }) => GetRequests.ReadNotification(query, auth), { - query: Gets.ReadNotificationQuery, - }); + app.post('/notification-read', ({ query, cookie: { auth } }) => GetRequests.ReadNotification(query, auth), { + query: Gets.ReadNotificationQuery, + }); - app.post('/mod/post-remove', ({ body, cookie: { auth } }) => PostRequests.ModRemovePost(body, auth), { - body: Posts.ModRemovePostBody, - }); - app.post('/mod/post-restore', ({ body, cookie: { auth } }) => PostRequests.ModRestorePost(body, auth), { - body: Posts.ModRemovePostBody, - }); - app.post('/mod/ban', ({ body, cookie: { auth } }) => PostRequests.ModBan(body, auth), { - body: Posts.ModBanBody, - }); - app.post('/mod/unban', ({ body, cookie: { auth } }) => PostRequests.ModUnban(body, auth), { - body: Posts.ModBanBody, - }); + app.post('/mod/post-remove', ({ body, cookie: { auth } }) => PostRequests.ModRemovePost(body, auth), { + body: Posts.ModRemovePostBody, + }); + app.post('/mod/post-restore', ({ body, cookie: { auth } }) => PostRequests.ModRestorePost(body, auth), { + body: Posts.ModRemovePostBody, + }); + app.post('/mod/ban', ({ body, cookie: { auth } }) => PostRequests.ModBan(body, auth), { + body: Posts.ModBanBody, + }); + app.post('/mod/unban', ({ body, cookie: { auth } }) => PostRequests.ModUnban(body, auth), { + body: Posts.ModBanBody, + }); - app.listen(config.PORT); + app.listen(config.PORT); } export function stop() { - app.stop(true); + app.stop(true); } if (!process.env.SKIP_START) { - start(); + start(); } diff --git a/packages/api-main/src/posts/auth.ts b/packages/api-main/src/posts/auth.ts index 2d7937cf..99749b06 100644 --- a/packages/api-main/src/posts/auth.ts +++ b/packages/api-main/src/posts/auth.ts @@ -1,6 +1,6 @@ -import type { Cookie } from 'elysia'; +import type { Posts } from '@atomone/dither-api-types'; -import { type Posts } from '@atomone/dither-api-types'; +import type { Cookie } from 'elysia'; import { useConfig } from '../config'; import { useRateLimiter } from '../shared/useRateLimiter'; @@ -12,31 +12,30 @@ const { JWT_STRICTNESS } = useConfig(); const rateLimiter = useRateLimiter(); export async function Auth(body: typeof Posts.AuthBody.static, auth: Cookie, request: Request) { - const ip = getRequestIP(request); - const isLimited = await rateLimiter.isLimited(ip); - if (isLimited) { - return { status: 429, error: 'Too many requests, try again later' }; + const ip = getRequestIP(request); + const isLimited = await rateLimiter.isLimited(ip); + if (isLimited) { + return { status: 429, error: 'Too many requests, try again later' }; + } + + await rateLimiter.update(ip); + + try { + if ('json' in body) { + const jwt = await verifyAndCreate(body.pub_key.value, body.signature, body.id); + return jwt; } - await rateLimiter.update(ip); - - try { - if ('json' in body) { - const jwt = await verifyAndCreate(body.pub_key.value, body.signature, body.id); - return jwt; - } - - const result = await verifyAndCreate(body.pub_key.value, body.signature, body.id); - if (result.status === 200) { - auth.remove(); - auth.set({ sameSite: JWT_STRICTNESS, httpOnly: true, secure: true, value: result.bearer, maxAge: 259200, priority: 'high' }); - return { status: 200 }; - } - - return result; - } - catch (err) { - console.error(err); - return { status: 400, error: 'unauthorized signature or key provided, failed to verify' }; + const result = await verifyAndCreate(body.pub_key.value, body.signature, body.id); + if (result.status === 200) { + auth.remove(); + auth.set({ sameSite: JWT_STRICTNESS, httpOnly: true, secure: true, value: result.bearer, maxAge: 259200, priority: 'high' }); + return { status: 200 }; } + + return result; + } catch (err) { + console.error(err); + return { status: 400, error: 'unauthorized signature or key provided, failed to verify' }; + } } diff --git a/packages/api-main/src/posts/authCreate.ts b/packages/api-main/src/posts/authCreate.ts index 2c4d25a9..30560133 100644 --- a/packages/api-main/src/posts/authCreate.ts +++ b/packages/api-main/src/posts/authCreate.ts @@ -1,4 +1,4 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { useRateLimiter } from '../shared/useRateLimiter'; import { useUserAuth } from '../shared/useUserAuth'; @@ -8,20 +8,19 @@ const { add } = useUserAuth(); const rateLimiter = useRateLimiter(); export async function AuthCreate(body: typeof Posts.AuthCreateBody.static, request: Request) { - const ip = getRequestIP(request); - const isLimited = await rateLimiter.isLimited(ip); - if (isLimited) { - return { status: 429, error: 'Too many requests, try again later' }; - } + const ip = getRequestIP(request); + const isLimited = await rateLimiter.isLimited(ip); + if (isLimited) { + return { status: 429, error: 'Too many requests, try again later' }; + } - await rateLimiter.update(ip); + await rateLimiter.update(ip); - try { - const result = await add(body.address); - return { status: 200, ...result }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to create authorization request' }; - } + try { + const result = await add(body.address); + return { status: 200, ...result }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to create authorization request' }; + } } diff --git a/packages/api-main/src/posts/dislike.ts b/packages/api-main/src/posts/dislike.ts index 876500a2..28f101d8 100644 --- a/packages/api-main/src/posts/dislike.ts +++ b/packages/api-main/src/posts/dislike.ts @@ -1,75 +1,74 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { DislikesTable, FeedTable } from '../../drizzle/schema'; +import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; +import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); -import { notify } from '../shared/notify'; -import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const statement = getDatabase() - .insert(DislikesTable) - .values({ - post_hash: sql.placeholder('post_hash'), - hash: sql.placeholder('hash'), - author: sql.placeholder('author'), - quantity: sql.placeholder('quantity'), - timestamp: sql.placeholder('timestamp'), - }) - .onConflictDoNothing() - .prepare('stmnt_add_dislike'); + .insert(DislikesTable) + .values({ + post_hash: sql.placeholder('post_hash'), + hash: sql.placeholder('hash'), + author: sql.placeholder('author'), + quantity: sql.placeholder('quantity'), + timestamp: sql.placeholder('timestamp'), + }) + .onConflictDoNothing() + .prepare('stmnt_add_dislike'); const statementAddDislikeToPost = getDatabase() - .update(FeedTable) - .set({ - dislikes: sql`${FeedTable.dislikes} + 1`, - dislikes_burnt: sql`(${FeedTable.dislikes_burnt})::int + ${sql.placeholder('quantity')}`, - }) - .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) - .prepare('stmnt_add_dislike_count_to_post'); + .update(FeedTable) + .set({ + dislikes: sql`${FeedTable.dislikes} + 1`, + dislikes_burnt: sql`(${FeedTable.dislikes_burnt})::int + ${sql.placeholder('quantity')}`, + }) + .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) + .prepare('stmnt_add_dislike_count_to_post'); export async function Dislike(body: typeof Posts.DislikeBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - if (body.post_hash.length !== 64) { - return { status: 400, error: 'Provided post_hash is not valid for dislike' }; - } - - try { - const result = await sharedQueries.doesPostExist(body.post_hash); - if (result.status !== 200) { - return { status: result.status, error: 'provided post_hash does not exist' }; - } + if (body.post_hash.length !== 64) { + return { status: 400, error: 'Provided post_hash is not valid for dislike' }; + } - const resultChanges = await statement.execute({ - post_hash: body.post_hash.toLowerCase(), - hash: body.hash.toLowerCase(), - author: body.from.toLowerCase(), - quantity: body.quantity, - timestamp: new Date(body.timestamp), - }); + try { + const result = await sharedQueries.doesPostExist(body.post_hash); + if (result.status !== 200) { + return { status: result.status, error: 'provided post_hash does not exist' }; + } - // Ensure that 'dislike' was triggered, and not already existing. - if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { - await statementAddDislikeToPost.execute({ post_hash: body.post_hash, quantity: body.quantity }); - await notify({ - post_hash: body.post_hash, - hash: body.hash, - type: 'dislike', - timestamp: new Date(body.timestamp), - actor: body.from, - }); - } + const resultChanges = await statement.execute({ + post_hash: body.post_hash.toLowerCase(), + hash: body.hash.toLowerCase(), + author: body.from.toLowerCase(), + quantity: body.quantity, + timestamp: new Date(body.timestamp), + }); - await postToDiscord(`Disliked by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to upsert data for dislike, dislike already exists' }; + // Ensure that 'dislike' was triggered, and not already existing. + if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { + await statementAddDislikeToPost.execute({ post_hash: body.post_hash, quantity: body.quantity }); + await notify({ + post_hash: body.post_hash, + hash: body.hash, + type: 'dislike', + timestamp: new Date(body.timestamp), + actor: body.from, + }); } + + await postToDiscord(`Disliked by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to upsert data for dislike, dislike already exists' }; + } } diff --git a/packages/api-main/src/posts/flag.ts b/packages/api-main/src/posts/flag.ts index 5be89fb8..690dec6d 100644 --- a/packages/api-main/src/posts/flag.ts +++ b/packages/api-main/src/posts/flag.ts @@ -1,74 +1,73 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable, FlagsTable } from '../../drizzle/schema'; +import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; +import { isReaderAuthorizationValid } from '../utility'; const sharedQueries = useSharedQueries(); -import { notify } from '../shared/notify'; -import { isReaderAuthorizationValid } from '../utility'; const statement = getDatabase() - .insert(FlagsTable) - .values({ - post_hash: sql.placeholder('post_hash'), - hash: sql.placeholder('hash'), - author: sql.placeholder('author'), - quantity: sql.placeholder('quantity'), - timestamp: sql.placeholder('timestamp'), - }) - .onConflictDoNothing() - .prepare('stmnt_add_flag'); + .insert(FlagsTable) + .values({ + post_hash: sql.placeholder('post_hash'), + hash: sql.placeholder('hash'), + author: sql.placeholder('author'), + quantity: sql.placeholder('quantity'), + timestamp: sql.placeholder('timestamp'), + }) + .onConflictDoNothing() + .prepare('stmnt_add_flag'); const statementAddFlagToPost = getDatabase() - .update(FeedTable) - .set({ - flags: sql`${FeedTable.flags} + 1`, - flags_burnt: sql`(${FeedTable.flags_burnt})::int + ${sql.placeholder('quantity')}`, - }) - .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) - .prepare('stmnt_add_flag_count_to_post'); + .update(FeedTable) + .set({ + flags: sql`${FeedTable.flags} + 1`, + flags_burnt: sql`(${FeedTable.flags_burnt})::int + ${sql.placeholder('quantity')}`, + }) + .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) + .prepare('stmnt_add_flag_count_to_post'); export async function Flag(body: typeof Posts.FlagBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - if (body.post_hash.length !== 64) { - return { status: 400, error: 'Provided post_hash is not valid for flag' }; - } - - try { - const result = await sharedQueries.doesPostExist(body.post_hash); - if (result.status !== 200) { - return { status: result.status, error: 'provided post_hash does not exist' }; - } + if (body.post_hash.length !== 64) { + return { status: 400, error: 'Provided post_hash is not valid for flag' }; + } - const resultChanges = await statement.execute({ - post_hash: body.post_hash.toLowerCase(), - hash: body.hash.toLowerCase(), - author: body.from.toLowerCase(), - quantity: body.quantity, - timestamp: new Date(body.timestamp), - }); + try { + const result = await sharedQueries.doesPostExist(body.post_hash); + if (result.status !== 200) { + return { status: result.status, error: 'provided post_hash does not exist' }; + } - // Ensure that 'flag' was triggered, and not already existing. - if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { - await statementAddFlagToPost.execute({ post_hash: body.post_hash, quantity: body.quantity }); - await notify({ - post_hash: body.post_hash, - hash: body.hash, - type: 'flag', - timestamp: new Date(body.timestamp), - actor: body.from, - }); - } + const resultChanges = await statement.execute({ + post_hash: body.post_hash.toLowerCase(), + hash: body.hash.toLowerCase(), + author: body.from.toLowerCase(), + quantity: body.quantity, + timestamp: new Date(body.timestamp), + }); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to upsert data for flag, flag already exists' }; + // Ensure that 'flag' was triggered, and not already existing. + if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { + await statementAddFlagToPost.execute({ post_hash: body.post_hash, quantity: body.quantity }); + await notify({ + post_hash: body.post_hash, + hash: body.hash, + type: 'flag', + timestamp: new Date(body.timestamp), + actor: body.from, + }); } + + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to upsert data for flag, flag already exists' }; + } } diff --git a/packages/api-main/src/posts/follow.ts b/packages/api-main/src/posts/follow.ts index 3c9f08db..593eb18d 100644 --- a/packages/api-main/src/posts/follow.ts +++ b/packages/api-main/src/posts/follow.ts @@ -1,4 +1,4 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { and, eq, isNotNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -7,75 +7,74 @@ import { notify } from '../shared/notify'; import { isReaderAuthorizationValid } from '../utility'; const statementAddFollower = getDatabase() - .insert(FollowsTable) - .values({ - follower: sql.placeholder('follower'), - following: sql.placeholder('following'), - hash: sql.placeholder('hash'), - timestamp: sql.placeholder('timestamp'), - }) - .onConflictDoNothing() - .prepare('stmnt_add_follower'); + .insert(FollowsTable) + .values({ + follower: sql.placeholder('follower'), + following: sql.placeholder('following'), + hash: sql.placeholder('hash'), + timestamp: sql.placeholder('timestamp'), + }) + .onConflictDoNothing() + .prepare('stmnt_add_follower'); export async function Follow(body: typeof Posts.FollowBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } - - if (body.hash.length !== 64) { - return { status: 400, error: 'Provided hash is not valid for follow' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - if (body.address.length !== 44) { - return { status: 400, error: 'Provided address is not valid for follow' }; - } + if (body.hash.length !== 64) { + return { status: 400, error: 'Provided hash is not valid for follow' }; + } - if (body.from.length !== 44) { - return { status: 400, error: 'Provided from address is not valid for follow' }; - } + if (body.address.length !== 44) { + return { status: 400, error: 'Provided address is not valid for follow' }; + } - if (body.from === body.address) { - return { status: 400, error: 'Provided from address cannot equal provided address, cannot follow self' }; - } + if (body.from.length !== 44) { + return { status: 400, error: 'Provided from address is not valid for follow' }; + } - try { - let result = await statementAddFollower.execute({ - follower: body.from.toLowerCase(), - following: body.address.toLowerCase(), - hash: body.hash.toLowerCase(), - timestamp: new Date(body.timestamp), - }); + if (body.from === body.address) { + return { status: 400, error: 'Provided from address cannot equal provided address, cannot follow self' }; + } - if (typeof result.rowCount !== 'number' || result.rowCount <= 0) { - // Attempts to add the follower because the entry already exists; and may be null - result = await getDatabase().update(FollowsTable).set( - { removed_at: null, hash: body.hash.toLowerCase() }).where( - and( - eq(FollowsTable.follower, body.from.toLowerCase()), - eq(FollowsTable.following, body.address.toLowerCase()), - isNotNull(FollowsTable.removed_at), - ), - ); + try { + let result = await statementAddFollower.execute({ + follower: body.from.toLowerCase(), + following: body.address.toLowerCase(), + hash: body.hash.toLowerCase(), + timestamp: new Date(body.timestamp), + }); - if (typeof result.rowCount !== 'number' || result.rowCount <= 0) { - return { status: 400, error: 'failed to add follow, follow already exists' }; - } - } - else { - // Only allow for notification of a new follower once, forever - await notify({ - owner: body.address, - hash: body.hash, - type: 'follow', - timestamp: new Date(body.timestamp), - actor: body.from, - }); - } + if (typeof result.rowCount !== 'number' || result.rowCount <= 0) { + // Attempts to add the follower because the entry already exists; and may be null + result = await getDatabase().update(FollowsTable).set( + { removed_at: null, hash: body.hash.toLowerCase() }, + ).where( + and( + eq(FollowsTable.follower, body.from.toLowerCase()), + eq(FollowsTable.following, body.address.toLowerCase()), + isNotNull(FollowsTable.removed_at), + ), + ); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to communicate with database' }; + if (typeof result.rowCount !== 'number' || result.rowCount <= 0) { + return { status: 400, error: 'failed to add follow, follow already exists' }; + } + } else { + // Only allow for notification of a new follower once, forever + await notify({ + owner: body.address, + hash: body.hash, + type: 'follow', + timestamp: new Date(body.timestamp), + actor: body.from, + }); } + + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to communicate with database' }; + } } diff --git a/packages/api-main/src/posts/like.ts b/packages/api-main/src/posts/like.ts index a25c24a3..0871645f 100644 --- a/packages/api-main/src/posts/like.ts +++ b/packages/api-main/src/posts/like.ts @@ -1,75 +1,74 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable, LikesTable } from '../../drizzle/schema'; +import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; +import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); -import { notify } from '../shared/notify'; -import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const statement = getDatabase() - .insert(LikesTable) - .values({ - post_hash: sql.placeholder('post_hash'), - hash: sql.placeholder('hash'), - author: sql.placeholder('author'), - quantity: sql.placeholder('quantity'), - timestamp: sql.placeholder('timestamp'), - }) - .onConflictDoNothing() - .prepare('stmnt_add_like'); + .insert(LikesTable) + .values({ + post_hash: sql.placeholder('post_hash'), + hash: sql.placeholder('hash'), + author: sql.placeholder('author'), + quantity: sql.placeholder('quantity'), + timestamp: sql.placeholder('timestamp'), + }) + .onConflictDoNothing() + .prepare('stmnt_add_like'); const statementAddLikeToPost = getDatabase() - .update(FeedTable) - .set({ - likes: sql`${FeedTable.likes} + 1`, - likes_burnt: sql`(${FeedTable.likes_burnt})::int + ${sql.placeholder('quantity')}`, - }) - .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) - .prepare('stmnt_add_like_count_to_post'); + .update(FeedTable) + .set({ + likes: sql`${FeedTable.likes} + 1`, + likes_burnt: sql`(${FeedTable.likes_burnt})::int + ${sql.placeholder('quantity')}`, + }) + .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) + .prepare('stmnt_add_like_count_to_post'); export async function Like(body: typeof Posts.LikeBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - if (body.post_hash.length !== 64) { - return { status: 400, error: 'Provided post_hash is not valid for like' }; - } - - try { - const result = await sharedQueries.doesPostExist(body.post_hash); - if (result.status !== 200) { - return { status: result.status, error: 'provided post_hash does not exist' }; - } + if (body.post_hash.length !== 64) { + return { status: 400, error: 'Provided post_hash is not valid for like' }; + } - const resultChanges = await statement.execute({ - post_hash: body.post_hash.toLowerCase(), - hash: body.hash.toLowerCase(), - author: body.from.toLowerCase(), - quantity: body.quantity, - timestamp: new Date(body.timestamp), - }); + try { + const result = await sharedQueries.doesPostExist(body.post_hash); + if (result.status !== 200) { + return { status: result.status, error: 'provided post_hash does not exist' }; + } - // Ensure that 'like' was triggered, and not already existing. - if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { - await statementAddLikeToPost.execute({ post_hash: body.post_hash, quantity: body.quantity }); - await notify({ - post_hash: body.post_hash, - hash: body.hash, - type: 'like', - timestamp: new Date(body.timestamp), - actor: body.from, - }); - } + const resultChanges = await statement.execute({ + post_hash: body.post_hash.toLowerCase(), + hash: body.hash.toLowerCase(), + author: body.from.toLowerCase(), + quantity: body.quantity, + timestamp: new Date(body.timestamp), + }); - await postToDiscord(`Liked by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to upsert data for like, like already exists' }; + // Ensure that 'like' was triggered, and not already existing. + if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { + await statementAddLikeToPost.execute({ post_hash: body.post_hash, quantity: body.quantity }); + await notify({ + post_hash: body.post_hash, + hash: body.hash, + type: 'like', + timestamp: new Date(body.timestamp), + actor: body.from, + }); } + + await postToDiscord(`Liked by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to upsert data for like, like already exists' }; + } } diff --git a/packages/api-main/src/posts/logout.ts b/packages/api-main/src/posts/logout.ts index 8fe6781d..902538a9 100644 --- a/packages/api-main/src/posts/logout.ts +++ b/packages/api-main/src/posts/logout.ts @@ -1,12 +1,11 @@ import type { Cookie } from 'elysia'; export async function Logout(auth: Cookie) { - try { - auth.remove(); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'unauthorized signature or key provided, failed to verify' }; - } + try { + auth.remove(); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'unauthorized signature or key provided, failed to verify' }; + } } diff --git a/packages/api-main/src/posts/mod.ts b/packages/api-main/src/posts/mod.ts index fc686033..576d7fb0 100644 --- a/packages/api-main/src/posts/mod.ts +++ b/packages/api-main/src/posts/mod.ts @@ -1,6 +1,6 @@ -import type { Cookie } from 'elysia'; +import type { Posts } from '@atomone/dither-api-types'; -import { type Posts } from '@atomone/dither-api-types'; +import type { Cookie } from 'elysia'; import { and, eq, isNull, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -8,232 +8,228 @@ import { AuditTable, FeedTable, ModeratorTable } from '../../drizzle/schema'; import { verifyJWT } from '../shared/jwt'; const statementAuditRemovePost = getDatabase() - .insert(AuditTable) - .values({ - post_hash: sql.placeholder('post_hash'), - hash: sql.placeholder('hash'), - created_by: sql.placeholder('created_by'), - created_at: sql.placeholder('created_at'), - reason: sql.placeholder('reason'), - }) - .prepare('stmnt_audit_remove_post'); + .insert(AuditTable) + .values({ + post_hash: sql.placeholder('post_hash'), + hash: sql.placeholder('hash'), + created_by: sql.placeholder('created_by'), + created_at: sql.placeholder('created_at'), + reason: sql.placeholder('reason'), + }) + .prepare('stmnt_audit_remove_post'); export async function ModRemovePost(body: typeof Posts.ModRemovePostBody.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } + + try { + const [mod] = await getDatabase() + .select() + .from(ModeratorTable) + .where(eq(ModeratorTable.address, response)) + .limit(1); + if (!mod) { + return { status: 404, error: 'moderator not found' }; } - try { - const [mod] = await getDatabase() - .select() - .from(ModeratorTable) - .where(eq(ModeratorTable.address, response)) - .limit(1); - if (!mod) { - return { status: 404, error: 'moderator not found' }; - } - - const [post] = await getDatabase().select().from(FeedTable).where(eq(FeedTable.hash, body.post_hash)).limit(1); - if (!post) { - return { status: 404, error: 'post not found' }; - } - - const statement = getDatabase() - .update(FeedTable) - .set({ - removed_at: new Date(body.timestamp), - removed_hash: body.hash.toLowerCase(), - removed_by: mod.address.toLowerCase(), - }) - .where(eq(FeedTable.hash, body.post_hash)) - .returning(); - - await statement.execute(); - - await statementAuditRemovePost.execute({ - post_hash: body.post_hash.toLowerCase(), - hash: body.hash.toLowerCase(), - created_by: mod.address.toLowerCase(), - created_at: new Date(body.timestamp), - reason: body.reason, - }); - - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to delete post' }; + const [post] = await getDatabase().select().from(FeedTable).where(eq(FeedTable.hash, body.post_hash)).limit(1); + if (!post) { + return { status: 404, error: 'post not found' }; } + + const statement = getDatabase() + .update(FeedTable) + .set({ + removed_at: new Date(body.timestamp), + removed_hash: body.hash.toLowerCase(), + removed_by: mod.address.toLowerCase(), + }) + .where(eq(FeedTable.hash, body.post_hash)) + .returning(); + + await statement.execute(); + + await statementAuditRemovePost.execute({ + post_hash: body.post_hash.toLowerCase(), + hash: body.hash.toLowerCase(), + created_by: mod.address.toLowerCase(), + created_at: new Date(body.timestamp), + reason: body.reason, + }); + + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to delete post' }; + } } const statementAuditRestorePost = getDatabase() - .insert(AuditTable) - .values({ - post_hash: sql.placeholder('post_hash'), - hash: sql.placeholder('hash'), - restored_at: sql.placeholder('restored_at'), - restored_by: sql.placeholder('restored_by'), - reason: sql.placeholder('reason'), - }) - .prepare('stmnt_audit_restore_post'); + .insert(AuditTable) + .values({ + post_hash: sql.placeholder('post_hash'), + hash: sql.placeholder('hash'), + restored_at: sql.placeholder('restored_at'), + restored_by: sql.placeholder('restored_by'), + reason: sql.placeholder('reason'), + }) + .prepare('stmnt_audit_restore_post'); export async function ModRestorePost(body: typeof Posts.ModRemovePostBody.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } + + try { + const [mod] = await getDatabase() + .select() + .from(ModeratorTable) + .where(eq(ModeratorTable.address, response)) + .limit(1); + if (!mod) { + return { status: 404, error: 'moderator not found' }; + } + + const [post] = await getDatabase().select().from(FeedTable).where(eq(FeedTable.hash, body.post_hash)).limit(1); + if (!post) { + return { status: 404, error: 'post not found' }; } - try { - const [mod] = await getDatabase() - .select() - .from(ModeratorTable) - .where(eq(ModeratorTable.address, response)) - .limit(1); - if (!mod) { - return { status: 404, error: 'moderator not found' }; - } - - const [post] = await getDatabase().select().from(FeedTable).where(eq(FeedTable.hash, body.post_hash)).limit(1); - if (!post) { - return { status: 404, error: 'post not found' }; - } - - if (!post.removed_at) { - return { status: 404, error: 'post not removed' }; - } - - const [postWasRemovedByMod] = await getDatabase() - .select() - .from(ModeratorTable) - .where(eq(ModeratorTable.address, post.removed_by ?? '')) - .limit(1); - if (!postWasRemovedByMod) { - return { status: 401, error: 'cannot restore a post removed by the user' }; - } - - const statement = getDatabase() - .update(FeedTable) - .set({ - removed_at: null, - removed_hash: null, - removed_by: null, - }) - .where(eq(FeedTable.hash, body.post_hash)) - .returning(); - - await statement.execute(); - - await statementAuditRestorePost.execute({ - post_hash: body.post_hash.toLowerCase(), - hash: body.hash.toLowerCase(), - restored_by: mod.address.toLowerCase(), - restored_at: new Date(body.timestamp), - reason: body.reason, - }); - - return { status: 200 }; + if (!post.removed_at) { + return { status: 404, error: 'post not removed' }; } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to delete post, maybe invalid' }; + + const [postWasRemovedByMod] = await getDatabase() + .select() + .from(ModeratorTable) + .where(eq(ModeratorTable.address, post.removed_by ?? '')) + .limit(1); + if (!postWasRemovedByMod) { + return { status: 401, error: 'cannot restore a post removed by the user' }; } + + const statement = getDatabase() + .update(FeedTable) + .set({ + removed_at: null, + removed_hash: null, + removed_by: null, + }) + .where(eq(FeedTable.hash, body.post_hash)) + .returning(); + + await statement.execute(); + + await statementAuditRestorePost.execute({ + post_hash: body.post_hash.toLowerCase(), + hash: body.hash.toLowerCase(), + restored_by: mod.address.toLowerCase(), + restored_at: new Date(body.timestamp), + reason: body.reason, + }); + + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to delete post, maybe invalid' }; + } } const statementAuditBanUser = getDatabase() - .insert(AuditTable) - .values({ - user_address: sql.placeholder('user_address'), - hash: sql.placeholder('hash'), - created_at: sql.placeholder('created_at'), - created_by: sql.placeholder('created_by'), - reason: sql.placeholder('reason'), - }) - .prepare('stmnt_audit_ban_user'); + .insert(AuditTable) + .values({ + user_address: sql.placeholder('user_address'), + hash: sql.placeholder('hash'), + created_at: sql.placeholder('created_at'), + created_by: sql.placeholder('created_by'), + reason: sql.placeholder('reason'), + }) + .prepare('stmnt_audit_ban_user'); export async function ModBan(body: typeof Posts.ModBanBody.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } + + try { + const [mod] = await getDatabase() + .select() + .from(ModeratorTable) + .where(eq(ModeratorTable.address, response)) + .limit(1); + if (!mod) { + return { status: 404, error: 'moderator not found' }; } - try { - const [mod] = await getDatabase() - .select() - .from(ModeratorTable) - .where(eq(ModeratorTable.address, response)) - .limit(1); - if (!mod) { - return { status: 404, error: 'moderator not found' }; - } - - const statement = getDatabase() - .update(FeedTable) - .set({ - removed_at: new Date(body.timestamp), - removed_hash: body.hash.toLowerCase(), - removed_by: mod.address.toLowerCase(), - }) - .where(and(eq(FeedTable.author, body.user_address), isNull(FeedTable.removed_at))) - .returning(); - - await statement.execute(); - - await statementAuditBanUser.execute({ - user_address: body.user_address.toLowerCase(), - hash: body.hash.toLowerCase(), - created_by: mod.address.toLowerCase(), - created_at: new Date(body.timestamp), - reason: body.reason, - }); - - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to ban user' }; - } + const statement = getDatabase() + .update(FeedTable) + .set({ + removed_at: new Date(body.timestamp), + removed_hash: body.hash.toLowerCase(), + removed_by: mod.address.toLowerCase(), + }) + .where(and(eq(FeedTable.author, body.user_address), isNull(FeedTable.removed_at))) + .returning(); + + await statement.execute(); + + await statementAuditBanUser.execute({ + user_address: body.user_address.toLowerCase(), + hash: body.hash.toLowerCase(), + created_by: mod.address.toLowerCase(), + created_at: new Date(body.timestamp), + reason: body.reason, + }); + + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to ban user' }; + } } const statementAuditUnbanUser = getDatabase() - .insert(AuditTable) - .values({ - user_address: sql.placeholder('user_address'), - hash: sql.placeholder('hash'), - restored_at: sql.placeholder('restored_at'), - restored_by: sql.placeholder('restored_by'), - reason: sql.placeholder('reason'), - }) - .prepare('stmnt_audit_unban_user'); + .insert(AuditTable) + .values({ + user_address: sql.placeholder('user_address'), + hash: sql.placeholder('hash'), + restored_at: sql.placeholder('restored_at'), + restored_by: sql.placeholder('restored_by'), + reason: sql.placeholder('reason'), + }) + .prepare('stmnt_audit_unban_user'); export async function ModUnban(body: typeof Posts.ModBanBody.static, auth: Cookie) { - const response = await verifyJWT(auth.value); - if (typeof response === 'undefined') { - return { status: 401, error: 'Unauthorized token proivided' }; + const response = await verifyJWT(auth.value); + if (typeof response === 'undefined') { + return { status: 401, error: 'Unauthorized token proivided' }; + } + + try { + const [mod] = await getDatabase() + .select() + .from(ModeratorTable) + .where(eq(ModeratorTable.address, response)) + .limit(1); + if (!mod) { + return { status: 404, error: 'moderator not found' }; } - try { - const [mod] = await getDatabase() - .select() - .from(ModeratorTable) - .where(eq(ModeratorTable.address, response)) - .limit(1); - if (!mod) { - return { status: 404, error: 'moderator not found' }; - } - - await statementAuditUnbanUser.execute({ - user_address: body.user_address.toLowerCase(), - hash: body.hash.toLowerCase(), - restored_by: mod.address.toLowerCase(), - restored_at: new Date(body.timestamp), - reason: body.reason, - }); - - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to unban user' }; - } + await statementAuditUnbanUser.execute({ + user_address: body.user_address.toLowerCase(), + hash: body.hash.toLowerCase(), + restored_by: mod.address.toLowerCase(), + restored_at: new Date(body.timestamp), + reason: body.reason, + }); + + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to unban user' }; + } } diff --git a/packages/api-main/src/posts/post.ts b/packages/api-main/src/posts/post.ts index f8f0a083..b78f8aab 100644 --- a/packages/api-main/src/posts/post.ts +++ b/packages/api-main/src/posts/post.ts @@ -1,4 +1,4 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { desc, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -6,69 +6,68 @@ import { AuditTable, FeedTable } from '../../drizzle/schema'; import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const statement = getDatabase() - .insert(FeedTable) - .values({ - hash: sql.placeholder('hash'), - timestamp: sql.placeholder('timestamp'), - author: sql.placeholder('author'), - message: sql.placeholder('message'), - quantity: sql.placeholder('quantity'), - }) - .onConflictDoNothing() - .prepare('stmnt_post'); + .insert(FeedTable) + .values({ + hash: sql.placeholder('hash'), + timestamp: sql.placeholder('timestamp'), + author: sql.placeholder('author'), + message: sql.placeholder('message'), + quantity: sql.placeholder('quantity'), + }) + .onConflictDoNothing() + .prepare('stmnt_post'); export async function Post(body: typeof Posts.PostBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - try { - if (body.msg.length >= 512) { - return { status: 400, error: 'message is too long' }; - } + try { + if (body.msg.length >= 512) { + return { status: 400, error: 'message is too long' }; + } - await statement.execute({ - hash: body.hash.toLowerCase(), - timestamp: new Date(body.timestamp), - author: body.from.toLowerCase(), - message: body.msg, - quantity: body.quantity, - }); + await statement.execute({ + hash: body.hash.toLowerCase(), + timestamp: new Date(body.timestamp), + author: body.from.toLowerCase(), + message: body.msg, + quantity: body.quantity, + }); - await removePostIfBanned(body); + await removePostIfBanned(body); - await postToDiscord(body.msg, `https://dither.chat/post/${body.hash.toLowerCase()}`); + await postToDiscord(body.msg, `https://dither.chat/post/${body.hash.toLowerCase()}`); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to upsert data for post' }; - } + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to upsert data for post' }; + } } async function removePostIfBanned(body: typeof Posts.PostBody.static) { - const [lastAuditOnUser] = await getDatabase() - .select() - .from(AuditTable) - .where(eq(AuditTable.user_address, body.from)) - .orderBy(desc(AuditTable.created_at)) - .limit(1); + const [lastAuditOnUser] = await getDatabase() + .select() + .from(AuditTable) + .where(eq(AuditTable.user_address, body.from)) + .orderBy(desc(AuditTable.created_at)) + .limit(1); // If there are not action over the user of they were restored (unbanned), do nothing - if (!lastAuditOnUser || lastAuditOnUser.restored_at) { - return; - } + if (!lastAuditOnUser || lastAuditOnUser.restored_at) { + return; + } - const statement = getDatabase() - .update(FeedTable) - .set({ - removed_at: new Date(body.timestamp), - removed_by: lastAuditOnUser.created_by, - }) - .where(eq(FeedTable.hash, body.hash)) - .returning(); + const statement = getDatabase() + .update(FeedTable) + .set({ + removed_at: new Date(body.timestamp), + removed_by: lastAuditOnUser.created_by, + }) + .where(eq(FeedTable.hash, body.hash)) + .returning(); - await statement.execute(); + await statement.execute(); - return lastAuditOnUser; + return lastAuditOnUser; } diff --git a/packages/api-main/src/posts/postRemove.ts b/packages/api-main/src/posts/postRemove.ts index 4433b684..2305aa9f 100644 --- a/packages/api-main/src/posts/postRemove.ts +++ b/packages/api-main/src/posts/postRemove.ts @@ -1,4 +1,4 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { and, eq } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -6,37 +6,36 @@ import { FeedTable } from '../../drizzle/schema'; import { isReaderAuthorizationValid } from '../utility'; export async function PostRemove(body: typeof Posts.PostRemoveBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - try { - const selectResults = await getDatabase() - .select() - .from(FeedTable) - .where(and(eq(FeedTable.hash, body.post_hash), eq(FeedTable.author, body.from))); + try { + const selectResults = await getDatabase() + .select() + .from(FeedTable) + .where(and(eq(FeedTable.hash, body.post_hash), eq(FeedTable.author, body.from))); - const hasOwnership = selectResults.length >= 1; - if (!hasOwnership) { - return { status: 200, error: 'did not have ownership for post removal, ignored removal' }; - } + const hasOwnership = selectResults.length >= 1; + if (!hasOwnership) { + return { status: 200, error: 'did not have ownership for post removal, ignored removal' }; + } - const statement = getDatabase() - .update(FeedTable) - .set({ - removed_at: new Date(body.timestamp), - removed_hash: body.hash.toLowerCase(), - removed_by: body.from.toLowerCase(), - }) - .where(eq(FeedTable.hash, body.post_hash)) - .returning(); + const statement = getDatabase() + .update(FeedTable) + .set({ + removed_at: new Date(body.timestamp), + removed_hash: body.hash.toLowerCase(), + removed_by: body.from.toLowerCase(), + }) + .where(eq(FeedTable.hash, body.post_hash)) + .returning(); - await statement.execute(); + await statement.execute(); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to delete post, maybe invalid' }; - } + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to delete post, maybe invalid' }; + } } diff --git a/packages/api-main/src/posts/reply.ts b/packages/api-main/src/posts/reply.ts index f96d496d..51fe0896 100644 --- a/packages/api-main/src/posts/reply.ts +++ b/packages/api-main/src/posts/reply.ts @@ -1,77 +1,76 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { FeedTable } from '../../drizzle/schema'; +import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; +import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); -import { notify } from '../shared/notify'; -import { isReaderAuthorizationValid, postToDiscord } from '../utility'; const statement = getDatabase() - .insert(FeedTable) - .values({ - author: sql.placeholder('author'), - hash: sql.placeholder('hash'), - message: sql.placeholder('message'), - post_hash: sql.placeholder('post_hash'), - quantity: sql.placeholder('quantity'), - timestamp: sql.placeholder('timestamp'), - }) - .onConflictDoNothing() - .prepare('stmnt_reply'); + .insert(FeedTable) + .values({ + author: sql.placeholder('author'), + hash: sql.placeholder('hash'), + message: sql.placeholder('message'), + post_hash: sql.placeholder('post_hash'), + quantity: sql.placeholder('quantity'), + timestamp: sql.placeholder('timestamp'), + }) + .onConflictDoNothing() + .prepare('stmnt_reply'); const statementAddReplyCount = getDatabase() - .update(FeedTable) - .set({ replies: sql`${FeedTable.replies} + 1` }) - .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) - .prepare('stmnt_add_reply_count'); + .update(FeedTable) + .set({ replies: sql`${FeedTable.replies} + 1` }) + .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) + .prepare('stmnt_add_reply_count'); export async function Reply(body: typeof Posts.ReplyBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - if (body.post_hash.length !== 64) { - return { status: 400, error: 'Provided post_hash is not valid for reply' }; - } - - try { - const result = await sharedQueries.doesPostExist(body.post_hash); - if (result.status !== 200) { - return { status: result.status, error: 'provided post_hash does not exist' }; - } + if (body.post_hash.length !== 64) { + return { status: 400, error: 'Provided post_hash is not valid for reply' }; + } - const resultChanges = await statement.execute({ - author: body.from.toLowerCase(), - hash: body.hash.toLowerCase(), - message: body.msg, - post_hash: body.post_hash.toLowerCase(), - quantity: body.quantity, - timestamp: new Date(body.timestamp), - }); + try { + const result = await sharedQueries.doesPostExist(body.post_hash); + if (result.status !== 200) { + return { status: result.status, error: 'provided post_hash does not exist' }; + } - if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { - await statementAddReplyCount.execute({ - post_hash: body.post_hash, - }); + const resultChanges = await statement.execute({ + author: body.from.toLowerCase(), + hash: body.hash.toLowerCase(), + message: body.msg, + post_hash: body.post_hash.toLowerCase(), + quantity: body.quantity, + timestamp: new Date(body.timestamp), + }); - await notify({ - post_hash: body.post_hash, - hash: body.hash, - type: 'reply', - timestamp: new Date(body.timestamp), - subcontext: body.msg.length >= 61 ? body.msg.slice(0, 61) + '...' : body.msg, - actor: body.from, - }); - } + if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { + await statementAddReplyCount.execute({ + post_hash: body.post_hash, + }); - await postToDiscord(`${body.msg}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 400, error: 'failed to upsert data for post' }; + await notify({ + post_hash: body.post_hash, + hash: body.hash, + type: 'reply', + timestamp: new Date(body.timestamp), + subcontext: body.msg.length >= 61 ? `${body.msg.slice(0, 61)}...` : body.msg, + actor: body.from, + }); } + + await postToDiscord(`${body.msg}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to upsert data for post' }; + } } diff --git a/packages/api-main/src/posts/unfollow.ts b/packages/api-main/src/posts/unfollow.ts index fa5f3912..a0cd3585 100644 --- a/packages/api-main/src/posts/unfollow.ts +++ b/packages/api-main/src/posts/unfollow.ts @@ -1,4 +1,4 @@ -import { type Posts } from '@atomone/dither-api-types'; +import type { Posts } from '@atomone/dither-api-types'; import { and, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; @@ -6,27 +6,26 @@ import { FollowsTable } from '../../drizzle/schema'; import { isReaderAuthorizationValid } from '../utility'; const statementRemoveFollowing = getDatabase() - .update(FollowsTable) - .set({ removed_at: sql.placeholder('removed_at') as never }) // Drizzle Type Issue atm. - .where( - and( - eq(FollowsTable.follower, sql.placeholder('follower')), - eq(FollowsTable.following, sql.placeholder('following')), - ), - ) - .prepare('stmnt_remove_follower'); + .update(FollowsTable) + .set({ removed_at: sql.placeholder('removed_at') as never }) // Drizzle Type Issue atm. + .where( + and( + eq(FollowsTable.follower, sql.placeholder('follower')), + eq(FollowsTable.following, sql.placeholder('following')), + ), + ) + .prepare('stmnt_remove_follower'); export async function Unfollow(body: typeof Posts.UnfollowBody.static, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - try { - await statementRemoveFollowing.execute({ follower: body.from.toLowerCase(), following: body.address.toLowerCase(), removed_at: new Date(body.timestamp) }); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 200, error: 'failed to unfollow user, user may not exist' }; - } + try { + await statementRemoveFollowing.execute({ follower: body.from.toLowerCase(), following: body.address.toLowerCase(), removed_at: new Date(body.timestamp) }); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 200, error: 'failed to unfollow user, user may not exist' }; + } } diff --git a/packages/api-main/src/posts/updateState.ts b/packages/api-main/src/posts/updateState.ts index b7189834..11917240 100644 --- a/packages/api-main/src/posts/updateState.ts +++ b/packages/api-main/src/posts/updateState.ts @@ -3,26 +3,26 @@ import { ReaderState } from '../../drizzle/schema'; import { isReaderAuthorizationValid } from '../utility'; export async function UpdateState(body: { last_block: string }, headers: Record) { - if (!isReaderAuthorizationValid(headers)) { - return { status: 401, error: 'Unauthorized to make write request' }; - } + if (!isReaderAuthorizationValid(headers)) { + return { status: 401, error: 'Unauthorized to make write request' }; + } - try { - await getDatabase() - .insert(ReaderState) - .values({ id: 0, last_block: body.last_block }) - .onConflictDoUpdate({ - target: ReaderState.id, - set: { - last_block: body.last_block, - }, - }).execute(); + try { + await getDatabase() + .insert(ReaderState) + .values({ id: 0, last_block: body.last_block }) + .onConflictDoUpdate({ + target: ReaderState.id, + set: { + last_block: body.last_block, + }, + }) + .execute(); - console.info(`Last Block Updated: ${body.last_block}`); - return { status: 200 }; - } - catch (err) { - console.error(err); - return { status: 500, error: 'failed to write to database' }; - } + console.info(`Last Block Updated: ${body.last_block}`); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 500, error: 'failed to write to database' }; + } } diff --git a/packages/api-main/src/shared/jwt.ts b/packages/api-main/src/shared/jwt.ts index e50d8927..174145bf 100644 --- a/packages/api-main/src/shared/jwt.ts +++ b/packages/api-main/src/shared/jwt.ts @@ -4,24 +4,23 @@ import { useConfig } from '../config'; const { JWT } = useConfig(); -export const verifyJWT = async (token: string | undefined) => { - if (!token) { - return undefined; - } - - try { - const tokenData = jwt.verify(token, JWT, { algorithms: ['HS256'], maxAge: '3d' }) as { data: string; iat: number; exp: number }; - if (!tokenData) { - return undefined; - } +export async function verifyJWT(token: string | undefined) { + if (!token) { + return undefined; + } - // token data is on the form Login,id,date,publicKey,nonce - // so to obtain the user address we need to split on the comma - // and take the 4th element - return tokenData.data.split(',')[2]; // Returns user address + try { + const tokenData = jwt.verify(token, JWT, { algorithms: ['HS256'], maxAge: '3d' }) as { data: string; iat: number; exp: number }; + if (!tokenData) { + return undefined; } - catch (err) { - console.error(err); - return undefined; - } -}; + + // token data is on the form Login,id,date,publicKey,nonce + // so to obtain the user address we need to split on the comma + // and take the 4th element + return tokenData.data.split(',')[2]; // Returns user address + } catch (err) { + console.error(err); + return undefined; + } +} diff --git a/packages/api-main/src/shared/notify.ts b/packages/api-main/src/shared/notify.ts index 4e06f3c2..1899c5d0 100644 --- a/packages/api-main/src/shared/notify.ts +++ b/packages/api-main/src/shared/notify.ts @@ -4,60 +4,60 @@ import { getDatabase } from '../../drizzle/db'; import { FeedTable, NotificationTable } from '../../drizzle/schema'; const statementInsertNotification = getDatabase() - .insert(NotificationTable) - .values({ - owner: sql.placeholder('owner'), - hash: sql.placeholder('hash'), - post_hash: sql.placeholder('post_hash'), - type: sql.placeholder('type'), - timestamp: sql.placeholder('timestamp'), - subcontext: sql.placeholder('subcontext'), - actor: sql.placeholder('actor'), - }) - .onConflictDoNothing() - .prepare('stmnt_insert_notification'); + .insert(NotificationTable) + .values({ + owner: sql.placeholder('owner'), + hash: sql.placeholder('hash'), + post_hash: sql.placeholder('post_hash'), + type: sql.placeholder('type'), + timestamp: sql.placeholder('timestamp'), + subcontext: sql.placeholder('subcontext'), + actor: sql.placeholder('actor'), + }) + .onConflictDoNothing() + .prepare('stmnt_insert_notification'); const statementGetTargetPost = getDatabase().select().from(FeedTable).where(eq(FeedTable.hash, sql.placeholder('post_hash'))).limit(1).prepare('stmnt_fetch_target_post'); -export const notify = async (data: { - hash: string; - type: string; - timestamp: Date; - actor: string; - subcontext?: string; - post_hash?: string; - owner?: string; -}) => { - let owner = data.owner; - - if (data.post_hash) { - const [post] = await statementGetTargetPost.execute({ post_hash: data.post_hash?.toLowerCase() }); - if (!post) { - throw new Error('post not found'); - } - owner ??= post.author; - - if (!data.subcontext) { - const subcontext = post.message.length >= 64 ? post.message.slice(0, 61) + '...' : post.message; - data.subcontext = subcontext; - } +export async function notify(data: { + hash: string; + type: string; + timestamp: Date; + actor: string; + subcontext?: string; + post_hash?: string; + owner?: string; +}) { + let owner = data.owner; + + if (data.post_hash) { + const [post] = await statementGetTargetPost.execute({ post_hash: data.post_hash?.toLowerCase() }); + if (!post) { + throw new Error('post not found'); } + owner ??= post.author; - if (!owner) { - throw new Error('failed to add owner'); + if (!data.subcontext) { + const subcontext = post.message.length >= 64 ? `${post.message.slice(0, 61)}...` : post.message; + data.subcontext = subcontext; } - - if (owner === data.actor) { - return; - } - - await statementInsertNotification.execute({ - owner: owner.toLowerCase(), - post_hash: data.post_hash?.toLowerCase(), - hash: data.hash.toLowerCase(), - type: data.type, - timestamp: data.timestamp ?? null, - subcontext: data.subcontext, - actor: data.actor, - }); -}; + } + + if (!owner) { + throw new Error('failed to add owner'); + } + + if (owner === data.actor) { + return; + } + + await statementInsertNotification.execute({ + owner: owner.toLowerCase(), + post_hash: data.post_hash?.toLowerCase(), + hash: data.hash.toLowerCase(), + type: data.type, + timestamp: data.timestamp ?? null, + subcontext: data.subcontext, + actor: data.actor, + }); +} diff --git a/packages/api-main/src/shared/useRateLimiter.ts b/packages/api-main/src/shared/useRateLimiter.ts index 069c3166..dcc236a8 100644 --- a/packages/api-main/src/shared/useRateLimiter.ts +++ b/packages/api-main/src/shared/useRateLimiter.ts @@ -13,113 +13,110 @@ let cleanupInterval: NodeJS.Timeout | null = null; let isCleaningUp = false; async function cleanup() { - if (isCleaningUp) { - return; - } + if (isCleaningUp) { + return; + } - isCleaningUp = true; - try { - const now = Date.now(); - const threshold = now - MAX_REQUEST_TIME_MS; - const result = await getDatabase() - .delete(rateLimits) - .where( - lt(rateLimits.lastRequest, sql`${threshold}`), - ) - .execute(); + isCleaningUp = true; + try { + const now = Date.now(); + const threshold = now - MAX_REQUEST_TIME_MS; + const result = await getDatabase() + .delete(rateLimits) + .where( + lt(rateLimits.lastRequest, sql`${threshold}`), + ) + .execute(); - if (result.rowCount && result.rowCount > 0) { - console.log(`Cleaned Up Requests | Count: ${result.rowCount}`); - } - } - catch (err) { - console.error('Error during database cleanup:', err); - } - finally { - isCleaningUp = false; + if (result.rowCount && result.rowCount > 0) { + console.log(`Cleaned Up Requests | Count: ${result.rowCount}`); } + } catch (err) { + console.error('Error during database cleanup:', err); + } finally { + isCleaningUp = false; + } } export function useRateLimiter() { - if (!cleanupInterval) { - cleanupInterval = setInterval(cleanup, TIME_BETWEEN_CLEANUP_MS); - // Ensure the process doesn't hang on this interval - if (cleanupInterval.unref) { - cleanupInterval.unref(); - } + if (!cleanupInterval) { + cleanupInterval = setInterval(cleanup, TIME_BETWEEN_CLEANUP_MS); + // Ensure the process doesn't hang on this interval + if (cleanupInterval.unref) { + cleanupInterval.unref(); } + } - /** - * Updates the request count for a given IP address. - * @param ip The IP address of the user. - */ - async function update(ip: string) { - const now = Date.now(); + /** + * Updates the request count for a given IP address. + * @param ip The IP address of the user. + */ + async function update(ip: string) { + const now = Date.now(); - // Use a transaction to ensure atomicity - await getDatabase().transaction(async (tx) => { - const existingRecord = await tx - .select() - .from(rateLimits) - .where(eq(rateLimits.ip, ip)) - .limit(1) - .execute(); + // Use a transaction to ensure atomicity + await getDatabase().transaction(async (tx) => { + const existingRecord = await tx + .select() + .from(rateLimits) + .where(eq(rateLimits.ip, ip)) + .limit(1) + .execute(); - if (existingRecord.length > 0) { - // If a record exists, update it. - await tx - .update(rateLimits) - .set({ - requests: sql`${rateLimits.requests} + 1`, - lastRequest: now, - }) - .where(eq(rateLimits.ip, ip)) - .execute(); - } - else { - // If no record exists, insert a new one. - await tx - .insert(rateLimits) - .values({ - ip: ip, - requests: 1, - lastRequest: now, - }) - .execute(); - } - }); - } - - /** - * Checks if a given IP address is limited. - * @param ip The IP address of the user. - * @returns True if the IP is limited, otherwise false. - */ - async function isLimited(ip: string): Promise { - const now = Date.now(); - const record = await getDatabase() - .select() - .from(rateLimits) - .where( - and( - eq(rateLimits.ip, ip), - gt(rateLimits.lastRequest, now - MAX_REQUEST_TIME_MS), - ), - ) - .limit(1) - .execute(); + if (existingRecord.length > 0) { + // If a record exists, update it. + await tx + .update(rateLimits) + .set({ + requests: sql`${rateLimits.requests} + 1`, + lastRequest: now, + }) + .where(eq(rateLimits.ip, ip)) + .execute(); + } else { + // If no record exists, insert a new one. + await tx + .insert(rateLimits) + .values({ + ip, + requests: 1, + lastRequest: now, + }) + .execute(); + } + }); + } - if (record.length === 0) { - // No record, so it's not limited - return false; - } + /** + * Checks if a given IP address is limited. + * @param ip The IP address of the user. + * @returns True if the IP is limited, otherwise false. + */ + async function isLimited(ip: string): Promise { + const now = Date.now(); + const record = await getDatabase() + .select() + .from(rateLimits) + .where( + and( + eq(rateLimits.ip, ip), + gt(rateLimits.lastRequest, now - MAX_REQUEST_TIME_MS), + ), + ) + .limit(1) + .execute(); - const { requests } = record[0]; - return requests >= MAX_REQUESTS; + if (record.length === 0) { + // No record, so it's not limited + return false; } - return { - update, - isLimited, - }; + const { requests } = record[0]; + return requests >= MAX_REQUESTS; + } + + return { + update, + isLimited, + }; } diff --git a/packages/api-main/src/shared/useSharedQueries.ts b/packages/api-main/src/shared/useSharedQueries.ts index 550893b8..660d12e6 100644 --- a/packages/api-main/src/shared/useSharedQueries.ts +++ b/packages/api-main/src/shared/useSharedQueries.ts @@ -6,18 +6,17 @@ import { FeedTable } from '../../drizzle/schema'; const doesPostExistStatement = getDatabase().select().from(FeedTable).where(eq(FeedTable.hash, sql.placeholder('post_hash'))).prepare('stmnt_get_post_by_hash'); export function useSharedQueries() { - const doesPostExist = async (post_hash: string) => { - try { - const results = await doesPostExistStatement.execute({ post_hash }); - return results.length >= 1 ? { status: 200 } : { status: 404 }; - } - catch (err) { - console.error(err); - return { status: 500 }; - } - }; + const doesPostExist = async (post_hash: string) => { + try { + const results = await doesPostExistStatement.execute({ post_hash }); + return results.length >= 1 ? { status: 200 } : { status: 404 }; + } catch (err) { + console.error(err); + return { status: 500 }; + } + }; - return { - doesPostExist, - }; + return { + doesPostExist, + }; } diff --git a/packages/api-main/src/shared/useUserAuth.ts b/packages/api-main/src/shared/useUserAuth.ts index 44426b58..0671e31c 100644 --- a/packages/api-main/src/shared/useUserAuth.ts +++ b/packages/api-main/src/shared/useUserAuth.ts @@ -1,10 +1,11 @@ -import { randomBytes } from 'crypto'; +import { Buffer } from 'node:buffer'; +import { randomBytes } from 'node:crypto'; import { encodeSecp256k1Pubkey, pubkeyToAddress } from '@cosmjs/amino'; import { verifyADR36Amino } from '@keplr-wallet/cosmos'; import { eq, lt } from 'drizzle-orm'; -import jwt from 'jsonwebtoken'; +import jwt from 'jsonwebtoken'; import { getDatabase } from '../../drizzle/db'; import { AuthRequests } from '../../drizzle/schema'; import { useConfig } from '../config'; @@ -14,96 +15,96 @@ const { JWT } = useConfig(); const expirationTime = 60_000 * 5; function getSignerAddressFromPublicKey(publicKeyBase64: string, prefix: string = 'atone'): string { - const publicKeyBytes = new Uint8Array(Buffer.from(publicKeyBase64, 'base64')); - const secp256k1Pubkey = encodeSecp256k1Pubkey(publicKeyBytes); - return pubkeyToAddress(secp256k1Pubkey, prefix); + const publicKeyBytes = new Uint8Array(Buffer.from(publicKeyBase64, 'base64')); + const secp256k1Pubkey = encodeSecp256k1Pubkey(publicKeyBytes); + return pubkeyToAddress(secp256k1Pubkey, prefix); } async function cleanupRequests() { - const epoch = new Date(Date.now()); - await getDatabase().delete(AuthRequests).where(lt(AuthRequests.timestamp, epoch)).execute(); + const epoch = new Date(Date.now()); + await getDatabase().delete(AuthRequests).where(lt(AuthRequests.timestamp, epoch)).execute(); } export function useUserAuth() { - /** - * Simply creates an authentication request for a specific key. - * It is a time-locked request with a unique identifier. - * - * @param {string} publicKey - * @return {*} - */ - const add = async (publicKey: string) => { - const nonce = randomBytes(16).toString('hex'); - const timestamp = Date.now() + expirationTime; - - let signableMessage = ''; - - // [msg, timestamp, key, nonce] - signableMessage += 'Login,'; - signableMessage += `${timestamp},`; - signableMessage += `${publicKey},`; - signableMessage += `${nonce}`; - - const rows = await getDatabase().insert(AuthRequests).values({ msg: signableMessage, timestamp: new Date(timestamp) }).returning(); - return { id: rows[0].id, message: signableMessage }; - }; - - /** - * How this works is that a user makes a request to authenticate. - * They are given a message that needs to be signed. - * In that message contains a timestamp with an expiration set 5 minutes in the future. - * Additionally they are given an id for their request. - * - * When they authenticate, they sign the message with a wallet. - * The signature and public key are passed up. - * We used the public key and id to identify the data that was stored in-memory. - * We take the signature bytes and verify it against the message that was signed. - * We take the original message, apply the future time, and verify the timestamp is in the correct window. - * - * Finally, if everything is valid the data is cleaned up and can never be authenticated against again. - * Additionally, during each failed attempt we go through and cleanup old login requests. - * - * @param {string} publicKey - * @param {string} signature - * @param {number} id - * @return {*} - */ - const verifyAndCreate = async (publicKey: string, signature: string, id: number) => { - const publicAddress = getSignerAddressFromPublicKey(publicKey, 'atone'); - - const rows = await getDatabase().select().from(AuthRequests).where(eq(AuthRequests.id, id)).limit(1).execute(); - if (rows.length <= 0) { - cleanupRequests(); - return { status: 401, error: 'no available requests found' }; - } - - if (Date.now() > new Date(rows[0].timestamp).getTime()) { - cleanupRequests(); - return { status: 401, error: 'request expired' }; - } - - const originalMessage = rows[0].msg; - const didVerify = verifyADR36Amino( - 'atone', - publicAddress, - originalMessage, - new Uint8Array(Buffer.from(publicKey, 'base64')), - new Uint8Array(Buffer.from(signature, 'base64')), - 'secp256k1', - ); - - if (!didVerify) { - console.warn(`Failed to Verify: ${publicAddress}, ${originalMessage}`); - cleanupRequests(); - return { status: 401, error: 'failed to verify request from public key' }; - } - - await getDatabase().delete(AuthRequests).where(eq(AuthRequests.id, id)).returning(); - return { status: 200, bearer: jwt.sign({ data: rows[0].msg }, JWT, { expiresIn: '3d', algorithm: 'HS256' }) }; - }; - - return { - add, - verifyAndCreate, - }; + /** + * Simply creates an authentication request for a specific key. + * It is a time-locked request with a unique identifier. + * + * @param {string} publicKey + * @return {*} + */ + const add = async (publicKey: string) => { + const nonce = randomBytes(16).toString('hex'); + const timestamp = Date.now() + expirationTime; + + let signableMessage = ''; + + // [msg, timestamp, key, nonce] + signableMessage += 'Login,'; + signableMessage += `${timestamp},`; + signableMessage += `${publicKey},`; + signableMessage += `${nonce}`; + + const rows = await getDatabase().insert(AuthRequests).values({ msg: signableMessage, timestamp: new Date(timestamp) }).returning(); + return { id: rows[0].id, message: signableMessage }; + }; + + /** + * How this works is that a user makes a request to authenticate. + * They are given a message that needs to be signed. + * In that message contains a timestamp with an expiration set 5 minutes in the future. + * Additionally they are given an id for their request. + * + * When they authenticate, they sign the message with a wallet. + * The signature and public key are passed up. + * We used the public key and id to identify the data that was stored in-memory. + * We take the signature bytes and verify it against the message that was signed. + * We take the original message, apply the future time, and verify the timestamp is in the correct window. + * + * Finally, if everything is valid the data is cleaned up and can never be authenticated against again. + * Additionally, during each failed attempt we go through and cleanup old login requests. + * + * @param {string} publicKey + * @param {string} signature + * @param {number} id + * @return {*} + */ + const verifyAndCreate = async (publicKey: string, signature: string, id: number) => { + const publicAddress = getSignerAddressFromPublicKey(publicKey, 'atone'); + + const rows = await getDatabase().select().from(AuthRequests).where(eq(AuthRequests.id, id)).limit(1).execute(); + if (rows.length <= 0) { + cleanupRequests(); + return { status: 401, error: 'no available requests found' }; + } + + if (Date.now() > new Date(rows[0].timestamp).getTime()) { + cleanupRequests(); + return { status: 401, error: 'request expired' }; + } + + const originalMessage = rows[0].msg; + const didVerify = verifyADR36Amino( + 'atone', + publicAddress, + originalMessage, + new Uint8Array(Buffer.from(publicKey, 'base64')), + new Uint8Array(Buffer.from(signature, 'base64')), + 'secp256k1', + ); + + if (!didVerify) { + console.warn(`Failed to Verify: ${publicAddress}, ${originalMessage}`); + cleanupRequests(); + return { status: 401, error: 'failed to verify request from public key' }; + } + + await getDatabase().delete(AuthRequests).where(eq(AuthRequests.id, id)).returning(); + return { status: 200, bearer: jwt.sign({ data: rows[0].msg }, JWT, { expiresIn: '3d', algorithm: 'HS256' }) }; + }; + + return { + add, + verifyAndCreate, + }; } diff --git a/packages/api-main/src/types/feed.ts b/packages/api-main/src/types/feed.ts index 9a605758..6e7e995c 100644 --- a/packages/api-main/src/types/feed.ts +++ b/packages/api-main/src/types/feed.ts @@ -9,10 +9,10 @@ export type Post = InferSelectModel; export const postSchema = createSelectSchema(FeedTable); export interface ReplyWithParent { - reply: InferSelectModel; - parent: InferSelectModel; + reply: InferSelectModel; + parent: InferSelectModel; }; export const replyWithParentSchema = Type.Object({ - reply: postSchema, - parent: postSchema, + reply: postSchema, + parent: postSchema, }); diff --git a/packages/api-main/src/types/follows.ts b/packages/api-main/src/types/follows.ts index caa93efd..fd9d387c 100644 --- a/packages/api-main/src/types/follows.ts +++ b/packages/api-main/src/types/follows.ts @@ -1,4 +1,5 @@ -import { type Static, Type } from '@sinclair/typebox'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; import { createSelectSchema } from 'drizzle-typebox'; import { FollowsTable } from '../../drizzle/schema'; @@ -6,7 +7,7 @@ import { FollowsTable } from '../../drizzle/schema'; export const followSchema = createSelectSchema(FollowsTable); export const followingSchema = Type.Object({ - address: followSchema.properties.following, - hash: followSchema.properties.hash, + address: followSchema.properties.following, + hash: followSchema.properties.hash, }); export type Following = Static; diff --git a/packages/api-main/src/types/index.ts b/packages/api-main/src/types/index.ts index 4993e267..48f3d0ea 100644 --- a/packages/api-main/src/types/index.ts +++ b/packages/api-main/src/types/index.ts @@ -1,12 +1,12 @@ import type { getDatabase } from '../../drizzle/db'; -export type MsgTransfer = { - '@type': string; - 'from_address': string; - 'to_address': string; - 'amount': Array<{ amount: string; denom: string }>; -}; +export interface MsgTransfer { + '@type': string; + 'from_address': string; + 'to_address': string; + 'amount': Array<{ amount: string; denom: string }>; +} -export type MsgGeneric = { [key: string]: unknown }; +export interface MsgGeneric { [key: string]: unknown } export type TransactionFunction = (tx: ReturnType) => Promise; diff --git a/packages/api-main/src/utility/index.ts b/packages/api-main/src/utility/index.ts index e61c4293..aeb44ddb 100644 --- a/packages/api-main/src/utility/index.ts +++ b/packages/api-main/src/utility/index.ts @@ -1,133 +1,129 @@ -import crypto from 'node:crypto'; - import type * as T from '../types/index'; +import Buffer from 'node:buffer'; +import crypto from 'node:crypto'; import { sql } from 'drizzle-orm'; - import { getDatabase } from '../../drizzle/db'; import { useConfig } from '../config'; const { AUTH, DISCORD_WEBHOOK_URL } = useConfig(); export function getTransferMessage(messages: Array) { - const msgTransfer = messages.find(msg => msg['@type'] === '/cosmos.bank.v1beta1.MsgSend'); - if (!msgTransfer) { - return null; - } + const msgTransfer = messages.find(msg => msg['@type'] === '/cosmos.bank.v1beta1.MsgSend'); + if (!msgTransfer) { + return null; + } - return msgTransfer as T.MsgTransfer; + return msgTransfer as T.MsgTransfer; } export function getTransferQuantities(messages: Array, denom = 'uatone') { - const msgTransfers = messages.filter(msg => msg['@type'] === '/cosmos.bank.v1beta1.MsgSend') as T.MsgTransfer[]; - let amount = BigInt('0'); + const msgTransfers = messages.filter(msg => msg['@type'] === '/cosmos.bank.v1beta1.MsgSend') as T.MsgTransfer[]; + let amount = BigInt('0'); - for (const msg of msgTransfers) { - for (const quantity of msg.amount) { - if (quantity.denom !== denom) { - continue; - } + for (const msg of msgTransfers) { + for (const quantity of msg.amount) { + if (quantity.denom !== denom) { + continue; + } - amount += BigInt(quantity.amount); - } + amount += BigInt(quantity.amount); } + } - return amount.toString(); + return amount.toString(); } export function isReaderAuthorizationValid(headers: Record) { - if (!headers['authorization']) { - return false; + if (!headers.authorization) { + return false; + } + + try { + const authHeaderBuffer = Buffer.from(headers.authorization, 'utf8'); + const authSecretBuffer = Buffer.from(AUTH, 'utf8'); + if (authHeaderBuffer.length !== authSecretBuffer.length) { + return false; } - try { - const authHeaderBuffer = Buffer.from(headers['authorization'], 'utf8'); - const authSecretBuffer = Buffer.from(AUTH, 'utf8'); - if (authHeaderBuffer.length !== authSecretBuffer.length) { - return false; - } - - return crypto.timingSafeEqual(authHeaderBuffer, authSecretBuffer); - } - catch (error) { - console.error('Error during authorization validation:', error); - return false; - } + return crypto.timingSafeEqual(authHeaderBuffer, authSecretBuffer); + } catch (error) { + console.error('Error during authorization validation:', error); + return false; + } } export async function getJsonbArrayCount(hash: string, tableName: string) { - const result = await getDatabase().execute(sql` + const result = await getDatabase().execute(sql` SELECT jsonb_array_length(data)::integer AS array_count FROM ${tableName} WHERE hash = ${hash} `); - return result.rows.length > 0 ? result.rows[0].array_count : 0; + return result.rows.length > 0 ? result.rows[0].array_count : 0; } export function getRequestIP(request: Request) { - const forwardedFor = request.headers.get('x-forwarded-for'); - if (forwardedFor) { - return forwardedFor.split(',')[0].trim(); - } - - const realIp = request.headers.get('x-real-ip'); - if (realIp) { - return realIp; - } - - const flyClientIP = request.headers.get('fly-client-ip'); - if (flyClientIP) { - return flyClientIP; - } - - const cfIp = request.headers.get('cf-connecting-ip'); - if (cfIp) { - return cfIp; - } - - // We'll just default to `host` if not found - return request.headers.get('host') ?? 'localhost:3000'; + const forwardedFor = request.headers.get('x-forwarded-for'); + if (forwardedFor) { + return forwardedFor.split(',')[0].trim(); + } + + const realIp = request.headers.get('x-real-ip'); + if (realIp) { + return realIp; + } + + const flyClientIP = request.headers.get('fly-client-ip'); + if (flyClientIP) { + return flyClientIP; + } + + const cfIp = request.headers.get('cf-connecting-ip'); + if (cfIp) { + return cfIp; + } + + // We'll just default to `host` if not found + return request.headers.get('host') ?? 'localhost:3000'; } export async function postToDiscord(content: string, url: string) { - if (DISCORD_WEBHOOK_URL == '') { - console.log(`DISCORD_WEBHOOK_URL was not provided.`); - return; - } - - const payload = { - content: '', - username: 'dither.chat', - embeds: [ - { - title: 'New Activity', - description: content, - color: 3447003, - url, - }, - ], - }; - - try { - const response = await fetch(DISCORD_WEBHOOK_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - if (response.ok) { - console.log('Discord webhook message sent successfully! 🎉'); - } - else { - console.error(`Failed to send Discord message. Status: ${response.status}`); - const errorText = await response.text(); - console.error('Response body:', errorText); - } - } - catch (error) { - console.error('An error occurred during the fetch request:', error); + if (!DISCORD_WEBHOOK_URL) { + console.log(`DISCORD_WEBHOOK_URL was not provided.`); + return; + } + + const payload = { + content: '', + username: 'dither.chat', + embeds: [ + { + title: 'New Activity', + description: content, + color: 3447003, + url, + }, + ], + }; + + try { + const response = await fetch(DISCORD_WEBHOOK_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (response.ok) { + console.log('Discord webhook message sent successfully! 🎉'); + } else { + console.error(`Failed to send Discord message. Status: ${response.status}`); + const errorText = await response.text(); + console.error('Response body:', errorText); } + } catch (error) { + console.error('An error occurred during the fetch request:', error); + } } diff --git a/packages/api-main/tests/auth.test.ts b/packages/api-main/tests/auth.test.ts index 341a28dd..53c6571b 100644 --- a/packages/api-main/tests/auth.test.ts +++ b/packages/api-main/tests/auth.test.ts @@ -5,28 +5,28 @@ import { assert, describe, it } from 'vitest'; import { createWallet, get, post, signADR36Document } from './shared'; describe('v1/auth', async () => { - const walletA = await createWallet(); - - it('create and verify request for wallet', async () => { - const body: typeof Posts.AuthCreateBody.static = { - address: walletA.publicKey, - }; - - const response = (await post(`auth-create`, body)) as { status: 200; id: number; message: string }; - assert.isOk(response?.status === 200, 'response was not okay'); - - const signData = await signADR36Document(walletA.mnemonic, response.message); - const verifyBody: typeof Posts.AuthBody.static & { json?: boolean } = { - id: response.id, - ...signData.signature, - json: true, - }; - - const responseVerifyCreated = (await post(`auth`, verifyBody)) as { status: 200; bearer: string }; - assert.isOk(responseVerifyCreated?.status === 200, 'response was not verified and confirmed okay'); - assert.isOk(responseVerifyCreated.bearer.length >= 1, 'bearer was not passed back'); - - const responseVerify = await get('auth-verify', responseVerifyCreated.bearer) as { status: number }; - assert.isOk(responseVerify.status === 200, 'could not verify through auth-verify endpoint, invalid cookie?'); - }); + const walletA = await createWallet(); + + it('create and verify request for wallet', async () => { + const body: typeof Posts.AuthCreateBody.static = { + address: walletA.publicKey, + }; + + const response = (await post(`auth-create`, body)) as { status: 200; id: number; message: string }; + assert.isOk(response?.status === 200, 'response was not okay'); + + const signData = await signADR36Document(walletA.mnemonic, response.message); + const verifyBody: typeof Posts.AuthBody.static & { json?: boolean } = { + id: response.id, + ...signData.signature, + json: true, + }; + + const responseVerifyCreated = (await post(`auth`, verifyBody)) as { status: 200; bearer: string }; + assert.isOk(responseVerifyCreated?.status === 200, 'response was not verified and confirmed okay'); + assert.isOk(responseVerifyCreated.bearer.length >= 1, 'bearer was not passed back'); + + const responseVerify = await get('auth-verify', responseVerifyCreated.bearer) as { status: number }; + assert.isOk(responseVerify.status === 200, 'could not verify through auth-verify endpoint, invalid cookie?'); + }); }); diff --git a/packages/api-main/tests/feed.test.ts b/packages/api-main/tests/feed.test.ts index 78d25ebf..90375689 100644 --- a/packages/api-main/tests/feed.test.ts +++ b/packages/api-main/tests/feed.test.ts @@ -5,229 +5,229 @@ import { assert, describe, it } from 'vitest'; import { createWallet, get, getRandomHash, post } from './shared'; describe('filter post depending on send tokens', async () => { - const walletB = await createWallet(); - const cheapPostMessage = 'cheap post'; - const expensivePostMessage = 'expensive post'; - const expensivePostTokens = '20'; - - it('user creates one cheap and one expensive posts', async () => { - const body: typeof Posts.PostBody.static = { - from: walletB.publicKey, - hash: getRandomHash(), - msg: cheapPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - let postResponse = await post(`post`, body); - - const expensiveBody: typeof Posts.PostBody.static = { - from: walletB.publicKey, - hash: getRandomHash(), - msg: expensivePostMessage, - quantity: expensivePostTokens, - timestamp: '2025-04-16T19:46:42Z', - }; - - postResponse = await post(`post`, expensiveBody); - assert.isOk(postResponse != null); - assert.isOk(postResponse && postResponse.status === 200, 'response was not okay'); - }); - - it('get feed without filtering by tokens', async () => { - const readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`feed`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.isOk(readResponse.rows.length >= 2); - }); - - it('filtering expensive posts', async () => { - const readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`feed?minQuantity=${expensivePostTokens}`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.lengthOf(readResponse.rows, 1); - }); - - it('Search: filtering cheap posts', async () => { - let readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`search?text="${cheapPostMessage}"&minQuantity=1`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.lengthOf(readResponse.rows, 1); - - readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`search?text="${cheapPostMessage}"&minQuantity=${expensivePostTokens}`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.lengthOf(readResponse.rows, 0); - }); + const walletB = await createWallet(); + const cheapPostMessage = 'cheap post'; + const expensivePostMessage = 'expensive post'; + const expensivePostTokens = '20'; + + it('user creates one cheap and one expensive posts', async () => { + const body: typeof Posts.PostBody.static = { + from: walletB.publicKey, + hash: getRandomHash(), + msg: cheapPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + let postResponse = await post(`post`, body); + + const expensiveBody: typeof Posts.PostBody.static = { + from: walletB.publicKey, + hash: getRandomHash(), + msg: expensivePostMessage, + quantity: expensivePostTokens, + timestamp: '2025-04-16T19:46:42Z', + }; + + postResponse = await post(`post`, expensiveBody); + assert.isOk(postResponse != null); + assert.isOk(postResponse && postResponse.status === 200, 'response was not okay'); + }); + + it('get feed without filtering by tokens', async () => { + const readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`feed`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.isOk(readResponse.rows.length >= 2); + }); + + it('filtering expensive posts', async () => { + const readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`feed?minQuantity=${expensivePostTokens}`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.lengthOf(readResponse.rows, 1); + }); + + it('search: filtering cheap posts', async () => { + let readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`search?text="${cheapPostMessage}"&minQuantity=1`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.lengthOf(readResponse.rows, 1); + + readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`search?text="${cheapPostMessage}"&minQuantity=${expensivePostTokens}`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.lengthOf(readResponse.rows, 0); + }); }); describe('user replies with parent', async () => { - const walletA = await createWallet(); - const walletB = await createWallet(); - const parentPost = getRandomHash(); - const replyPost = getRandomHash(); - const postMessage = 'this is a post'; - const replyMessage = 'this is a reply'; - - it('POST - /post', async () => { - const body: typeof Posts.PostBody.static = { - from: walletA.publicKey, - hash: parentPost, - msg: postMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - }); - - it('POST - /reply', async () => { - const body: typeof Posts.ReplyBody.static = { - from: walletB.publicKey, - hash: replyPost, - post_hash: parentPost, - msg: replyMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const replyResponse = await post(`reply`, body); - assert.isOk(replyResponse?.status === 200, 'response was not okay'); - }); - - it('Get user replies', async () => { - const userRepliesResponse = await get<{ - status: number; - rows: { - parent: { hash: string; author: string; message: string }; - reply: { hash: string; author: string; message: string }; - }[]; - }>(`user-replies?address=${walletB.publicKey}`); - assert.isOk(userRepliesResponse?.status === 200, `response was not okay, got ${userRepliesResponse?.status}`); - assert.isOk(userRepliesResponse.rows.length >= 1); - assert.equal(userRepliesResponse.rows[0].reply.hash, replyPost); - assert.equal(userRepliesResponse.rows[0].parent.hash, parentPost); - assert.equal(userRepliesResponse.rows[0].reply.message, replyMessage); - assert.equal(userRepliesResponse.rows[0].parent.message, postMessage); - }); + const walletA = await createWallet(); + const walletB = await createWallet(); + const parentPost = getRandomHash(); + const replyPost = getRandomHash(); + const postMessage = 'this is a post'; + const replyMessage = 'this is a reply'; + + it('pOST - /post', async () => { + const body: typeof Posts.PostBody.static = { + from: walletA.publicKey, + hash: parentPost, + msg: postMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + }); + + it('pOST - /reply', async () => { + const body: typeof Posts.ReplyBody.static = { + from: walletB.publicKey, + hash: replyPost, + post_hash: parentPost, + msg: replyMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const replyResponse = await post(`reply`, body); + assert.isOk(replyResponse?.status === 200, 'response was not okay'); + }); + + it('get user replies', async () => { + const userRepliesResponse = await get<{ + status: number; + rows: { + parent: { hash: string; author: string; message: string }; + reply: { hash: string; author: string; message: string }; + }[]; + }>(`user-replies?address=${walletB.publicKey}`); + assert.isOk(userRepliesResponse?.status === 200, `response was not okay, got ${userRepliesResponse?.status}`); + assert.isOk(userRepliesResponse.rows.length >= 1); + assert.equal(userRepliesResponse.rows[0].reply.hash, replyPost); + assert.equal(userRepliesResponse.rows[0].parent.hash, parentPost); + assert.equal(userRepliesResponse.rows[0].reply.message, replyMessage); + assert.equal(userRepliesResponse.rows[0].parent.message, postMessage); + }); }); describe('get post from followed', async () => { - const walletA = await createWallet(); - const walletB = await createWallet(); - const postMessage = 'this is a post'; - - it('zero posts if not followers', async () => { - const readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - quantity: string; - }[]; - }>(`following-posts?address=${walletA.publicKey}`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.lengthOf(readResponse.rows, 0); - }); - - it('POST - now followed user posts', async () => { - const body: typeof Posts.PostBody.static = { - from: walletB.publicKey, - hash: getRandomHash(), - msg: postMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const postResponse = await post(`post`, body); - assert.isOk(postResponse != null); - assert.isOk(postResponse && postResponse.status === 200, 'response was not okay'); - }); - - it('Still empty response', async () => { - const readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`following-posts?address=${walletA.publicKey}`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.lengthOf(readResponse.rows, 0); - }); - - it('One post when user follows', async () => { - const body: typeof Posts.FollowBody.static = { - from: walletA.publicKey, - hash: getRandomHash(), - address: walletB.publicKey, - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`follow`, body); - assert.isOk(response?.status === 200, 'unable to follow user'); - - const readResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - quantity: string; - }[]; - }>(`following-posts?address=${walletA.publicKey}`); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - assert.lengthOf(readResponse.rows, 1); - assert.isOk(readResponse.rows[0].author, 'Author was not included'); - assert.isOk(readResponse.rows[0].message, 'message was not included'); - assert.isOk(readResponse.rows[0].quantity, 'quantity was not included'); - }); + const walletA = await createWallet(); + const walletB = await createWallet(); + const postMessage = 'this is a post'; + + it('zero posts if not followers', async () => { + const readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + quantity: string; + }[]; + }>(`following-posts?address=${walletA.publicKey}`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.lengthOf(readResponse.rows, 0); + }); + + it('pOST - now followed user posts', async () => { + const body: typeof Posts.PostBody.static = { + from: walletB.publicKey, + hash: getRandomHash(), + msg: postMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const postResponse = await post(`post`, body); + assert.isOk(postResponse != null); + assert.isOk(postResponse && postResponse.status === 200, 'response was not okay'); + }); + + it('still empty response', async () => { + const readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`following-posts?address=${walletA.publicKey}`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.lengthOf(readResponse.rows, 0); + }); + + it('one post when user follows', async () => { + const body: typeof Posts.FollowBody.static = { + from: walletA.publicKey, + hash: getRandomHash(), + address: walletB.publicKey, + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`follow`, body); + assert.isOk(response?.status === 200, 'unable to follow user'); + + const readResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + quantity: string; + }[]; + }>(`following-posts?address=${walletA.publicKey}`); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + assert.lengthOf(readResponse.rows, 1); + assert.isOk(readResponse.rows[0].author, 'Author was not included'); + assert.isOk(readResponse.rows[0].message, 'message was not included'); + assert.isOk(readResponse.rows[0].quantity, 'quantity was not included'); + }); }); diff --git a/packages/api-main/tests/follow.test.ts b/packages/api-main/tests/follow.test.ts index 4ac27734..f7b9d221 100644 --- a/packages/api-main/tests/follow.test.ts +++ b/packages/api-main/tests/follow.test.ts @@ -5,100 +5,100 @@ import { assert, describe, it } from 'vitest'; import { get, getAtomOneAddress, getRandomHash, post } from './shared'; describe('follows', async () => { - const addressUserA = getAtomOneAddress(); - const addressUserB = getAtomOneAddress(); - const addressUserC = getAtomOneAddress(); - - // Follows - it('POST - /follow', async () => { - const body: typeof Posts.FollowBody.static = { - from: addressUserA, - hash: getRandomHash(), - address: addressUserB, - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`follow`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - }); - - it('GET - /is-following', async () => { - let response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `is-following?follower=${addressUserA}&following=${addressUserB}`, - ); - - assert.isOk(response?.status === 200, 'follower was not found, should have follower'); - response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `is-following?follower=${addressUserA}&following=${addressUserC}`, - ); - - assert.isOk(response?.status === 404, 'follower was found when follower should not be following anyone'); - }); - - it('POST - /follow - no duplicates', async () => { - const body: typeof Posts.FollowBody.static = { - hash: getRandomHash(), - from: addressUserA, - address: addressUserB, - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`follow`, body, 'WRITE'); - assert.isOk(response?.status === 400, 'additional follow was allowed somehow'); - }); - - it('GET - /following', async () => { - const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `following?address=${addressUserA}`, - ); - - assert.isOk(response && Array.isArray(response.rows), 'following response was not an array'); - assert.isOk(response && response.rows.find(x => x.address === addressUserB)); - }); - - it('GET - /followers', async () => { - const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `followers?address=${addressUserB}`, - ); - - assert.isOk(response && Array.isArray(response.rows), 'following response was not an array'); - assert.isOk(response && response.rows.find(x => x.address === addressUserA)); - }); - - // Unfollow - it('POST - /unfollow', async () => { - const body: typeof Posts.UnfollowBody.static = { - hash: getRandomHash(), - from: addressUserA, - address: addressUserB, - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`unfollow`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - }); - - it('GET - /is-following (Not Following)', async () => { - const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `is-following?follower=${addressUserA}&following=${addressUserB}`, - ); - - assert.isOk(response?.status === 404, 'follower was found, should not have follower'); - }); - - it('GET - /followers', async () => { - const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `followers?address=${addressUserB}`, - ); - assert.isOk(response && Array.isArray(response.rows), 'followers response was not an array'); - assert.isOk(response && response.rows.length <= 0, 'did not unfollow all users'); - }); - - it('GET - /following', async () => { - const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( - `following?address=${addressUserA}`, - ); - assert.isOk(response && Array.isArray(response.rows), 'following response was not an array'); - assert.isOk(response && response.rows.length <= 0, 'did not unfollow all users'); - }); + const addressUserA = getAtomOneAddress(); + const addressUserB = getAtomOneAddress(); + const addressUserC = getAtomOneAddress(); + + // Follows + it('pOST - /follow', async () => { + const body: typeof Posts.FollowBody.static = { + from: addressUserA, + hash: getRandomHash(), + address: addressUserB, + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`follow`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + }); + + it('gET - /is-following', async () => { + let response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `is-following?follower=${addressUserA}&following=${addressUserB}`, + ); + + assert.isOk(response?.status === 200, 'follower was not found, should have follower'); + response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `is-following?follower=${addressUserA}&following=${addressUserC}`, + ); + + assert.isOk(response?.status === 404, 'follower was found when follower should not be following anyone'); + }); + + it('pOST - /follow - no duplicates', async () => { + const body: typeof Posts.FollowBody.static = { + hash: getRandomHash(), + from: addressUserA, + address: addressUserB, + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`follow`, body, 'WRITE'); + assert.isOk(response?.status === 400, 'additional follow was allowed somehow'); + }); + + it('gET - /following', async () => { + const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `following?address=${addressUserA}`, + ); + + assert.isOk(response && Array.isArray(response.rows), 'following response was not an array'); + assert.isOk(response && response.rows.find(x => x.address === addressUserB)); + }); + + it('gET - /followers', async () => { + const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `followers?address=${addressUserB}`, + ); + + assert.isOk(response && Array.isArray(response.rows), 'following response was not an array'); + assert.isOk(response && response.rows.find(x => x.address === addressUserA)); + }); + + // Unfollow + it('pOST - /unfollow', async () => { + const body: typeof Posts.UnfollowBody.static = { + hash: getRandomHash(), + from: addressUserA, + address: addressUserB, + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`unfollow`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + }); + + it('gET - /is-following (Not Following)', async () => { + const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `is-following?follower=${addressUserA}&following=${addressUserB}`, + ); + + assert.isOk(response?.status === 404, 'follower was found, should not have follower'); + }); + + it('gET - /followers - user B', async () => { + const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `followers?address=${addressUserB}`, + ); + assert.isOk(response && Array.isArray(response.rows), 'followers response was not an array'); + assert.isOk(response && response.rows.length <= 0, 'did not unfollow all users'); + }); + + it('gET - /following - empty', async () => { + const response = await get<{ status: number; rows: { hash: string; address: string }[] }>( + `following?address=${addressUserA}`, + ); + assert.isOk(response && Array.isArray(response.rows), 'following response was not an array'); + assert.isOk(response && response.rows.length <= 0, 'did not unfollow all users'); + }); }); diff --git a/packages/api-main/tests/moderator.test.ts b/packages/api-main/tests/moderator.test.ts index 0ee248e5..f26032d9 100644 --- a/packages/api-main/tests/moderator.test.ts +++ b/packages/api-main/tests/moderator.test.ts @@ -8,306 +8,306 @@ import { ModeratorTable } from '../drizzle/schema'; import { createWallet, get, getAtomOneAddress, getRandomHash, post, signADR36Document } from './shared'; describe('v1 - mod', { sequential: true }, () => { - const addressUserA = getAtomOneAddress(); - let addressModerator = getAtomOneAddress(); - const genericPostMessage - = 'hello world, this is a really intereresting post $@!($)@!()@!$21,4214,12,42142,14,12,421,'; - const postHash = getRandomHash(); - const secondPostHash = getRandomHash(); - let bearerToken: string; - - it('POST mod obtain bearer token', async () => { - const walletA = await createWallet(); - addressModerator = walletA.publicKey; - const body: typeof Posts.AuthCreateBody.static = { - address: walletA.publicKey, - }; - - const response = (await post(`auth-create`, body)) as { status: 200; id: number; message: string }; - assert.isOk(response?.status === 200, 'response was not okay'); - - const signData = await signADR36Document(walletA.mnemonic, response.message); - const verifyBody: typeof Posts.AuthBody.static & { json?: boolean } = { - id: response.id, - ...signData.signature, - json: true, - }; - - const responseVerify = (await post(`auth`, verifyBody)) as { status: 200; bearer: string }; - assert.isOk(responseVerify?.status === 200, 'response was not verified and confirmed okay'); - assert.isOk(responseVerify.bearer.length >= 1, 'bearer was not passed back'); - bearerToken = responseVerify.bearer; - }); - - it('POST - /post', async () => { - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: postHash, - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - }); - - it('POST - /mod/post-remove without autorization', async () => { - const body: typeof Posts.ModRemovePostBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: postHash, - reason: 'spam', - }; - - const replyResponse = await post(`mod/post-remove`, body); - assert.isOk(replyResponse?.status === 401, `expected unauthorized, got ${JSON.stringify(replyResponse)}`); - }); - - it('POST - /mod/post-remove moderator does not exists', async () => { - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `posts?address=${addressUserA}`, - ); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - - const body: typeof Posts.ModRemovePostBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: response.rows[0].hash, - reason: 'spam', - }; - - const replyResponse = await post(`mod/post-remove`, body, bearerToken); - assert.isOk(replyResponse?.status === 404, `expected moderator was not found`); - - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); - assert.isOk(data, 'data was hidden'); - }); - - it('POST - /mod/post-remove moderator exists', async () => { - await getDatabase() - .insert(ModeratorTable) - .values({ - address: addressModerator, - alias: 'mod', - }) - .execute(); - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `posts?address=${addressUserA}`, - ); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - - const body: typeof Posts.ModRemovePostBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: response.rows[0].hash, - reason: 'spam', - }; - - const replyResponse = await post(`mod/post-remove`, body, bearerToken); - assert.isOk(replyResponse?.status === 200, `response was not okay, got ${JSON.stringify(replyResponse)}`); - - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); - assert.isUndefined(data, 'data was not hidden'); - }); - - it('POST - /mod/post-restore', async () => { - const body: typeof Posts.ModRemovePostBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: postHash, - reason: 'spam', - }; - - const replyResponse = await post(`mod/post-restore`, body, bearerToken); - assert.isOk(replyResponse?.status === 200, `response was not okay, got ${JSON.stringify(replyResponse)}`); - - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === postHash); - assert.isOk(data, 'data is hidden'); - }); - - it('POST - /mod/post-restore on an user deleted post', async () => { - // USER REMOVES POST - const body: typeof Posts.PostRemoveBody.static = { - from: addressUserA, - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: postHash, - }; - - const userRemoveResponse = await post(`post-remove`, body, bearerToken); - assert.isOk(userRemoveResponse?.status === 200, 'response was not okay'); - - // MOD tries to restore post - const bodymod: typeof Posts.ModRemovePostBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: postHash, - reason: 'spam', - }; - - const replyResponse = await post(`mod/post-restore`, bodymod); - assert.isOk(replyResponse?.status === 401, `response was not okay, expected unauthorized`); - }); - - it('POST - /post user creates a second post', async () => { - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: secondPostHash, - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - }); - - it('POST - /mod/ban user banned deletes posts', async () => { - // moderator bans user - const body: typeof Posts.ModBanBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - user_address: addressUserA, - reason: 'user too political', - }; - - const userBanResponse = await post(`mod/ban`, body, bearerToken); - assert.isOk(userBanResponse?.status === 200, `response was not okay ${JSON.stringify(userBanResponse)}`); - - // post from user should be all hidden - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - assert.isOk( - Array.isArray(postsResponse.rows) && postsResponse.rows.length == 0, - 'some of the user posts are shown', - ); - }); - - it('POST - banned user publishes post is deleted automatically', async () => { - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: getRandomHash(), - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body, bearerToken); - assert.isOk(response?.status === 200, 'response was not okay'); - - // Even new post should be hidden - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - assert.isOk( - Array.isArray(postsResponse.rows) && postsResponse.rows.length == 0, - 'some of the user posts are shown', - ); - }); - - it('POST - unban restore all posts but user deleted ones', async () => { - const body: typeof Posts.ModBanBody.static = { - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - user_address: addressUserA, - reason: 'user too political', - }; - - const userBanResponse = await post(`mod/unban`, body, bearerToken); - assert.isOk(userBanResponse?.status === 200, `response was not okay ${JSON.stringify(userBanResponse)}`); - }); - - it('POST - freshly unbanned user publishes without problems', async () => { - const newPostHash = getRandomHash(); - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: newPostHash, - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body, bearerToken); - assert.isOk(response?.status === 200, 'response was not okay'); - - // Even new post should be hidden - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === newPostHash); - assert.isOk(data, 'New post was hidden'); - }); + const addressUserA = getAtomOneAddress(); + let addressModerator = getAtomOneAddress(); + const genericPostMessage + = 'hello world, this is a really intereresting post $@!($)@!()@!$21,4214,12,42142,14,12,421,'; + const postHash = getRandomHash(); + const secondPostHash = getRandomHash(); + let bearerToken: string; + + it('pOST mod obtain bearer token', async () => { + const walletA = await createWallet(); + addressModerator = walletA.publicKey; + const body: typeof Posts.AuthCreateBody.static = { + address: walletA.publicKey, + }; + + const response = (await post(`auth-create`, body)) as { status: 200; id: number; message: string }; + assert.isOk(response?.status === 200, 'response was not okay'); + + const signData = await signADR36Document(walletA.mnemonic, response.message); + const verifyBody: typeof Posts.AuthBody.static & { json?: boolean } = { + id: response.id, + ...signData.signature, + json: true, + }; + + const responseVerify = (await post(`auth`, verifyBody)) as { status: 200; bearer: string }; + assert.isOk(responseVerify?.status === 200, 'response was not verified and confirmed okay'); + assert.isOk(responseVerify.bearer.length >= 1, 'bearer was not passed back'); + bearerToken = responseVerify.bearer; + }); + + it('pOST - /post', async () => { + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: postHash, + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + }); + + it('pOST - /mod/post-remove without autorization', async () => { + const body: typeof Posts.ModRemovePostBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: postHash, + reason: 'spam', + }; + + const replyResponse = await post(`mod/post-remove`, body); + assert.isOk(replyResponse?.status === 401, `expected unauthorized, got ${JSON.stringify(replyResponse)}`); + }); + + it('pOST - /mod/post-remove moderator does not exists', async () => { + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `posts?address=${addressUserA}`, + ); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + + const body: typeof Posts.ModRemovePostBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: response.rows[0].hash, + reason: 'spam', + }; + + const replyResponse = await post(`mod/post-remove`, body, bearerToken); + assert.isOk(replyResponse?.status === 404, `expected moderator was not found`); + + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); + assert.isOk(data, 'data was hidden'); + }); + + it('pOST - /mod/post-remove moderator exists', async () => { + await getDatabase() + .insert(ModeratorTable) + .values({ + address: addressModerator, + alias: 'mod', + }) + .execute(); + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `posts?address=${addressUserA}`, + ); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + + const body: typeof Posts.ModRemovePostBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: response.rows[0].hash, + reason: 'spam', + }; + + const replyResponse = await post(`mod/post-remove`, body, bearerToken); + assert.isOk(replyResponse?.status === 200, `response was not okay, got ${JSON.stringify(replyResponse)}`); + + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); + assert.isUndefined(data, 'data was not hidden'); + }); + + it('pOST - /mod/post-restore', async () => { + const body: typeof Posts.ModRemovePostBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: postHash, + reason: 'spam', + }; + + const replyResponse = await post(`mod/post-restore`, body, bearerToken); + assert.isOk(replyResponse?.status === 200, `response was not okay, got ${JSON.stringify(replyResponse)}`); + + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === postHash); + assert.isOk(data, 'data is hidden'); + }); + + it('pOST - /mod/post-restore on an user deleted post', async () => { + // USER REMOVES POST + const body: typeof Posts.PostRemoveBody.static = { + from: addressUserA, + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: postHash, + }; + + const userRemoveResponse = await post(`post-remove`, body, bearerToken); + assert.isOk(userRemoveResponse?.status === 200, 'response was not okay'); + + // MOD tries to restore post + const bodymod: typeof Posts.ModRemovePostBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: postHash, + reason: 'spam', + }; + + const replyResponse = await post(`mod/post-restore`, bodymod); + assert.isOk(replyResponse?.status === 401, `response was not okay, expected unauthorized`); + }); + + it('pOST - /post user creates a second post', async () => { + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: secondPostHash, + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + }); + + it('pOST - /mod/ban user banned deletes posts', async () => { + // moderator bans user + const body: typeof Posts.ModBanBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + user_address: addressUserA, + reason: 'user too political', + }; + + const userBanResponse = await post(`mod/ban`, body, bearerToken); + assert.isOk(userBanResponse?.status === 200, `response was not okay ${JSON.stringify(userBanResponse)}`); + + // post from user should be all hidden + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + assert.isOk( + Array.isArray(postsResponse.rows) && postsResponse.rows.length === 0, + 'some of the user posts are shown', + ); + }); + + it('pOST - banned user publishes post is deleted automatically', async () => { + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: getRandomHash(), + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body, bearerToken); + assert.isOk(response?.status === 200, 'response was not okay'); + + // Even new post should be hidden + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + assert.isOk( + Array.isArray(postsResponse.rows) && postsResponse.rows.length === 0, + 'some of the user posts are shown', + ); + }); + + it('pOST - unban restore all posts but user deleted ones', async () => { + const body: typeof Posts.ModBanBody.static = { + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + user_address: addressUserA, + reason: 'user too political', + }; + + const userBanResponse = await post(`mod/unban`, body, bearerToken); + assert.isOk(userBanResponse?.status === 200, `response was not okay ${JSON.stringify(userBanResponse)}`); + }); + + it('pOST - freshly unbanned user publishes without problems', async () => { + const newPostHash = getRandomHash(); + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: newPostHash, + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body, bearerToken); + assert.isOk(response?.status === 200, 'response was not okay'); + + // Even new post should be hidden + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === newPostHash); + assert.isOk(data, 'New post was hidden'); + }); }); diff --git a/packages/api-main/tests/notifications.test.ts b/packages/api-main/tests/notifications.test.ts index 21667f8a..58063fd5 100644 --- a/packages/api-main/tests/notifications.test.ts +++ b/packages/api-main/tests/notifications.test.ts @@ -5,143 +5,143 @@ import { assert, describe, it } from 'vitest'; import { createWallet, get, getRandomHash, post, userLogin } from './shared'; describe('v1/notifications', async () => { - const walletA = await createWallet(); - const walletB = await createWallet(); - - let bearerToken: string; - - it('User obtains bearer token', async () => { - bearerToken = await userLogin(walletB); - assert.isOk(bearerToken.length >= 1, 'bearer was not passed back'); - }); - - // Follows - it('POST - /follow', async () => { - const body: typeof Posts.FollowBody.static = { - from: walletA.publicKey, - hash: getRandomHash(), - address: walletB.publicKey, - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`follow`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - const notificationResponse = await get<{ - status: number; - rows: { - hash: string; - owner: string; - type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; - timestamp: Date | null; - was_read: boolean | null; - actor: string; - }[]; - }>(`notifications?address=${walletB.publicKey}`, bearerToken); - // Asert user got a notification and can read it - assert.isOk(notificationResponse?.status === 200, `response was not okay, got ${notificationResponse?.status}`); - assert.lengthOf(notificationResponse.rows, 1); - assert.isFalse(notificationResponse.rows[0].was_read, `notification was not marked as read, got true`); - assert.isOk(notificationResponse.rows[0].actor === walletA.publicKey, `unexpected actor, got ${notificationResponse.rows[0].actor}`); - - const readResponse = await post<{ - status: number; - }>( - `notification-read?address=${walletB.publicKey}&hash=${notificationResponse.rows[0].hash}`, - {}, - bearerToken, - ); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - // after reading the notification it should no longer show - const lastResponse = await get<{ - status: number; - rows: { - hash: string; - owner: string; - type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; - timestamp: Date | null; - was_read: boolean | null; - }[]; - }>(`notifications?address=${walletB.publicKey}`, bearerToken); - - assert.isOk(lastResponse?.rows.findIndex(x => x.hash == notificationResponse.rows[0].hash) === -1, 'notification was still available in array'); - }); - - // Follows - it('liking a post notify the post owner', async () => { - const genericPostMessage - = 'hello world, this is a really intereresting post $@!($)@!()@!$21,4214,12,42142,14,12,421,'; - const postHash = getRandomHash(); - const body: typeof Posts.PostBody.static = { - from: walletA.publicKey, - hash: postHash, - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const postResponse = await post(`post`, body); - assert.isOk(postResponse != null); - assert.isOk(postResponse && postResponse.status === 200, 'response was not okay'); - - const likeBody: typeof Posts.LikeBody.static = { - from: walletB.publicKey, - hash: getRandomHash(), - post_hash: postHash, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const likeResponse = await post(`like`, likeBody); - assert.isOk(likeResponse != null); - assert.isOk(likeResponse && likeResponse.status === 200, 'response was not okay got ' + likeResponse.status); - - // Login as userA - bearerToken = await userLogin(walletA); - assert.isOk(bearerToken.length >= 1, 'bearer was not passed back'); - - const notificationCount = await get<{ - status: number; - count: number; - }>(`notifications-count?address=${walletA.publicKey}`, bearerToken); - assert.isOk(notificationCount?.count === 1, 'notification count was not at least one'); - - const notificationResponse = await get<{ - status: number; - rows: { - hash: string; - owner: string; - type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; - timestamp: Date | null; - was_read: boolean | null; - actor: string; - }[]; - }>(`notifications?address=${walletA.publicKey}`, bearerToken); - // Asert user got a notification and can read it - assert.isOk(notificationResponse?.status === 200, `response was not okay, got ${notificationResponse?.status}`); - assert.lengthOf(notificationResponse.rows, 1); - assert.isFalse(notificationResponse.rows[0].was_read, `notification was not marked as read, got true`); - assert.isOk(notificationResponse.rows[0].actor === walletB.publicKey, `unexpected actor, got ${notificationResponse.rows[0].actor}`); - - const readResponse = await post<{ - status: number; - }>( - `notification-read?address=${walletA.publicKey}&hash=${notificationResponse.rows[0].hash}`, - {}, - bearerToken, - ); - assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); - // after reading the notification it should be marked as read - const lastResponse = await get<{ - status: number; - rows: { - hash: string; - owner: string; - type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; - timestamp: Date | null; - was_read: boolean | null; - }[]; - }>(`notifications?address=${walletA.publicKey}`, bearerToken); - - assert.isOk(lastResponse?.rows.findIndex(x => x.hash == notificationResponse.rows[0].hash) === -1, 'notification was still available in array'); - }); + const walletA = await createWallet(); + const walletB = await createWallet(); + + let bearerToken: string; + + it('user obtains bearer token', async () => { + bearerToken = await userLogin(walletB); + assert.isOk(bearerToken.length >= 1, 'bearer was not passed back'); + }); + + // Follows + it('pOST - /follow', async () => { + const body: typeof Posts.FollowBody.static = { + from: walletA.publicKey, + hash: getRandomHash(), + address: walletB.publicKey, + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`follow`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + const notificationResponse = await get<{ + status: number; + rows: { + hash: string; + owner: string; + type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; + timestamp: Date | null; + was_read: boolean | null; + actor: string; + }[]; + }>(`notifications?address=${walletB.publicKey}`, bearerToken); + // Asert user got a notification and can read it + assert.isOk(notificationResponse?.status === 200, `response was not okay, got ${notificationResponse?.status}`); + assert.lengthOf(notificationResponse.rows, 1); + assert.isFalse(notificationResponse.rows[0].was_read, `notification was not marked as read, got true`); + assert.isOk(notificationResponse.rows[0].actor === walletA.publicKey, `unexpected actor, got ${notificationResponse.rows[0].actor}`); + + const readResponse = await post<{ + status: number; + }>( + `notification-read?address=${walletB.publicKey}&hash=${notificationResponse.rows[0].hash}`, + {}, + bearerToken, + ); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + // after reading the notification it should no longer show + const lastResponse = await get<{ + status: number; + rows: { + hash: string; + owner: string; + type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; + timestamp: Date | null; + was_read: boolean | null; + }[]; + }>(`notifications?address=${walletB.publicKey}`, bearerToken); + + assert.isOk(lastResponse?.rows.findIndex(x => x.hash === notificationResponse.rows[0].hash) === -1, 'notification was still available in array'); + }); + + // Follows + it('liking a post notify the post owner', async () => { + const genericPostMessage + = 'hello world, this is a really intereresting post $@!($)@!()@!$21,4214,12,42142,14,12,421,'; + const postHash = getRandomHash(); + const body: typeof Posts.PostBody.static = { + from: walletA.publicKey, + hash: postHash, + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const postResponse = await post(`post`, body); + assert.isOk(postResponse != null); + assert.isOk(postResponse && postResponse.status === 200, 'response was not okay'); + + const likeBody: typeof Posts.LikeBody.static = { + from: walletB.publicKey, + hash: getRandomHash(), + post_hash: postHash, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const likeResponse = await post(`like`, likeBody); + assert.isOk(likeResponse != null); + assert.isOk(likeResponse && likeResponse.status === 200, `response was not okay got ${likeResponse.status}`); + + // Login as userA + bearerToken = await userLogin(walletA); + assert.isOk(bearerToken.length >= 1, 'bearer was not passed back'); + + const notificationCount = await get<{ + status: number; + count: number; + }>(`notifications-count?address=${walletA.publicKey}`, bearerToken); + assert.isOk(notificationCount?.count === 1, 'notification count was not at least one'); + + const notificationResponse = await get<{ + status: number; + rows: { + hash: string; + owner: string; + type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; + timestamp: Date | null; + was_read: boolean | null; + actor: string; + }[]; + }>(`notifications?address=${walletA.publicKey}`, bearerToken); + // Asert user got a notification and can read it + assert.isOk(notificationResponse?.status === 200, `response was not okay, got ${notificationResponse?.status}`); + assert.lengthOf(notificationResponse.rows, 1); + assert.isFalse(notificationResponse.rows[0].was_read, `notification was not marked as read, got true`); + assert.isOk(notificationResponse.rows[0].actor === walletB.publicKey, `unexpected actor, got ${notificationResponse.rows[0].actor}`); + + const readResponse = await post<{ + status: number; + }>( + `notification-read?address=${walletA.publicKey}&hash=${notificationResponse.rows[0].hash}`, + {}, + bearerToken, + ); + assert.isOk(readResponse?.status === 200, `response was not okay, got ${readResponse?.status}`); + // after reading the notification it should be marked as read + const lastResponse = await get<{ + status: number; + rows: { + hash: string; + owner: string; + type: 'like' | 'dislike' | 'flag' | 'follow' | 'reply'; + timestamp: Date | null; + was_read: boolean | null; + }[]; + }>(`notifications?address=${walletA.publicKey}`, bearerToken); + + assert.isOk(lastResponse?.rows.findIndex(x => x.hash === notificationResponse.rows[0].hash) === -1, 'notification was still available in array'); + }); }); diff --git a/packages/api-main/tests/search.test.ts b/packages/api-main/tests/search.test.ts index 574725ef..1fb59f46 100644 --- a/packages/api-main/tests/search.test.ts +++ b/packages/api-main/tests/search.test.ts @@ -5,51 +5,51 @@ import { assert, describe, it } from 'vitest'; import { get, getAtomOneAddress, getRandomHash, post } from './shared'; describe('should search for posts', async () => { - const addressUserA = getAtomOneAddress(); - - it('Search - /search', async () => { - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: getRandomHash(), - msg: 'this is a very unique message with a very unique result', - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - - const results1 = await get<{ status: number; rows: { message: string }[] }>( - 'search?text="very unique message"', - ); - assert.isOk(results1?.status === 200); - assert.isOk(results1.rows.length === 1); - - const results2 = await get<{ status: number; rows: { message: string }[] }>( - 'search?text="supercalifragilisticexpialidocious"', - ); - assert.isOk(results2?.status === 200); - assert.isOk(results2.rows.length <= 0); - }); - - it('Search - /search post with owner', async () => { - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: getRandomHash(), - msg: 'content not related at all with owner', - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - - const results1 = await get<{ status: number; rows: { message: string }[]; users: string[] }>( - `search?text=${addressUserA}`, - ); - assert.isOk(results1?.status === 200); - assert.isOk(results1.rows.length > 1); - assert.isOk(results1.users.length === 1); - assert.isOk(results1.users[0] === addressUserA); - }); + const addressUserA = getAtomOneAddress(); + + it('search - /search', async () => { + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: getRandomHash(), + msg: 'this is a very unique message with a very unique result', + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + + const results1 = await get<{ status: number; rows: { message: string }[] }>( + 'search?text="very unique message"', + ); + assert.isOk(results1?.status === 200); + assert.isOk(results1.rows.length === 1); + + const results2 = await get<{ status: number; rows: { message: string }[] }>( + 'search?text="supercalifragilisticexpialidocious"', + ); + assert.isOk(results2?.status === 200); + assert.isOk(results2.rows.length <= 0); + }); + + it('search - /search post with owner', async () => { + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: getRandomHash(), + msg: 'content not related at all with owner', + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + + const results1 = await get<{ status: number; rows: { message: string }[]; users: string[] }>( + `search?text=${addressUserA}`, + ); + assert.isOk(results1?.status === 200); + assert.isOk(results1.rows.length > 1); + assert.isOk(results1.users.length === 1); + assert.isOk(results1.users[0] === addressUserA); + }); }); diff --git a/packages/api-main/tests/setup.ts b/packages/api-main/tests/setup.ts index 300d400e..5ef4d4c1 100644 --- a/packages/api-main/tests/setup.ts +++ b/packages/api-main/tests/setup.ts @@ -7,24 +7,23 @@ import { tables } from '../drizzle/schema'; import { start } from '../src/index'; async function clearTables() { - console.log('Clearing Tables'); - try { - for (const tableName of tables) { - await getDatabase().execute(sql`TRUNCATE TABLE ${sql.raw(tableName)};`); - } - } - catch (err) { - console.error('Error clearing tables:', err); - // Continue anyway - tables might not exist yet + console.log('Clearing Tables'); + try { + for (const tableName of tables) { + await getDatabase().execute(sql`TRUNCATE TABLE ${sql.raw(tableName)};`); } + } catch (err) { + console.error('Error clearing tables:', err); + // Continue anyway - tables might not exist yet + } } export async function setup(project: TestProject) { - start(); + start(); - // Give server time to start - await new Promise(resolve => setTimeout(resolve, 1000)); + // Give server time to start + await new Promise(resolve => setTimeout(resolve, 1000)); - project.onTestsRerun(clearTables); - await clearTables(); + project.onTestsRerun(clearTables); + await clearTables(); } diff --git a/packages/api-main/tests/shared.ts b/packages/api-main/tests/shared.ts index 9284b428..eede318e 100644 --- a/packages/api-main/tests/shared.ts +++ b/packages/api-main/tests/shared.ts @@ -1,7 +1,8 @@ -import { createHash, randomBytes } from 'crypto'; - import type { Posts } from '@atomone/dither-api-types'; +import { createHash, randomBytes } from 'node:crypto'; +import process from 'node:process'; + import { Secp256k1HdWallet } from '@cosmjs/amino'; import { toBech32 } from '@cosmjs/encoding'; import { makeADR36AminoSignDoc } from '@keplr-wallet/cosmos'; @@ -9,162 +10,162 @@ import { makeADR36AminoSignDoc } from '@keplr-wallet/cosmos'; let lastHeight = 1_000_000; export async function get(endpoint: string, token?: string) { - const headers: Record = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers['Cookie'] = `auth=${token}`; - } - - const response = await fetch(`http://localhost:3000/v1/${endpoint}`, { - method: 'GET', - headers, - }).catch((err) => { - console.error(err); - return null; - }); - - if (!response?.ok) { - return null; - } - - const jsonData = await response.json(); - return jsonData as T; + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers.Cookie = `auth=${token}`; + } + + const response = await fetch(`http://localhost:3000/v1/${endpoint}`, { + method: 'GET', + headers, + }).catch((err) => { + console.error(err); + return null; + }); + + if (!response?.ok) { + return null; + } + + const jsonData = await response.json(); + return jsonData as T; } export async function post( - endpoint: string, - body: object, - token?: string, + endpoint: string, + body: object, + token?: string, ): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - 'authorization': process.env.AUTH ?? 'whatever', - }; - - if (token) { - headers['Cookie'] = `auth=${token}`; - } - - const response = await fetch(`http://localhost:3000/v1/${endpoint}`, { - method: 'POST', - headers, - body: JSON.stringify({ ...body }), - }).catch((err) => { - console.error(err); - return null; - }); - - if (!response?.ok) { - console.log(await response?.json()); - return null; - } - - const jsonData = (await response.json()) as { status: number }; - if (jsonData.status && jsonData.status !== 200) { - return jsonData as T; - } - + const headers: Record = { + 'Content-Type': 'application/json', + 'authorization': process.env.AUTH ?? 'whatever', + }; + + if (token) { + headers.Cookie = `auth=${token}`; + } + + const response = await fetch(`http://localhost:3000/v1/${endpoint}`, { + method: 'POST', + headers, + body: JSON.stringify({ ...body }), + }).catch((err) => { + console.error(err); + return null; + }); + + if (!response?.ok) { + console.log(await response?.json()); + return null; + } + + const jsonData = (await response.json()) as { status: number }; + if (jsonData.status && jsonData.status !== 200) { return jsonData as T; + } + + return jsonData as T; } export function getSha256Hash(input: string | Uint8Array): string { - const hash = createHash('sha256'); - hash.update(input); - return hash.digest('hex'); + const hash = createHash('sha256'); + hash.update(input); + return hash.digest('hex'); } export function getAtomOneAddress(): string { - const randomData = randomBytes(32); - const hash = createHash('sha256').update(randomData).digest(); - const addressBytes = hash.slice(0, 20); - const encodedAddress = toBech32('atone', addressBytes); - return encodedAddress; + const randomData = randomBytes(32); + const hash = createHash('sha256').update(randomData).digest(); + const addressBytes = hash.slice(0, 20); + const encodedAddress = toBech32('atone', addressBytes); + return encodedAddress; } export async function createWallet() { - const wallet = await Secp256k1HdWallet.generate(24, { prefix: 'atone' }); - const accounts = await wallet.getAccounts(); - return { mnemonic: wallet.mnemonic, publicKey: accounts[0].address }; + const wallet = await Secp256k1HdWallet.generate(24, { prefix: 'atone' }); + const accounts = await wallet.getAccounts(); + return { mnemonic: wallet.mnemonic, publicKey: accounts[0].address }; } export async function signADR36Document(mnemonic: string, messageToSign: string) { - const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'atone' }); - const accounts = await wallet.getAccounts(); + const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'atone' }); + const accounts = await wallet.getAccounts(); - const document = makeADR36AminoSignDoc(accounts[0].address, messageToSign); - return await wallet.signAmino(accounts[0].address, document); + const document = makeADR36AminoSignDoc(accounts[0].address, messageToSign); + return await wallet.signAmino(accounts[0].address, document); } export function getRandomHash() { - return getSha256Hash(randomBytes(256).toString()); + return getSha256Hash(randomBytes(256).toString()); } export function generateFakeData(memo: string, from_address: string, to_address: string) { - lastHeight++; - - return { - hash: getSha256Hash(randomBytes(256).toString()), - height: lastHeight.toString(), - timestamp: '2025-04-16T19:46:42Z', // Doesn't matter, just need to store some timestamps - memo, - messages: [ - { - '@type': '/cosmos.bank.v1beta1.MsgSend', - 'from_address': from_address, - 'to_address': to_address, - 'amount': [{ denom: 'uatone', amount: '1' }], - }, - ], - }; + lastHeight++; + + return { + hash: getSha256Hash(randomBytes(256).toString()), + height: lastHeight.toString(), + timestamp: '2025-04-16T19:46:42Z', // Doesn't matter, just need to store some timestamps + memo, + messages: [ + { + '@type': '/cosmos.bank.v1beta1.MsgSend', + 'from_address': from_address, + 'to_address': to_address, + 'amount': [{ denom: 'uatone', amount: '1' }], + }, + ], + }; } export function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } export async function userLogin(wallet: { mnemonic: string; publicKey: string }) { - const body: typeof Posts.AuthCreateBody.static = { - address: wallet.publicKey, - }; - - const response = (await post(`auth-create`, body, 'READ')) as { status: 200; id: number; message: string }; - if (response?.status !== 200) { - return ''; - } - - const signData = await signADR36Document(wallet.mnemonic, response.message); - const verifyBody: typeof Posts.AuthBody.static & { json: boolean } = { - id: response.id, - ...signData.signature, - json: true, - }; - - const responseVerify = (await post(`auth`, verifyBody, 'READ')) as { status: 200; bearer: string }; - if (response?.status !== 200) { - return ''; - } - - return responseVerify.bearer; + const body: typeof Posts.AuthCreateBody.static = { + address: wallet.publicKey, + }; + + const response = (await post(`auth-create`, body, 'READ')) as { status: 200; id: number; message: string }; + if (response?.status !== 200) { + return ''; + } + + const signData = await signADR36Document(wallet.mnemonic, response.message); + const verifyBody: typeof Posts.AuthBody.static & { json: boolean } = { + id: response.id, + ...signData.signature, + json: true, + }; + + const responseVerify = (await post(`auth`, verifyBody, 'READ')) as { status: 200; bearer: string }; + if (response?.status !== 200) { + return ''; + } + + return responseVerify.bearer; } export async function createPost(msg = 'default content') { - const address = getAtomOneAddress(); - const hash = getRandomHash(); - - const body: typeof Posts.PostBody.static = { - from: address, - hash: hash, - msg, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - if (response?.status !== 200) { - return undefined; - } - - return { hash, address }; + const address = getAtomOneAddress(); + const hash = getRandomHash(); + + const body: typeof Posts.PostBody.static = { + from: address, + hash, + msg, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + if (response?.status !== 200) { + return undefined; + } + + return { hash, address }; }; diff --git a/packages/api-main/tests/state.test.ts b/packages/api-main/tests/state.test.ts index a9b81935..18952de5 100644 --- a/packages/api-main/tests/state.test.ts +++ b/packages/api-main/tests/state.test.ts @@ -7,17 +7,17 @@ import { ReaderState } from '../drizzle/schema'; import { post } from './shared'; describe('update state', () => { - it('should update state', async () => { - let response = await post<{ status: number; error: string }>(`update-state`, { last_block: '1' }); - assert.isOk(response?.status === 200, 'could not post to update state'); + it('should update state', async () => { + let response = await post<{ status: number; error: string }>(`update-state`, { last_block: '1' }); + assert.isOk(response?.status === 200, 'could not post to update state'); - let [state] = await getDatabase().select().from(ReaderState).where(eq(ReaderState.id, 0)).limit(1); - assert.isOk(state.last_block == '1'); + let [state] = await getDatabase().select().from(ReaderState).where(eq(ReaderState.id, 0)).limit(1); + assert.isOk(state.last_block === '1'); - response = await post<{ status: number; error: string }>(`update-state`, { last_block: '2' }); - assert.isOk(response?.status === 200, 'could not post to update state'); + response = await post<{ status: number; error: string }>(`update-state`, { last_block: '2' }); + assert.isOk(response?.status === 200, 'could not post to update state'); - [state] = await getDatabase().select().from(ReaderState).where(eq(ReaderState.id, 0)).limit(1); - assert.isOk(state.last_block == '2'); - }); + [state] = await getDatabase().select().from(ReaderState).where(eq(ReaderState.id, 0)).limit(1); + assert.isOk(state.last_block === '2'); + }); }); diff --git a/packages/api-main/tests/utility.test.ts b/packages/api-main/tests/utility.test.ts index 83716199..9725b959 100644 --- a/packages/api-main/tests/utility.test.ts +++ b/packages/api-main/tests/utility.test.ts @@ -5,31 +5,31 @@ import { getTransferMessage, getTransferQuantities } from '../src/utility'; import { generateFakeData, getAtomOneAddress } from './shared'; describe('utility tests', () => { - it('getTransferMessage', () => { - const userA = getAtomOneAddress(); - const userB = getAtomOneAddress(); + it('getTransferMessage', () => { + const userA = getAtomOneAddress(); + const userB = getAtomOneAddress(); - const msgTransfer = getTransferMessage([generateFakeData('whatever', userA, userB)]); + const msgTransfer = getTransferMessage([generateFakeData('whatever', userA, userB)]); - expect(msgTransfer && msgTransfer.from_address == userA, 'from address did not match'); - expect(msgTransfer && msgTransfer.to_address == userB, 'to address did not match'); - }); + expect(msgTransfer && msgTransfer.from_address === userA, 'from address did not match'); + expect(msgTransfer && msgTransfer.to_address === userB, 'to address did not match'); + }); - it('getAtomOneAddress', () => { - for (let i = 0; i < 100; i++) { - expect(getAtomOneAddress().length === 44, 'address length was incorrect'); - } - }); + it('getAtomOneAddress', () => { + for (let i = 0; i < 100; i++) { + expect(getAtomOneAddress().length === 44, 'address length was incorrect'); + } + }); - it('getTransferQuantities', () => { - let totalQuantity = BigInt('0'); + it('getTransferQuantities', () => { + let totalQuantity = BigInt('0'); - for (let i = 0; i < 100; i++) { - totalQuantity += BigInt( - getTransferQuantities([generateFakeData('whatever', getAtomOneAddress(), getAtomOneAddress())]), - ); - } + for (let i = 0; i < 100; i++) { + totalQuantity += BigInt( + getTransferQuantities([generateFakeData('whatever', getAtomOneAddress(), getAtomOneAddress())]), + ); + } - expect(totalQuantity === BigInt('100')); - }); + expect(totalQuantity === BigInt('100')); + }); }); diff --git a/packages/api-main/tests/v1.test.ts b/packages/api-main/tests/v1.test.ts index 7b56350c..038e13be 100644 --- a/packages/api-main/tests/v1.test.ts +++ b/packages/api-main/tests/v1.test.ts @@ -5,306 +5,306 @@ import { assert, describe, it } from 'vitest'; import { createPost, get, getAtomOneAddress, getRandomHash, post } from './shared'; describe('v1', { sequential: true }, async () => { - const addressUserA = getAtomOneAddress(); - const replyHash = getRandomHash(); - const genericPostMessage - = 'hello world, this is a really intereresting post $@!($)@!()@!$21,4214,12,42142,14,12,421,'; - - // Posts - it('POST - /post', async () => { - const body: typeof Posts.PostBody.static = { - from: addressUserA, - hash: getRandomHash(), - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const response = await post(`post`, body); - assert.isOk(response?.status === 200, 'response was not okay'); - }); - - it('POST - /reply', async () => { - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `feed`, - ); - assert.isOk(response, 'failed to fetch feed data'); - assert.isOk( - response && Array.isArray(response.rows) && response.rows.length >= 1, - 'feed result was not an array type', - ); - - const body: typeof Posts.ReplyBody.static = { - from: addressUserA, - hash: replyHash, - post_hash: response.rows[0].hash, - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const replyResponse = await post(`reply`, body); - assert.isOk(replyResponse?.status === 200, 'response was not okay'); - }); - - it('GET - /feed', async () => { - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `feed`, - ); - assert.isOk(response, 'failed to fetch feed data'); - assert.isOk( - response && Array.isArray(response.rows) && response.rows.length >= 1, - 'feed result was not an array type', - ); - - const message = response.rows.find(x => x.author === addressUserA && x.message === genericPostMessage); - assert.isOk(message); - }); - - it('GET - /posts', async () => { - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `posts?address=${addressUserA}`, - ); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk( - response && Array.isArray(response.rows) && response.rows.length >= 1, - 'feed result was not an array type', - ); - }); - - // Likes - const likeablePost = await createPost('A Likeable Post'); - it ('should have a likeable post', () => { - assert.isOk(likeablePost); - }); - - it('POST - /like', async () => { - if (!likeablePost) { - assert.fail('Likeable post does not exist'); - } - - for (let i = 0; i < 50; i++) { - const body: typeof Posts.LikeBody.static = { - from: addressUserA, - hash: getRandomHash(), - post_hash: likeablePost?.hash, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const likeResponse = await post(`like`, body); - assert.isOk(likeResponse != null); - assert.isOk(likeResponse && likeResponse.status === 200, 'response was not okay'); - } - }); - - it('GET - /likes', async () => { - if (!likeablePost) { - assert.fail('Likeable post does not exist'); - } - - const response = await get<{ status: number; rows: { hash: string; likes: number }[] }>( - `post?hash=${likeablePost.hash}`, - ); - - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - assert.isOk(response && response.rows[0].likes >= 50, 'likes were not incremented on post'); - }); - - // Dislikes - const dislikeablePost = await createPost('A Dislikeable Post'); - it ('should have a dislikeable post', () => { - assert.isOk(dislikeablePost); - }); - - it('POST - /dislike', async () => { - if (!dislikeablePost) { - assert.fail('Likeable post does not exist'); - } - - for (let i = 0; i < 50; i++) { - const body: typeof Posts.DislikeBody.static = { - from: addressUserA, - hash: getRandomHash(), - post_hash: dislikeablePost.hash, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const dislikeResponse = await post(`dislike`, body); - assert.isOk(dislikeResponse != null); - assert.isOk(dislikeResponse && dislikeResponse.status === 200, 'response was not okay'); - } - }); - - it('GET - /dislikes', async () => { - if (!dislikeablePost) { - assert.fail('Likeable post does not exist'); - } - - const response = await get<{ - status: number; - rows: { hash: string; author: string; message: string; dislikes: number }[]; - }>(`post?hash=${dislikeablePost.hash}`); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - assert.isOk(response && response.rows[0].dislikes >= 50, 'likes were not incremented on post'); - }); - - // Flags - const flagPost = await createPost('A Dislikeable Post'); - it ('should have a dislikeable post', () => { - assert.isOk(flagPost); - }); - - it('POST - /flag', async () => { - if (!flagPost) { - assert.fail('Likeable post does not exist'); - } - - for (let i = 0; i < 50; i++) { - const body: typeof Posts.FlagBody.static = { - from: addressUserA, - hash: getRandomHash(), - post_hash: flagPost.hash, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const flagResponse = await post(`flag`, body); - assert.isOk(flagResponse != null); - assert.isOk(flagResponse && flagResponse.status === 200, 'response was not okay'); - } - }); - - it('GET - /flags', async () => { - if (!flagPost) { - assert.fail('Likeable post does not exist'); - } - - const getResponse = await get<{ status: number; rows: Array<{ hash: string }> }>( - `flags?hash=${flagPost.hash}`, - ); - assert.isOk(getResponse, 'failed to fetch posts data'); - assert.isOk(getResponse.status == 200, 'flags result was not valid'); - assert.isOk( - getResponse && Array.isArray(getResponse.rows) && getResponse.rows.length >= 50, - 'feed result was not an array type', - ); - }); - - // PostRemove - it('POST - /post-remove', async () => { - const bodyPost: typeof Posts.PostBody.static = { - from: addressUserA, - hash: getRandomHash(), - msg: genericPostMessage, - quantity: '1', - timestamp: '2025-04-16T19:46:42Z', - }; - - const responsePost = await post(`post`, bodyPost); - assert.isOk(responsePost?.status === 200, 'response was not okay'); - - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `posts?address=${addressUserA}`, - ); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - - const body: typeof Posts.PostRemoveBody.static = { - from: addressUserA, - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: response.rows[0].hash, - }; - - const replyResponse = await post(`post-remove`, body); - assert.isOk(replyResponse?.status === 200, 'response was not okay'); - - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); - assert.isUndefined(data, 'data was not hidden'); - }); - - it('POST - /post-remove - No Permission', async () => { - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `posts?address=${addressUserA}`, - ); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - - const body: typeof Posts.PostRemoveBody.static = { - from: addressUserA + 'abcd', - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: response.rows[0].hash, - }; - - const replyResponse = await post(`post-remove`, body); - assert.isOk(replyResponse?.status === 200, 'response was not okay'); - - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); - assert.isOk(data, 'data was hidden'); - }); - - it('POST - /mod/post-remove - No Permission', async () => { - const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( - `posts?address=${addressUserA}`, - ); - assert.isOk(response, 'failed to fetch posts data'); - assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); - - const body: typeof Posts.PostRemoveBody.static = { - from: addressUserA + 'abcd', - hash: getRandomHash(), - timestamp: '2025-04-16T19:46:42Z', - post_hash: response.rows[0].hash, - }; - - const replyResponse = await post(`post-remove`, body); - assert.isOk(replyResponse?.status === 200, 'response was not okay'); - - const postsResponse = await get<{ - status: number; - rows: { - hash: string; - author: string; - message: string; - deleted_at: Date; - deleted_reason: string; - deleted_hash: string; - }[]; - }>(`posts?address=${addressUserA}`); - - assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); - const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); - assert.isOk(data, 'data was hidden'); - }); + const addressUserA = getAtomOneAddress(); + const replyHash = getRandomHash(); + const genericPostMessage + = 'hello world, this is a really intereresting post $@!($)@!()@!$21,4214,12,42142,14,12,421,'; + + // Posts + it('pOST - /post', async () => { + const body: typeof Posts.PostBody.static = { + from: addressUserA, + hash: getRandomHash(), + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const response = await post(`post`, body); + assert.isOk(response?.status === 200, 'response was not okay'); + }); + + it('pOST - /reply', async () => { + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `feed`, + ); + assert.isOk(response, 'failed to fetch feed data'); + assert.isOk( + response && Array.isArray(response.rows) && response.rows.length >= 1, + 'feed result was not an array type', + ); + + const body: typeof Posts.ReplyBody.static = { + from: addressUserA, + hash: replyHash, + post_hash: response.rows[0].hash, + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const replyResponse = await post(`reply`, body); + assert.isOk(replyResponse?.status === 200, 'response was not okay'); + }); + + it('gET - /feed', async () => { + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `feed`, + ); + assert.isOk(response, 'failed to fetch feed data'); + assert.isOk( + response && Array.isArray(response.rows) && response.rows.length >= 1, + 'feed result was not an array type', + ); + + const message = response.rows.find(x => x.author === addressUserA && x.message === genericPostMessage); + assert.isOk(message); + }); + + it('gET - /posts', async () => { + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `posts?address=${addressUserA}`, + ); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk( + response && Array.isArray(response.rows) && response.rows.length >= 1, + 'feed result was not an array type', + ); + }); + + // Likes + const likeablePost = await createPost('A Likeable Post'); + it ('should have a likeable post', () => { + assert.isOk(likeablePost); + }); + + it('pOST - /like', async () => { + if (!likeablePost) { + assert.fail('Likeable post does not exist'); + } + + for (let i = 0; i < 50; i++) { + const body: typeof Posts.LikeBody.static = { + from: addressUserA, + hash: getRandomHash(), + post_hash: likeablePost?.hash, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const likeResponse = await post(`like`, body); + assert.isOk(likeResponse != null); + assert.isOk(likeResponse && likeResponse.status === 200, 'response was not okay'); + } + }); + + it('gET - /likes', async () => { + if (!likeablePost) { + assert.fail('Likeable post does not exist'); + } + + const response = await get<{ status: number; rows: { hash: string; likes: number }[] }>( + `post?hash=${likeablePost.hash}`, + ); + + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + assert.isOk(response && response.rows[0].likes >= 50, 'likes were not incremented on post'); + }); + + // Dislikes + const dislikeablePost = await createPost('A Dislikeable Post'); + it ('should have a dislikeable post', () => { + assert.isOk(dislikeablePost); + }); + + it('pOST - /dislike', async () => { + if (!dislikeablePost) { + assert.fail('Likeable post does not exist'); + } + + for (let i = 0; i < 50; i++) { + const body: typeof Posts.DislikeBody.static = { + from: addressUserA, + hash: getRandomHash(), + post_hash: dislikeablePost.hash, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const dislikeResponse = await post(`dislike`, body); + assert.isOk(dislikeResponse != null); + assert.isOk(dislikeResponse && dislikeResponse.status === 200, 'response was not okay'); + } + }); + + it('gET - /dislikes', async () => { + if (!dislikeablePost) { + assert.fail('Likeable post does not exist'); + } + + const response = await get<{ + status: number; + rows: { hash: string; author: string; message: string; dislikes: number }[]; + }>(`post?hash=${dislikeablePost.hash}`); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + assert.isOk(response && response.rows[0].dislikes >= 50, 'likes were not incremented on post'); + }); + + // Flags + const flagPost = await createPost('A Dislikeable Post'); + it ('should have a dislikeable post (flag)', () => { + assert.isOk(flagPost); + }); + + it('pOST - /flag', async () => { + if (!flagPost) { + assert.fail('Likeable post does not exist'); + } + + for (let i = 0; i < 50; i++) { + const body: typeof Posts.FlagBody.static = { + from: addressUserA, + hash: getRandomHash(), + post_hash: flagPost.hash, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const flagResponse = await post(`flag`, body); + assert.isOk(flagResponse != null); + assert.isOk(flagResponse && flagResponse.status === 200, 'response was not okay'); + } + }); + + it('gET - /flags', async () => { + if (!flagPost) { + assert.fail('Likeable post does not exist'); + } + + const getResponse = await get<{ status: number; rows: Array<{ hash: string }> }>( + `flags?hash=${flagPost.hash}`, + ); + assert.isOk(getResponse, 'failed to fetch posts data'); + assert.isOk(getResponse.status === 200, 'flags result was not valid'); + assert.isOk( + getResponse && Array.isArray(getResponse.rows) && getResponse.rows.length >= 50, + 'feed result was not an array type', + ); + }); + + // PostRemove + it('pOST - /post-remove', async () => { + const bodyPost: typeof Posts.PostBody.static = { + from: addressUserA, + hash: getRandomHash(), + msg: genericPostMessage, + quantity: '1', + timestamp: '2025-04-16T19:46:42Z', + }; + + const responsePost = await post(`post`, bodyPost); + assert.isOk(responsePost?.status === 200, 'response was not okay'); + + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `posts?address=${addressUserA}`, + ); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + + const body: typeof Posts.PostRemoveBody.static = { + from: addressUserA, + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: response.rows[0].hash, + }; + + const replyResponse = await post(`post-remove`, body); + assert.isOk(replyResponse?.status === 200, 'response was not okay'); + + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); + assert.isUndefined(data, 'data was not hidden'); + }); + + it('pOST - /post-remove - No Permission', async () => { + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `posts?address=${addressUserA}`, + ); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + + const body: typeof Posts.PostRemoveBody.static = { + from: `${addressUserA}abcd`, + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: response.rows[0].hash, + }; + + const replyResponse = await post(`post-remove`, body); + assert.isOk(replyResponse?.status === 200, 'response was not okay'); + + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); + assert.isOk(data, 'data was hidden'); + }); + + it('pOST - /mod/post-remove - No Permission', async () => { + const response = await get<{ status: number; rows: { hash: string; author: string; message: string }[] }>( + `posts?address=${addressUserA}`, + ); + assert.isOk(response, 'failed to fetch posts data'); + assert.isOk(Array.isArray(response.rows) && response.rows.length >= 1, 'feed result was not an array type'); + + const body: typeof Posts.PostRemoveBody.static = { + from: `${addressUserA}abcd`, + hash: getRandomHash(), + timestamp: '2025-04-16T19:46:42Z', + post_hash: response.rows[0].hash, + }; + + const replyResponse = await post(`post-remove`, body); + assert.isOk(replyResponse?.status === 200, 'response was not okay'); + + const postsResponse = await get<{ + status: number; + rows: { + hash: string; + author: string; + message: string; + deleted_at: Date; + deleted_reason: string; + deleted_hash: string; + }[]; + }>(`posts?address=${addressUserA}`); + + assert.isOk(postsResponse?.status === 200, 'posts did not resolve'); + const data = postsResponse?.rows.find(x => x.hash === response.rows[0].hash); + assert.isOk(data, 'data was hidden'); + }); }); diff --git a/packages/api-main/tsconfig.json b/packages/api-main/tsconfig.json index f3adf71b..4af00f3e 100644 --- a/packages/api-main/tsconfig.json +++ b/packages/api-main/tsconfig.json @@ -1,19 +1,19 @@ { - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "Bundler", - "lib": ["ESNext", "DOM"], - "outDir": "./dist", - "strict": true, - "allowJs": true, - "checkJs": false, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "rootDir": ".", - "types": ["vitest/importMeta"] - }, - "include": ["src", "drizzle.config.ts", "db", "drizzle/schema.ts", "drizzle/db.ts"], - "exclude": ["node_modules", "dist"] + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext", "DOM"], + "rootDir": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["vitest/importMeta"], + "allowJs": true, + "checkJs": false, + "strict": true, + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["src", "drizzle.config.ts", "db", "drizzle/schema.ts", "drizzle/db.ts"], + "exclude": ["node_modules", "dist"] } diff --git a/packages/api-main/vitest.config.ts b/packages/api-main/vitest.config.ts index b97b1eb2..1f7ffaf3 100644 --- a/packages/api-main/vitest.config.ts +++ b/packages/api-main/vitest.config.ts @@ -1,27 +1,27 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ - test: { - sequence: { - setupFiles: 'list', - }, - globalSetup: './tests/setup.ts', - forceRerunTriggers: [ - '**/tests/**/*', - ], - pool: 'forks', - poolOptions: { - forks: { - singleFork: true, - }, - }, - // reporters: ['verbose'], + test: { + sequence: { + setupFiles: 'list', }, - define: { - 'process.env.SKIP_START': JSON.stringify(true), - 'process.env.PG_URI': JSON.stringify('postgresql://default:password@localhost:5432/postgres'), - 'process.env.JWT': JSON.stringify('default_jwt_secret'), - 'process.env.JWT_STRICTNESS': JSON.stringify('lax'), - 'process.env.AUTH': JSON.stringify('whatever'), + globalSetup: './tests/setup.ts', + forceRerunTriggers: [ + '**/tests/**/*', + ], + pool: 'forks', + poolOptions: { + forks: { + singleFork: true, + }, }, + // reporters: ['verbose'], + }, + define: { + 'process.env.SKIP_START': JSON.stringify(true), + 'process.env.PG_URI': JSON.stringify('postgresql://default:password@localhost:5432/postgres'), + 'process.env.JWT': JSON.stringify('default_jwt_secret'), + 'process.env.JWT_STRICTNESS': JSON.stringify('lax'), + 'process.env.AUTH': JSON.stringify('whatever'), + }, }); diff --git a/packages/cli/README.md b/packages/cli/README.md index 4e4c66a6..c17abf00 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -18,4 +18,4 @@ go run main.go ## Endpoint -Modify `config.json` to change the `rpc` endpoint. \ No newline at end of file +Modify `config.json` to change the `rpc` endpoint. diff --git a/packages/cli/config.json b/packages/cli/config.json index 718bca69..d4aa044b 100644 --- a/packages/cli/config.json +++ b/packages/cli/config.json @@ -1,3 +1,3 @@ { "rpc_endpoint": "https://atomone-testnet-1-rpc.allinbits.services" -} \ No newline at end of file +} diff --git a/packages/frontend-main/STYLEGUIDE.md b/packages/frontend-main/STYLEGUIDE.md index a4cb5187..3c4f5d92 100644 --- a/packages/frontend-main/STYLEGUIDE.md +++ b/packages/frontend-main/STYLEGUIDE.md @@ -33,10 +33,10 @@ This document defines the coding standards, patterns, and constraints for the Di ```json { - "tabWidth": 4, - "singleQuote": true, - "semi": true, - "printWidth": 120 + "tabWidth": 4, + "singleQuote": true, + "semi": true, + "printWidth": 120 } ``` @@ -55,29 +55,29 @@ This document defines the coding standards, patterns, and constraints for the Di ```vue ``` @@ -151,19 +154,19 @@ onMounted(() => { ```typescript // Good - Explicit interface interface Props { - title: string; - isVisible?: boolean; - items: Array<{ id: string; name: string }>; + title: string; + isVisible?: boolean; + items: Array<{ id: string; name: string }>; } const props = withDefaults(defineProps(), { - isVisible: false, + isVisible: false, }); // Bad - Inline props const props = defineProps<{ - title: string; - isVisible?: boolean; + title: string; + isVisible?: boolean; }>(); ``` @@ -172,9 +175,9 @@ const props = defineProps<{ ```typescript // Good - Explicit interface interface Emits { - update: [value: string]; - close: []; - error: [error: Error]; + update: [value: string]; + close: []; + error: [error: Error]; } const emit = defineEmits(); @@ -188,24 +191,24 @@ const emit = defineEmits(['update', 'close', 'error']); ```typescript // Good - Return object with named properties export function useWallet() { - const address = ref(''); - const isConnected = computed(() => !!address.value); - - function connect() { - // Connection logic - } - - return { - address: readonly(address), - isConnected, - connect, - }; + const address = ref(''); + const isConnected = computed(() => !!address.value); + + function connect() { + // Connection logic + } + + return { + address: readonly(address), + isConnected, + connect, + }; } // Bad - Return array export function useWallet() { - const address = ref(''); - return [address, connect]; + const address = ref(''); + return [address, connect]; } ``` @@ -215,14 +218,18 @@ export function useWallet() { ```vue ``` @@ -231,16 +238,16 @@ export function useWallet() { ```css /* Good - Use design tokens */ .component { - background-color: var(--background); - color: var(--foreground); - border-radius: var(--radius); + background-color: var(--background); + color: var(--foreground); + border-radius: var(--radius); } /* Bad - Hard-coded values */ .component { - background-color: #ffffff; - color: #000000; - border-radius: 8px; + background-color: #ffffff; + color: #000000; + border-radius: 8px; } ``` @@ -252,18 +259,18 @@ export function useWallet() { import { cn } from '@/utility'; const props = defineProps<{ - variant: 'primary' | 'secondary'; - size: 'sm' | 'md' | 'lg'; + variant: 'primary' | 'secondary'; + size: 'sm' | 'md' | 'lg'; }>(); const buttonClasses = computed(() => - cn('px-4 py-2 rounded-md font-medium transition-colors', { - 'bg-primary text-primary-foreground': props.variant === 'primary', - 'bg-secondary text-secondary-foreground': props.variant === 'secondary', - 'px-2 py-1 text-sm': props.size === 'sm', - 'px-4 py-2': props.size === 'md', - 'px-6 py-3 text-lg': props.size === 'lg', - }), + cn('px-4 py-2 rounded-md font-medium transition-colors', { + 'bg-primary text-primary-foreground': props.variant === 'primary', + 'bg-secondary text-secondary-foreground': props.variant === 'secondary', + 'px-2 py-1 text-sm': props.size === 'sm', + 'px-4 py-2': props.size === 'md', + 'px-6 py-3 text-lg': props.size === 'lg', + }), ); ``` @@ -275,43 +282,43 @@ const buttonClasses = computed(() => ```typescript // Good - Clear store structure export const useWalletStore = defineStore( - 'wallet', - () => { - // State - const address = ref(''); - const isConnected = ref(false); - - // Getters - const shortAddress = computed(() => (address.value ? shorten(address.value) : '')); - - // Actions - function setAddress(newAddress: string) { - address.value = newAddress; - isConnected.value = !!newAddress; - } - - function disconnect() { - address.value = ''; - isConnected.value = false; - } - - return { - // State - address: readonly(address), - isConnected: readonly(isConnected), - // Getters - shortAddress, - // Actions - setAddress, - disconnect, - }; - }, - { - persist: { - storage: sessionStorage, - pick: ['address', 'isConnected'], - }, + 'wallet', + () => { + // State + const address = ref(''); + const isConnected = ref(false); + + // Getters + const shortAddress = computed(() => (address.value ? shorten(address.value) : '')); + + // Actions + function setAddress(newAddress: string) { + address.value = newAddress; + isConnected.value = !!newAddress; + } + + function disconnect() { + address.value = ''; + isConnected.value = false; + } + + return { + // State + address: readonly(address), + isConnected: readonly(isConnected), + // Getters + shortAddress, + // Actions + setAddress, + disconnect, + }; + }, + { + persist: { + storage: sessionStorage, + pick: ['address', 'isConnected'], }, + }, ); ``` @@ -320,19 +327,19 @@ export const useWalletStore = defineStore( ```typescript // Good - Proper query structure export function useFeed() { - const queryClient = useQueryClient(); - - return useInfiniteQuery({ - queryKey: ['feed'], - queryFn: async ({ pageParam = 0 }) => { - const response = await fetch(`/api/feed?page=${pageParam}`); - return response.json(); - }, - getNextPageParam: (lastPage, allPages) => { - return lastPage.hasMore ? allPages.length : undefined; - }, - staleTime: 5 * 60 * 1000, // 5 minutes - }); + const queryClient = useQueryClient(); + + return useInfiniteQuery({ + queryKey: ['feed'], + queryFn: async ({ pageParam = 0 }) => { + const response = await fetch(`/api/feed?page=${pageParam}`); + return response.json(); + }, + getNextPageParam: (lastPage, allPages) => { + return lastPage.hasMore ? allPages.length : undefined; + }, + staleTime: 5 * 60 * 1000, // 5 minutes + }); } ``` @@ -344,25 +351,20 @@ export function useFeed() { - - - - + + + + ``` ### Use These Patterns Instead @@ -391,30 +400,32 @@ import { ref } from 'vue'; const count = ref(0); function increment() { - count.value++; + count.value++; } - - - - - - + + + + + + ``` ## Code Quality Rules diff --git a/packages/frontend-main/components.json b/packages/frontend-main/components.json index e99b45b8..fb3c596b 100644 --- a/packages/frontend-main/components.json +++ b/packages/frontend-main/components.json @@ -16,4 +16,4 @@ "ui": "@/components/ui" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/packages/frontend-main/index.html b/packages/frontend-main/index.html index aaadcdac..c21cc285 100644 --- a/packages/frontend-main/index.html +++ b/packages/frontend-main/index.html @@ -1,26 +1,39 @@ - + - - - - - dither.chat - - - - - - - - - - - - - - - -
- - + + + + + dither.chat + + + + + + + + + + + + + + + +
+ + diff --git a/packages/frontend-main/src/App.vue b/packages/frontend-main/src/App.vue index f01ecc60..871b2ede 100644 --- a/packages/frontend-main/src/App.vue +++ b/packages/frontend-main/src/App.vue @@ -21,28 +21,27 @@ const wallet = useWallet(); // Fetch balance when wallet state is loaded from storage onMounted(() => { - // Wait for next tick to ensure state is loaded from storage - nextTick(() => { - if (wallet.address.value) { - wallet.refreshAddress(); - balanceFetcher.updateAddress(wallet.address.value); - } - }); + // Wait for next tick to ensure state is loaded from storage + nextTick(() => { + if (wallet.address.value) { + wallet.refreshAddress(); + balanceFetcher.updateAddress(wallet.address.value); + } + }); }); -