From f4cabb3032741b9ed48a4355d95a3e1a3445ff4d Mon Sep 17 00:00:00 2001 From: Mahesh Date: Sun, 25 Jan 2026 22:19:07 +0530 Subject: [PATCH] feat: googleAuth --- bun.lock | 115 +- package.json | 3 +- src/controllers/agent.controller.ts | 1680 ++++++++++++++------------- src/controllers/auth.controller.ts | 80 +- src/index.ts | 32 +- src/lib/db/index.ts | 12 +- src/lib/env.ts | 27 +- src/lib/google/index.ts | 89 ++ src/middlewares/jwt.middleware.ts | 48 +- src/routes/auth.route.ts | 8 +- src/services/auth.service.ts | 175 ++- 11 files changed, 1273 insertions(+), 996 deletions(-) create mode 100644 src/lib/google/index.ts diff --git a/bun.lock b/bun.lock index 44d895f..0e3e3d6 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,7 @@ "chromadb": "^3.1.6", "dotenv": "^17.2.3", "ethers": "^6.15.0", + "googleapis": "^170.1.0", "hono": "^4.9.8", "pg": "^8.16.3", "prisma": "^7.0.1", @@ -221,6 +222,8 @@ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -271,6 +274,8 @@ "@peculiar/webcrypto": ["@peculiar/webcrypto@1.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.3.5", "tslib": "^2.6.2", "webcrypto-core": "^1.8.0" } }, "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@prisma/adapter-pg": ["@prisma/adapter-pg@7.0.1", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.0.1", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-01GpPPhLMoDMF4ipgfZz0L87fla/TV/PBQcmHy+9vV1ml6gUoqF8dUIRNI5Yf2YKpOwzQg9sn8C7dYD1Yio9Ug=="], "@prisma/client": ["@prisma/client@7.0.1", "", { "dependencies": { "@prisma/client-runtime-utils": "7.0.1" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g=="], @@ -409,6 +414,12 @@ "aes-js": ["aes-js@4.0.0-beta.5", "", {}, "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], "asn1js": ["asn1js@3.0.6", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA=="], @@ -423,6 +434,8 @@ "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "bare-addon-resolve": ["bare-addon-resolve@1.9.6", "", { "dependencies": { "bare-module-resolve": "^1.10.0", "bare-semver": "^1.0.0" }, "peerDependencies": { "bare-url": "*" }, "optionalPeers": ["bare-url"] }, "sha512-hvOQY1zDK6u0rSr27T6QlULoVLwi8J2k8HHHJlxSfT7XQdQ/7bsS+AnjYkHtu/TkL+gm3aMXAKucJkJAbrDG/g=="], "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], @@ -497,6 +510,8 @@ "bn.js": ["bn.js@5.2.2", "", {}, "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw=="], + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], "bs58": ["bs58@6.0.0", "", { "dependencies": { "base-x": "^5.0.0" } }, "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw=="], @@ -505,6 +520,8 @@ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], + "bufferutil": ["bufferutil@4.0.9", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw=="], "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], @@ -561,6 +578,10 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "compact2string": ["compact2string@1.4.1", "", { "dependencies": { "ipaddr.js": ">= 0.1.5" } }, "sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og=="], @@ -623,12 +644,18 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], + "ecpair": ["ecpair@2.1.0", "", { "dependencies": { "randombytes": "^2.1.0", "typeforce": "^1.18.0", "wif": "^2.0.6" } }, "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw=="], "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], "electron-to-chromium": ["electron-to-chromium@1.5.262", "", {}, "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], @@ -659,6 +686,8 @@ "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -697,6 +726,10 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], + + "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], @@ -721,14 +754,26 @@ "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "global": ["global@4.4.0", "", { "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" } }, "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w=="], + "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], + + "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], + + "googleapis": ["googleapis@170.1.0", "", { "dependencies": { "google-auth-library": "^10.2.0", "googleapis-common": "^8.0.0" } }, "sha512-RLbc7yG6qzZqvAmGcgjvNIoZ7wpcCFxtc+HN+46etxDrlO4a8l5Cb7NxNQGhV91oRmL7mt56VoRoypAtEQEIKg=="], + + "googleapis-common": ["googleapis-common@8.0.1", "", { "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" } }, "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "grammex": ["grammex@3.1.11", "", {}, "sha512-HNwLkgRg9SqTAd1N3Uh/MnKwTBTzwBxTOPbXQ8pb0tpwydjk90k4zRE8JUn9fMUiRwKtXFZ1TWFmms3dZHN+Fg=="], + "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], @@ -747,6 +792,8 @@ "http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "human-signals": ["human-signals@4.3.1", "", {}, "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ=="], "i": ["i@0.3.7", "", {}, "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q=="], @@ -775,6 +822,8 @@ "is-file": ["is-file@1.0.0", "", {}, "sha512-ZGMuc+xA8mRnrXtmtf2l/EkIW2zaD2LSBWlaOVEF6yH4RTndHob65V4SwWWdtGKVthQfXPVKsXqw4TDUjbVxVQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], @@ -793,6 +842,8 @@ "isomorphic-ws": ["isomorphic-ws@4.0.1", "", { "peerDependencies": { "ws": "*" } }, "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="], + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "join-async-iterator": ["join-async-iterator@1.1.1", "", {}, "sha512-ATse+nuNeKZ9K1y27LKdvPe/GCe9R/u9dw9vI248e+vILeRK3IcJP4JUPAlSmKRCDK0cKhEwfmiw4Skqx7UnGQ=="], @@ -811,6 +862,10 @@ "junk": ["junk@4.0.1", "", {}, "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], + + "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + "k-bucket": ["k-bucket@5.1.0", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Fac7iINEovXIWU20GPnOMLUbjctiS+cnmyjC4zAUgvs3XPf1vo9akfCHkigftSic/jiKqKl+KA3a/vFcJbHyCg=="], "k-rpc": ["k-rpc@5.1.0", "", { "dependencies": { "k-bucket": "^5.0.0", "k-rpc-socket": "^1.7.2", "randombytes": "^2.0.5" } }, "sha512-FGc+n70Hcjoa/X2JTwP+jMIOpBz+pkRffHnSl9yrYiwUxg3FIgD50+u1ePfJUOnRCnx6pbjmVk5aAeB1wIijuQ=="], @@ -865,8 +920,12 @@ "min-document": ["min-document@2.19.2", "", { "dependencies": { "dom-walk": "^0.1.0" } }, "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A=="], + "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -897,7 +956,7 @@ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], @@ -913,6 +972,8 @@ "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], @@ -923,10 +984,14 @@ "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parse-torrent": ["parse-torrent@11.0.19", "", { "dependencies": { "bencode": "^4.0.0", "cross-fetch-ponyfill": "^1.0.3", "get-stdin": "^9.0.0", "magnet-uri": "^7.0.7", "queue-microtask": "^1.2.3", "uint8-util": "^2.2.5" }, "bin": { "parse-torrent": "bin/cmd.js" } }, "sha512-T0lEkDdFVQsy0YxHIKjzDHSgt/yl57f3INs5jl7OZqAm77XDF0FgRgrv3LCKgSqsTOrMwYaF0t2761WKdvhgig=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "pbkdf2": ["pbkdf2@3.1.5", "", { "dependencies": { "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "ripemd160": "^2.0.3", "safe-buffer": "^5.2.1", "sha.js": "^2.4.12", "to-buffer": "^1.2.1" } }, "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ=="], @@ -991,6 +1056,8 @@ "qrcode-svg": ["qrcode-svg@1.1.0", "", { "bin": { "qrcode-svg": "bin/qrcode-svg.js" } }, "sha512-XyQCIXux1zEIA3NPb0AeR8UMYvXZzWEhgdBgBjH9gO7M48H9uoHzviNz8pXw3UzrAcxRRRn9gxHewAVK7bn9qw=="], + "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "queue-tick": ["queue-tick@1.0.1", "", {}, "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="], @@ -1037,6 +1104,8 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "ripemd160": ["ripemd160@2.0.3", "", { "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" } }, "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA=="], "rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="], @@ -1079,6 +1148,14 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], @@ -1103,10 +1180,18 @@ "streamx": ["streamx@2.22.1", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string2compact": ["string2compact@2.0.1", "", { "dependencies": { "addr-to-ip-port": "^2.0.0", "ipaddr.js": "^2.0.0" } }, "sha512-Bm/T8lHMTRXw+u83LE+OW7fXmC/wM+Mbccfdo533ajSBNxddDHlRrvxE49NdciGHgXkUQM5WYskJ7uTkbBUI0A=="], "string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="], + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], @@ -1179,6 +1264,8 @@ "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], + "url-template": ["url-template@2.0.8", "", {}, "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], @@ -1227,6 +1314,10 @@ "wif": ["wif@2.0.6", "", { "dependencies": { "bs58check": "<3.0.0" } }, "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ=="], + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], @@ -1319,7 +1410,7 @@ "c12/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - "cross-fetch-ponyfill/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "ethers/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="], @@ -1755,6 +1846,8 @@ "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], @@ -1771,6 +1864,12 @@ "socks/ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "utp-native/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -1779,6 +1878,12 @@ "wif/bs58check": ["bs58check@2.1.2", "", { "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", "safe-buffer": "^5.1.2" } }, "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@cardano-sdk/input-selection/@cardano-sdk/core/@cardano-sdk/crypto": ["@cardano-sdk/crypto@0.1.32", "", { "dependencies": { "@cardano-sdk/util": "~0.15.5", "blake2b": "^2.1.4", "i": "^0.3.7", "libsodium-wrappers-sumo": "^0.7.5", "lodash": "^4.17.21", "npm": "^9.3.0", "pbkdf2": "^3.1.2", "ts-custom-error": "^3.2.0", "ts-log": "^2.2.4" }, "peerDependencies": { "@dcspark/cardano-multiplatform-lib-asmjs": "^3.1.1", "@dcspark/cardano-multiplatform-lib-browser": "^3.1.1", "@dcspark/cardano-multiplatform-lib-nodejs": "^3.1.1" }, "optionalPeers": ["@dcspark/cardano-multiplatform-lib-asmjs", "@dcspark/cardano-multiplatform-lib-browser", "@dcspark/cardano-multiplatform-lib-nodejs"] }, "sha512-RCKFvkzD32QpKQ0jULADVRNmfBNkCwiZl2nlFbkZ3aCrfIex+6/2CizoagJ161fA7lL5/HGuzWfjOg3GX2ax6w=="], "@fabianbormann/meerkat/bs58check/@noble/hashes": ["@noble/hashes@1.3.2", "", {}, "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="], @@ -1855,12 +1960,18 @@ "npm/wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tar-stream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "utp-native/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "wif/bs58check/bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@meshsdk/web3-sdk/@meshsdk/core/@meshsdk/react/@meshsdk/web3-sdk": ["@meshsdk/web3-sdk@0.0.37", "", { "dependencies": { "@meshsdk/bitcoin": "1.9.0-beta.53", "@meshsdk/common": "1.9.0-beta.53", "@meshsdk/core-cst": "1.9.0-beta.53", "@meshsdk/wallet": "1.9.0-beta.53", "@peculiar/webcrypto": "^1.5.0", "axios": "^1.8.3", "base32-encoding": "^1.0.0", "uuid": "^11.1.0" } }, "sha512-uRG0jLjsa83JbPZqnVkec3gjvi0LEMiu1E6ItUALEnKUTTuhDOe3Cx4Ov1PbPTsYVsGRq61DCgzCNHSh2bXy+Q=="], "bl/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], diff --git a/package.json b/package.json index 0358260..6619c84 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "dagent-api", "scripts": { - "dev": "bun run --hot src/index.ts", + "dev": "PORT=4000 bun run --hot src/index.ts", "db:deploy": "bunx prisma migrate deploy && bunx prisma generate" }, "dependencies": { @@ -15,6 +15,7 @@ "chromadb": "^3.1.6", "dotenv": "^17.2.3", "ethers": "^6.15.0", + "googleapis": "^170.1.0", "hono": "^4.9.8", "pg": "^8.16.3", "prisma": "^7.0.1", diff --git a/src/controllers/agent.controller.ts b/src/controllers/agent.controller.ts index a9743e9..0f397c9 100644 --- a/src/controllers/agent.controller.ts +++ b/src/controllers/agent.controller.ts @@ -1,798 +1,906 @@ -import { Context } from "hono" -import { AgentService } from "../services/agent.service" -import { api_response } from "../lib/utils/parser" -import { errorMessageIncludes, getErrorMessage, getErrorStack } from "../lib/utils/error" -import { IAgentCreate, IAgentNameVerification } from "../lib/validators/agent.validator" -import { SessionService } from "../services/session.service" +import { Context } from "hono"; +import { AgentService } from "../services/agent.service"; +import { api_response } from "../lib/utils/parser"; +import { + errorMessageIncludes, + getErrorMessage, + getErrorStack, +} from "../lib/utils/error"; +import { + IAgentCreate, + IAgentNameVerification, +} from "../lib/validators/agent.validator"; +import { SessionService } from "../services/session.service"; export class AgentController { - public static readonly primary = async (c: Context) => { - try { - const { requirements, text, is_new_session } = await c.req.json(); - if (!text || typeof text !== 'string') { - return c.json(api_response({ - message: "text field is required and must be a string", - is_error: true - }), 400); - } - - // Get api_key from context (set by verifyApiKey middleware) - const api_key = await c.get("api_key"); - if (!api_key || !api_key.userId) { - return c.json( - api_response({ - message: "API key authentication required", - is_error: true - }), - 401 - ); - } - - // Clear session if starting a new session - if (is_new_session === true) { - await SessionService.clearAgentId(api_key.userId, api_key.id); - } - - // Get agent_id from Redis session storage - const agentId = await SessionService.getAgentId(api_key.userId, api_key.id) - - console.log("agentId ALA re", agentId); - let agentResponse; - if (!agentId) { - agentResponse = await AgentService.primary(c, { - requirement_json: requirements, - message: text.trim(), - }); - } else { - agentResponse = await AgentService.primary(c, { agent_id: agentId, message: text.trim() }); - // Refresh session TTL on each request - await SessionService.refreshSession(api_key.userId, api_key.id); - } - return c.json(api_response({ message: "Agent response", data: agentResponse })); - - } catch (error) { - const errorMessage = getErrorMessage(error); - const errorStack = getErrorStack(error); - - console.error("Error in primary agent method:", { - error: errorMessage, - stack: errorStack, - timestamp: new Date().toISOString() - }); - - // Handle specific error types - if (errorMessageIncludes(error, "Agent URL or provider not found") || errorMessageIncludes(error, "Agent not found")) { - return c.json( - api_response({ - message: "Agent not found or not properly configured", - is_error: true - }), - 404 - ); - } - - if (errorMessageIncludes(error, "insufficient balance") || errorMessageIncludes(error, "payment")) { - return c.json( - api_response({ - message: errorMessage, - is_error: true - }), - 402 // Payment Required - ); - } - - if (errorMessageIncludes(error, "unauthorized") || errorMessageIncludes(error, "authentication")) { - return c.json( - api_response({ - message: "Authentication required to access agent", - is_error: true - }), - 401 - ); - } - - if (errorMessageIncludes(error, "rate limit") || errorMessageIncludes(error, "too many requests")) { - return c.json( - api_response({ - message: "Rate limit exceeded. Please try again later.", - is_error: true - }), - 429 - ); - } - - if (errorMessageIncludes(error, "timeout") || errorMessageIncludes(error, "connection")) { - return c.json( - api_response({ - message: "Agent service temporarily unavailable. Please try again later.", - is_error: true - }), - 503 - ); - } - - // Generic server error - return c.json( - api_response({ - message: "Failed to process agent request. Please try again later.", - is_error: true - }), - 500 - ); - } + public static readonly primary = async (c: Context) => { + try { + const { requirements, text, is_new_session } = await c.req.json(); + if (!text || typeof text !== "string") { + return c.json( + api_response({ + message: "text field is required and must be a string", + is_error: true, + }), + 400, + ); + } + + // Get api_key from context (set by verifyApiKey middleware) + const api_key = await c.get("api_key"); + if (!api_key || !api_key.userId) { + return c.json( + api_response({ + message: "API key authentication required", + is_error: true, + }), + 401, + ); + } + + // Clear session if starting a new session + if (is_new_session === true) { + await SessionService.clearAgentId(api_key.userId, api_key.id); + } + + // Get agent_id from Redis session storage + const agentId = await SessionService.getAgentId( + api_key.userId, + api_key.id, + ); + + console.log("agentId ALA re", agentId); + let agentResponse; + if (!agentId) { + agentResponse = await AgentService.primary(c, { + requirement_json: requirements, + message: text.trim(), + }); + } else { + agentResponse = await AgentService.primary(c, { + agent_id: agentId, + message: text.trim(), + }); + // Refresh session TTL on each request + await SessionService.refreshSession(api_key.userId, api_key.id); + } + return c.json( + api_response({ message: "Agent response", data: agentResponse }), + ); + } catch (error) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); + + console.error("Error in primary agent method:", { + error: errorMessage, + stack: errorStack, + timestamp: new Date().toISOString(), + }); + + // Handle specific error types + if ( + errorMessageIncludes(error, "Agent URL or provider not found") || + errorMessageIncludes(error, "Agent not found") + ) { + return c.json( + api_response({ + message: "Agent not found or not properly configured", + is_error: true, + }), + 404, + ); + } + + if ( + errorMessageIncludes(error, "insufficient balance") || + errorMessageIncludes(error, "payment") + ) { + return c.json( + api_response({ + message: errorMessage, + is_error: true, + }), + 402, // Payment Required + ); + } + + if ( + errorMessageIncludes(error, "unauthorized") || + errorMessageIncludes(error, "authentication") + ) { + return c.json( + api_response({ + message: "Authentication required to access agent", + is_error: true, + }), + 401, + ); + } + + if ( + errorMessageIncludes(error, "rate limit") || + errorMessageIncludes(error, "too many requests") + ) { + return c.json( + api_response({ + message: "Rate limit exceeded. Please try again later.", + is_error: true, + }), + 429, + ); + } + + if ( + errorMessageIncludes(error, "timeout") || + errorMessageIncludes(error, "connection") + ) { + return c.json( + api_response({ + message: + "Agent service temporarily unavailable. Please try again later.", + is_error: true, + }), + 503, + ); + } + + // Generic server error + return c.json( + api_response({ + message: "Failed to process agent request. Please try again later.", + is_error: true, + }), + 500, + ); } - - public static readonly verifyAgent = async (c: Context) => { - try { - const { deployedUrl, default_agent_name } = (await c.req.json()) as IAgentNameVerification; - if (!deployedUrl && !default_agent_name) { - return c.json(api_response({ message: 'deployedUrl and default_agent_name are required' }), 400); - } - await AgentService.verifyAgent(deployedUrl, default_agent_name); - return c.json(api_response({ message: 'Agent URL verified successfully', data: true }), 200); - } catch (error) { - if (error instanceof Error) { - return c.json(api_response({ message: error.message, data: false, is_error: true }), 500); - } - } + }; + + public static readonly verifyAgent = async (c: Context) => { + try { + const { deployedUrl, default_agent_name } = + (await c.req.json()) as IAgentNameVerification; + if (!deployedUrl && !default_agent_name) { + return c.json( + api_response({ + message: "deployedUrl and default_agent_name are required", + }), + 400, + ); + } + await AgentService.verifyAgent(deployedUrl, default_agent_name); + return c.json( + api_response({ + message: "Agent URL verified successfully", + data: true, + }), + 200, + ); + } catch (error) { + if (error instanceof Error) { + return c.json( + api_response({ message: error.message, data: false, is_error: true }), + 500, + ); + } } - - public static readonly createAgent = async (c: Context) => { - try { - // Input validation - let requestBody; - try { - requestBody = await c.req.json(); - } catch (parseError) { - console.error("Invalid JSON in createAgent request:", parseError); - return c.json( - api_response({ - message: "Invalid JSON format in request body", - is_error: true - }), - 400 - ); - } - - const { - name, - description, - agentCost, - deployedUrl, - llmProvider, - skills, - is_multiAgentSystem, - default_agent_name, - framework_used, - can_stream, - } = requestBody as IAgentCreate; - - // Validate required fields - const requiredStringFields = { - name: { value: name, maxLength: 100 }, - description: { value: description, maxLength: 1000 }, - agentCost: { value: agentCost, maxLength: 50 }, - deployedUrl: { value: deployedUrl, maxLength: 500 }, - llmProvider: { value: llmProvider, maxLength: 100 } - }; - - for (const [fieldName, config] of Object.entries(requiredStringFields)) { - const { value, maxLength } = config; - if (!value || typeof value !== 'string') { - return c.json( - api_response({ - message: `${fieldName} is required and must be a non-empty string`, - is_error: true - }), - 400 - ); - } - - if (value.trim().length === 0) { - return c.json( - api_response({ - message: `${fieldName} cannot be empty`, - is_error: true - }), - 400 - ); - } - - // if (value.length > maxLength) { - // return c.json( - // api_response({ - // message: `${fieldName} must be ${maxLength} characters or less`, - // is_error: true - // }), - // 400 - // ); - // } - } - - // Validate URL format - try { - new URL(deployedUrl); - } catch { - return c.json( - api_response({ - message: "deployedUrl must be a valid URL", - is_error: true - }), - 400 - ); - } - - // Validate skills array - if (!Array.isArray(skills)) { - return c.json( - api_response({ - message: "skills must be an array", - is_error: true - }), - 400 - ); - } - - if (skills.length === 0) { - return c.json( - api_response({ - message: "skills array cannot be empty", - is_error: true - }), - 400 - ); - } - - if (!skills.every(skill => typeof skill === 'string' && skill.trim().length > 0)) { - return c.json( - api_response({ - message: "all skills must be non-empty strings", - is_error: true - }), - 400 - ); - } - - // Validate boolean fields - if (typeof is_multiAgentSystem !== 'boolean') { - return c.json( - api_response({ - message: "is_multiAgentSystem must be a boolean", - is_error: true - }), - 400 - ); - } - - if (typeof can_stream !== 'boolean') { - return c.json( - api_response({ - message: "can_stream must be a boolean", - is_error: true - }), - 400 - ); - } - - // Validate numeric fields - let numericCost: number; - if (typeof agentCost === 'number') { - numericCost = agentCost; - } else if (typeof agentCost === 'string') { - numericCost = parseFloat(agentCost); - } else { - return c.json( - api_response({ - message: "agentCost must be a valid non-negative number", - is_error: true - }), - 400 - ); - } - if (isNaN(numericCost) || numericCost < 0) { - return c.json( - api_response({ - message: "agentCost must be a valid non-negative number", - is_error: true - }), - 400 - ); - } - - // Get user from context - const user = await c.get('user'); - if (!user || !user.id) { - console.error("User not found in context for createAgent"); - return c.json( - api_response({ - message: "User authentication required", - is_error: true - }), - 401 - ); - } - - const agent = await AgentService.createAgent(user.id, { - name: name.trim(), - description: description.trim(), - agentCost: agentCost.toString(), - deployedUrl: deployedUrl.trim(), - llmProvider: llmProvider.trim(), - skills: skills.map(skill => skill.trim()), - is_multiAgentSystem, - default_agent_name: default_agent_name?.trim() || '', - framework_used: framework_used?.trim() || '', - can_stream - }); - - return c.json(api_response({ message: "Agent created successfully", data: agent })); - } catch (error) { - const errorMessage = getErrorMessage(error); - const errorStack = getErrorStack(error); - - console.error("Error in createAgent:", { - error: errorMessage, - stack: errorStack, - timestamp: new Date().toISOString() - }); - - // Handle specific error types - if (errorMessageIncludes(error, "duplicate") || errorMessageIncludes(error, "already exists")) { - return c.json( - api_response({ - message: "Agent with this name or URL already exists", - is_error: true - }), - 409 // Conflict - ); - } - - if (errorMessageIncludes(error, "wallet not found") || errorMessageIncludes(error, "user stake")) { - return c.json( - api_response({ - message: errorMessage, - is_error: true - }), - 404 - ); - } - - if (errorMessageIncludes(error, "insufficient balance") || errorMessageIncludes(error, "stake must be greater than 0")) { - return c.json( - api_response({ - message: errorMessage, - is_error: true - }), - 402 // Payment Required - ); - } - - if (errorMessageIncludes(error, "embedding") || errorMessageIncludes(error, "generation failed")) { - return c.json( - api_response({ - message: "Failed to generate agent embedding. Please try again later.", - is_error: true - }), - 503 - ); - } - - if (errorMessageIncludes(error, "contract") || errorMessageIncludes(error, "blockchain")) { - return c.json( - api_response({ - message: "Failed to register agent on blockchain. Please try again later.", - is_error: true - }), - 503 - ); - } - - // Generic server error - return c.json( - api_response({ - message: "Failed to create agent. Please try again later.", - is_error: true - }), - 500 - ); + }; + + public static readonly createAgent = async (c: Context) => { + try { + // Input validation + let requestBody; + try { + requestBody = await c.req.json(); + } catch (parseError) { + console.error("Invalid JSON in createAgent request:", parseError); + return c.json( + api_response({ + message: "Invalid JSON format in request body", + is_error: true, + }), + 400, + ); + } + + const { + name, + description, + agentCost, + deployedUrl, + llmProvider, + skills, + is_multiAgentSystem, + default_agent_name, + framework_used, + can_stream, + } = requestBody as IAgentCreate; + + // Validate required fields + const requiredStringFields = { + name: { value: name, maxLength: 100 }, + description: { value: description, maxLength: 1000 }, + agentCost: { value: agentCost, maxLength: 50 }, + deployedUrl: { value: deployedUrl, maxLength: 500 }, + llmProvider: { value: llmProvider, maxLength: 100 }, + }; + + for (const [fieldName, config] of Object.entries(requiredStringFields)) { + const { value, maxLength } = config; + if (!value || typeof value !== "string") { + return c.json( + api_response({ + message: `${fieldName} is required and must be a non-empty string`, + is_error: true, + }), + 400, + ); } - } - public static readonly getAllAgents = async (c: Context) => { - try { - // Get user from context (optional for public agents) - const user = await c.get('user'); - const user_id = user?.id; - - const agents = await AgentService.getAllAgents(user_id); - return c.json(api_response({ message: "Agents retrieved successfully", data: agents })); - } catch (error) { - const errorMessage = getErrorMessage(error); - const errorStack = getErrorStack(error); - - console.error("Error in getAllAgents:", { - error: errorMessage, - stack: errorStack, - timestamp: new Date().toISOString() - }); - - // Generic server error - return c.json( - api_response({ - message: "Failed to retrieve agents. Please try again later.", - is_error: true - }), - 500 - ); + if (value.trim().length === 0) { + return c.json( + api_response({ + message: `${fieldName} cannot be empty`, + is_error: true, + }), + 400, + ); } - }; - public static readonly getAgent = async (c: Context) => { - try { - const agent_id = c.req.param('id'); - - if (!agent_id || typeof agent_id !== 'string' || agent_id.trim().length === 0) { - return c.json( - api_response({ - message: "agent_id is required and must be a non-empty string", - is_error: true - }), - 400 - ); - } - - const agent = await AgentService.getAgent(agent_id.trim()); - return c.json(api_response({ message: "Agent retrieved successfully", data: agent })); - } catch (error) { - const errorMessage = getErrorMessage(error); - const errorStack = getErrorStack(error); - - console.error("Error in getAgent:", { - error: errorMessage, - stack: errorStack, - timestamp: new Date().toISOString() - }); - - // Handle specific error types - if (errorMessageIncludes(error, "not found")) { - return c.json( - api_response({ - message: "Agent not found", - is_error: true - }), - 404 - ); - } - - // Generic server error - return c.json( - api_response({ - message: "Failed to retrieve agent. Please try again later.", - is_error: true - }), - 500 - ); + // if (value.length > maxLength) { + // return c.json( + // api_response({ + // message: `${fieldName} must be ${maxLength} characters or less`, + // is_error: true + // }), + // 400 + // ); + // } + } + + // Validate URL format + try { + new URL(deployedUrl); + } catch { + return c.json( + api_response({ + message: "deployedUrl must be a valid URL", + is_error: true, + }), + 400, + ); + } + + // Validate skills array + if (!Array.isArray(skills)) { + return c.json( + api_response({ + message: "skills must be an array", + is_error: true, + }), + 400, + ); + } + + if (skills.length === 0) { + return c.json( + api_response({ + message: "skills array cannot be empty", + is_error: true, + }), + 400, + ); + } + + if ( + !skills.every( + (skill) => typeof skill === "string" && skill.trim().length > 0, + ) + ) { + return c.json( + api_response({ + message: "all skills must be non-empty strings", + is_error: true, + }), + 400, + ); + } + + // Validate boolean fields + if (typeof is_multiAgentSystem !== "boolean") { + return c.json( + api_response({ + message: "is_multiAgentSystem must be a boolean", + is_error: true, + }), + 400, + ); + } + + if (typeof can_stream !== "boolean") { + return c.json( + api_response({ + message: "can_stream must be a boolean", + is_error: true, + }), + 400, + ); + } + + // Validate numeric fields + let numericCost: number; + if (typeof agentCost === "number") { + numericCost = agentCost; + } else if (typeof agentCost === "string") { + numericCost = parseFloat(agentCost); + } else { + return c.json( + api_response({ + message: "agentCost must be a valid non-negative number", + is_error: true, + }), + 400, + ); + } + if (isNaN(numericCost) || numericCost < 0) { + return c.json( + api_response({ + message: "agentCost must be a valid non-negative number", + is_error: true, + }), + 400, + ); + } + + // Get user from context + const user = await c.get("user"); + if (!user || !user.id) { + console.error("User not found in context for createAgent"); + return c.json( + api_response({ + message: "User authentication required", + is_error: true, + }), + 401, + ); + } + + const agent = await AgentService.createAgent(user.id, { + name: name.trim(), + description: description.trim(), + agentCost: agentCost.toString(), + deployedUrl: deployedUrl.trim(), + llmProvider: llmProvider.trim(), + skills: skills.map((skill) => skill.trim()), + is_multiAgentSystem, + default_agent_name: default_agent_name?.trim() || "", + framework_used: framework_used?.trim() || "", + can_stream, + }); + + return c.json( + api_response({ message: "Agent created successfully", data: agent }), + ); + } catch (error) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); + + console.error("Error in createAgent:", { + error: errorMessage, + stack: errorStack, + timestamp: new Date().toISOString(), + }); + + // Handle specific error types + if ( + errorMessageIncludes(error, "duplicate") || + errorMessageIncludes(error, "already exists") + ) { + return c.json( + api_response({ + message: "Agent with this name or URL already exists", + is_error: true, + }), + 409, // Conflict + ); + } + + if ( + errorMessageIncludes(error, "wallet not found") || + errorMessageIncludes(error, "user stake") + ) { + return c.json( + api_response({ + message: errorMessage, + is_error: true, + }), + 404, + ); + } + + if ( + errorMessageIncludes(error, "insufficient balance") || + errorMessageIncludes(error, "stake must be greater than 0") + ) { + return c.json( + api_response({ + message: errorMessage, + is_error: true, + }), + 402, // Payment Required + ); + } + + if ( + errorMessageIncludes(error, "embedding") || + errorMessageIncludes(error, "generation failed") + ) { + return c.json( + api_response({ + message: + "Failed to generate agent embedding. Please try again later.", + is_error: true, + }), + 503, + ); + } + + if ( + errorMessageIncludes(error, "contract") || + errorMessageIncludes(error, "blockchain") + ) { + return c.json( + api_response({ + message: + "Failed to register agent on blockchain. Please try again later.", + is_error: true, + }), + 503, + ); + } + + // Generic server error + return c.json( + api_response({ + message: "Failed to create agent. Please try again later.", + is_error: true, + }), + 500, + ); + } + }; + + public static readonly getAllAgents = async (c: Context) => { + try { + // Get user from context (optional for public agents) + const user = await c.get("user"); + const user_id = user?.id; + + const agents = await AgentService.getAllAgents(user_id); + return c.json( + api_response({ + message: "Agents retrieved successfully", + data: agents, + }), + ); + } catch (error) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); + + console.error("Error in getAllAgents:", { + error: errorMessage, + stack: errorStack, + timestamp: new Date().toISOString(), + }); + + // Generic server error + return c.json( + api_response({ + message: "Failed to retrieve agents. Please try again later.", + is_error: true, + }), + 500, + ); + } + }; + + public static readonly getAgent = async (c: Context) => { + try { + const agent_id = c.req.param("id"); + + if ( + !agent_id || + typeof agent_id !== "string" || + agent_id.trim().length === 0 + ) { + return c.json( + api_response({ + message: "agent_id is required and must be a non-empty string", + is_error: true, + }), + 400, + ); + } + + const agent = await AgentService.getAgent(agent_id.trim()); + return c.json( + api_response({ message: "Agent retrieved successfully", data: agent }), + ); + } catch (error) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); + + console.error("Error in getAgent:", { + error: errorMessage, + stack: errorStack, + timestamp: new Date().toISOString(), + }); + + // Handle specific error types + if (errorMessageIncludes(error, "not found")) { + return c.json( + api_response({ + message: "Agent not found", + is_error: true, + }), + 404, + ); + } + + // Generic server error + return c.json( + api_response({ + message: "Failed to retrieve agent. Please try again later.", + is_error: true, + }), + 500, + ); + } + }; + + public static readonly updateAgent = async (c: Context) => { + try { + const agent_id = c.req.param("id"); + + if ( + !agent_id || + typeof agent_id !== "string" || + agent_id.trim().length === 0 + ) { + return c.json( + api_response({ + message: "agent_id is required and must be a non-empty string", + is_error: true, + }), + 400, + ); + } + + // Input validation + let requestBody; + try { + requestBody = await c.req.json(); + } catch (parseError) { + console.error("Invalid JSON in updateAgent request:", parseError); + return c.json( + api_response({ + message: "Invalid JSON format in request body", + is_error: true, + }), + 400, + ); + } + + const { + name, + description, + agentCost, + deployedUrl, + llmProvider, + isActive, + isPublic, + } = requestBody; + + // Validate optional fields if provided + const updateData: any = {}; + + if (name !== undefined) { + if (typeof name !== "string" || name.trim().length === 0) { + return c.json( + api_response({ + message: "name must be a non-empty string", + is_error: true, + }), + 400, + ); } - }; - - public static readonly updateAgent = async (c: Context) => { - try { - const agent_id = c.req.param('id'); - - if (!agent_id || typeof agent_id !== 'string' || agent_id.trim().length === 0) { - return c.json( - api_response({ - message: "agent_id is required and must be a non-empty string", - is_error: true - }), - 400 - ); - } - - // Input validation - let requestBody; - try { - requestBody = await c.req.json(); - } catch (parseError) { - console.error("Invalid JSON in updateAgent request:", parseError); - return c.json( - api_response({ - message: "Invalid JSON format in request body", - is_error: true - }), - 400 - ); - } - - const { - name, - description, - agentCost, - deployedUrl, - llmProvider, - isActive, - isPublic - } = requestBody; - - // Validate optional fields if provided - const updateData: any = {}; - - if (name !== undefined) { - if (typeof name !== 'string' || name.trim().length === 0) { - return c.json( - api_response({ - message: "name must be a non-empty string", - is_error: true - }), - 400 - ); - } - if (name.length > 100) { - return c.json( - api_response({ - message: "name must be 100 characters or less", - is_error: true - }), - 400 - ); - } - updateData.name = name.trim(); - } - - if (description !== undefined) { - if (typeof description !== 'string' || description.trim().length === 0) { - return c.json( - api_response({ - message: "description must be a non-empty string", - is_error: true - }), - 400 - ); - } - if (description.length > 1000) { - return c.json( - api_response({ - message: "description must be 1000 characters or less", - is_error: true - }), - 400 - ); - } - updateData.description = description.trim(); - } - - if (agentCost !== undefined) { - if (typeof agentCost !== 'string') { - return c.json( - api_response({ - message: "agentCost must be a string", - is_error: true - }), - 400 - ); - } - const numericCost = parseFloat(agentCost); - if (isNaN(numericCost) || numericCost < 0) { - return c.json( - api_response({ - message: "agentCost must be a valid non-negative number", - is_error: true - }), - 400 - ); - } - updateData.agentCost = agentCost; - } - - if (deployedUrl !== undefined) { - if (typeof deployedUrl !== 'string' || deployedUrl.trim().length === 0) { - return c.json( - api_response({ - message: "deployedUrl must be a non-empty string", - is_error: true - }), - 400 - ); - } - try { - new URL(deployedUrl); - } catch { - return c.json( - api_response({ - message: "deployedUrl must be a valid URL", - is_error: true - }), - 400 - ); - } - updateData.deployedUrl = deployedUrl.trim(); - } - - if (llmProvider !== undefined) { - if (typeof llmProvider !== 'string' || llmProvider.trim().length === 0) { - return c.json( - api_response({ - message: "llmProvider must be a non-empty string", - is_error: true - }), - 400 - ); - } - updateData.llmProvider = llmProvider.trim(); - } - - if (isActive !== undefined) { - if (typeof isActive !== 'boolean') { - return c.json( - api_response({ - message: "isActive must be a boolean", - is_error: true - }), - 400 - ); - } - updateData.isActive = isActive; - } - - if (isPublic !== undefined) { - if (typeof isPublic !== 'boolean') { - return c.json( - api_response({ - message: "isPublic must be a boolean", - is_error: true - }), - 400 - ); - } - updateData.isPublic = isPublic; - } - - // Check if at least one field is being updated - if (Object.keys(updateData).length === 0) { - return c.json( - api_response({ - message: "At least one field must be provided for update", - is_error: true - }), - 400 - ); - } - - // Get user from context - const user = await c.get('user'); - if (!user || !user.id) { - console.error("User not found in context for updateAgent"); - return c.json( - api_response({ - message: "User authentication required", - is_error: true - }), - 401 - ); - } - - const agent = await AgentService.updateAgent(agent_id.trim(), user.id, updateData); - return c.json(api_response({ message: "Agent updated successfully", data: agent })); - } catch (error) { - const errorMessage = getErrorMessage(error); - const errorStack = getErrorStack(error); - - console.error("Error in updateAgent:", { - error: errorMessage, - stack: errorStack, - timestamp: new Date().toISOString() - }); - - // Handle specific error types - if (errorMessageIncludes(error, "not found")) { - return c.json( - api_response({ - message: "Agent not found", - is_error: true - }), - 404 - ); - } - - if (errorMessageIncludes(error, "unauthorized")) { - return c.json( - api_response({ - message: "Unauthorized to update this agent", - is_error: true - }), - 403 - ); - } - - if (errorMessageIncludes(error, "embedding")) { - return c.json( - api_response({ - message: "Failed to update agent embedding. Please try again later.", - is_error: true - }), - 503 - ); - } - - // Generic server error - return c.json( - api_response({ - message: "Failed to update agent. Please try again later.", - is_error: true - }), - 500 - ); + if (name.length > 100) { + return c.json( + api_response({ + message: "name must be 100 characters or less", + is_error: true, + }), + 400, + ); + } + updateData.name = name.trim(); + } + + if (description !== undefined) { + if ( + typeof description !== "string" || + description.trim().length === 0 + ) { + return c.json( + api_response({ + message: "description must be a non-empty string", + is_error: true, + }), + 400, + ); + } + if (description.length > 1000) { + return c.json( + api_response({ + message: "description must be 1000 characters or less", + is_error: true, + }), + 400, + ); + } + updateData.description = description.trim(); + } + + if (agentCost !== undefined) { + if (typeof agentCost !== "string") { + return c.json( + api_response({ + message: "agentCost must be a string", + is_error: true, + }), + 400, + ); + } + const numericCost = parseFloat(agentCost); + if (isNaN(numericCost) || numericCost < 0) { + return c.json( + api_response({ + message: "agentCost must be a valid non-negative number", + is_error: true, + }), + 400, + ); + } + updateData.agentCost = agentCost; + } + + if (deployedUrl !== undefined) { + if ( + typeof deployedUrl !== "string" || + deployedUrl.trim().length === 0 + ) { + return c.json( + api_response({ + message: "deployedUrl must be a non-empty string", + is_error: true, + }), + 400, + ); } - }; - - public static readonly deleteAgent = async (c: Context) => { try { - const agent_id = c.req.param('id'); - - if (!agent_id || typeof agent_id !== 'string' || agent_id.trim().length === 0) { - return c.json( - api_response({ - message: "agent_id is required and must be a non-empty string", - is_error: true - }), - 400 - ); - } - - // Get user from context - const user = await c.get('user'); - if (!user || !user.id) { - console.error("User not found in context for deleteAgent"); - return c.json( - api_response({ - message: "User authentication required", - is_error: true - }), - 401 - ); - } - - const agent = await AgentService.deleteAgent(agent_id.trim(), user.id); - return c.json(api_response({ message: "Agent deleted successfully", data: agent })); - } catch (error) { - const errorMessage = getErrorMessage(error); - const errorStack = getErrorStack(error); - - console.error("Error in deleteAgent:", { - error: errorMessage, - stack: errorStack, - timestamp: new Date().toISOString() - }); - - // Handle specific error types - if (errorMessageIncludes(error, "not found")) { - return c.json( - api_response({ - message: "Agent not found", - is_error: true - }), - 404 - ); - } - - if (errorMessageIncludes(error, "unauthorized")) { - return c.json( - api_response({ - message: "Unauthorized to delete this agent", - is_error: true - }), - 403 - ); - } - - // Generic server error - return c.json( - api_response({ - message: "Failed to delete agent. Please try again later.", - is_error: true - }), - 500 - ); + new URL(deployedUrl); + } catch { + return c.json( + api_response({ + message: "deployedUrl must be a valid URL", + is_error: true, + }), + 400, + ); + } + updateData.deployedUrl = deployedUrl.trim(); + } + + if (llmProvider !== undefined) { + if ( + typeof llmProvider !== "string" || + llmProvider.trim().length === 0 + ) { + return c.json( + api_response({ + message: "llmProvider must be a non-empty string", + is_error: true, + }), + 400, + ); + } + updateData.llmProvider = llmProvider.trim(); + } + + if (isActive !== undefined) { + if (typeof isActive !== "boolean") { + return c.json( + api_response({ + message: "isActive must be a boolean", + is_error: true, + }), + 400, + ); } - }; -} \ No newline at end of file + updateData.isActive = isActive; + } + + if (isPublic !== undefined) { + if (typeof isPublic !== "boolean") { + return c.json( + api_response({ + message: "isPublic must be a boolean", + is_error: true, + }), + 400, + ); + } + updateData.isPublic = isPublic; + } + + // Check if at least one field is being updated + if (Object.keys(updateData).length === 0) { + return c.json( + api_response({ + message: "At least one field must be provided for update", + is_error: true, + }), + 400, + ); + } + + // Get user from context + const user = await c.get("user"); + if (!user || !user.id) { + console.error("User not found in context for updateAgent"); + return c.json( + api_response({ + message: "User authentication required", + is_error: true, + }), + 401, + ); + } + + const agent = await AgentService.updateAgent( + agent_id.trim(), + user.id, + updateData, + ); + return c.json( + api_response({ message: "Agent updated successfully", data: agent }), + ); + } catch (error) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); + + console.error("Error in updateAgent:", { + error: errorMessage, + stack: errorStack, + timestamp: new Date().toISOString(), + }); + + // Handle specific error types + if (errorMessageIncludes(error, "not found")) { + return c.json( + api_response({ + message: "Agent not found", + is_error: true, + }), + 404, + ); + } + + if (errorMessageIncludes(error, "unauthorized")) { + return c.json( + api_response({ + message: "Unauthorized to update this agent", + is_error: true, + }), + 403, + ); + } + + if (errorMessageIncludes(error, "embedding")) { + return c.json( + api_response({ + message: + "Failed to update agent embedding. Please try again later.", + is_error: true, + }), + 503, + ); + } + + // Generic server error + return c.json( + api_response({ + message: "Failed to update agent. Please try again later.", + is_error: true, + }), + 500, + ); + } + }; + + public static readonly deleteAgent = async (c: Context) => { + try { + const agent_id = c.req.param("id"); + + if ( + !agent_id || + typeof agent_id !== "string" || + agent_id.trim().length === 0 + ) { + return c.json( + api_response({ + message: "agent_id is required and must be a non-empty string", + is_error: true, + }), + 400, + ); + } + + // Get user from context + const user = await c.get("user"); + if (!user || !user.id) { + console.error("User not found in context for deleteAgent"); + return c.json( + api_response({ + message: "User authentication required", + is_error: true, + }), + 401, + ); + } + + const agent = await AgentService.deleteAgent(agent_id.trim(), user.id); + return c.json( + api_response({ message: "Agent deleted successfully", data: agent }), + ); + } catch (error) { + const errorMessage = getErrorMessage(error); + const errorStack = getErrorStack(error); + + console.error("Error in deleteAgent:", { + error: errorMessage, + stack: errorStack, + timestamp: new Date().toISOString(), + }); + + // Handle specific error types + if (errorMessageIncludes(error, "not found")) { + return c.json( + api_response({ + message: "Agent not found", + is_error: true, + }), + 404, + ); + } + + if (errorMessageIncludes(error, "unauthorized")) { + return c.json( + api_response({ + message: "Unauthorized to delete this agent", + is_error: true, + }), + 403, + ); + } + + // Generic server error + return c.json( + api_response({ + message: "Failed to delete agent. Please try again later.", + is_error: true, + }), + 500, + ); + } + }; +} diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 5cd503c..00f3836 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -3,47 +3,43 @@ import { AuthService } from "../services/auth.service"; import { DataSignature } from "@meshsdk/core"; export class AuthController { - public static readonly sendNonce = async (c: Context) => { - try { - const { address } = await c.req.json(); + public static readonly GoogleOAuth = async (c: Context) => { + try { + const { url } = await AuthService.GoogleSignin(c); + return c.redirect(url); + } catch (error) { + return c.json( + { + error: "Failed to generate OauthURl", + message: error instanceof Error ? error.message : "Unknown error", + }, + 500, + ); + } + }; + public static readonly GoogleCallback = async (c: Context) => { + try { + const { code, state } = c.req.query(); + console.log("code, " ,code) + if (!code || !state) { + return c.json( + { + error: "Authorization code or state is missing", + }, + 400, + ); + } - if (!address || typeof address !== "string") { - return c.json({ - error: "address is required and must be a string" - }, 400); - } - - const result = await AuthService.sendNonce(address); - return c.json(result); - } catch (error) { - return c.json({ - error: "Failed to generate nonce", - message: error instanceof Error ? error.message : 'Unknown error' - }, 500); - } - }; - - public static readonly verifyNonce = async (c: Context) => { - try { - const { address, signature } = await c.req.json(); - - if (!address || typeof address !== "string") { - return c.json({ - error: "userAddress is required and must be a string" - }, 400); - } - - if (!signature) { - return c.json({ error: "signature is required" }, 400); - } - - const result = await AuthService.verifyNonce(c, address, signature as DataSignature); - return c.json(result); - } catch (error) { - return c.json({ - error: "Verification failed", - message: error instanceof Error ? error.message : 'Unknown error' - }, 500); - } - }; + const user = await AuthService.GoogleCallback(c, code, state); + return c.json({ message: "User signed in" }); + } catch (error) { + return c.json( + { + error: "google Signin Failed", + message: error instanceof Error ? error.message : "Unknown error", + }, + 500, + ); + } + }; } diff --git a/src/index.ts b/src/index.ts index b1f7c17..26a95ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import { config } from './lib/env'; -import { showRoutes } from 'hono/dev'; -import Routes from './routes/index'; -import { logger } from 'hono/logger'; -import { poweredBy } from 'hono/powered-by'; -import { prettyJSON } from 'hono/pretty-json'; +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { config } from "./lib/env"; +import { showRoutes } from "hono/dev"; +import Routes from "./routes/index"; +import { logger } from "hono/logger"; +import { poweredBy } from "hono/powered-by"; +import { prettyJSON } from "hono/pretty-json"; interface JWTPayload { sub: string; @@ -16,7 +16,7 @@ interface JWTPayload { const app = new Hono<{ Variables: { user: JWTPayload | null; - } + }; }>(); app.use(prettyJSON()); @@ -26,7 +26,15 @@ app.use(logger()); app.use( "*", cors({ - origin: ["http://localhost:5173", "http://localhost:3000", "http://localhost:3001", "http://localhost:3002", "https://dagent.dev", "https://web.dagent.dev", "https://www.dagent.dev"], + origin: [ + "http://localhost:5173", + "http://localhost:3000", + "http://localhost:3001", + "http://localhost:3002", + "https://dagent.dev", + "https://web.dagent.dev", + "https://www.dagent.dev", + ], allowHeaders: ["Content-Type", "Authorization", "x-api-key"], allowMethods: ["POST", "GET", "OPTIONS", "PUT", "DELETE"], exposeHeaders: ["Content-Length", "x-api-key"], @@ -35,8 +43,8 @@ app.use( }), ); -app.route('/', Routes); +app.route("/", Routes); showRoutes(app); -export default app +export default app; diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 212c985..5112ec1 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -1,10 +1,10 @@ import "dotenv/config"; -import { PrismaPg } from '@prisma/adapter-pg' -import { PrismaClient } from '../../generated/prisma' +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "../../generated/prisma"; -const connectionString = `${process.env.DATABASE_URL}` +const connectionString = `${process.env.DATABASE_URL}`; -const adapter = new PrismaPg({ connectionString }) -const prisma = new PrismaClient({ adapter }) +const adapter = new PrismaPg({ connectionString }); +const prisma = new PrismaClient({ adapter }); -export { prisma } \ No newline at end of file +export { prisma }; diff --git a/src/lib/env.ts b/src/lib/env.ts index d87645f..41bf163 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -1,17 +1,20 @@ -import { z } from 'zod'; +import { z } from "zod"; export const env = z.object({ - BETTER_AUTH_SECRET: z.string(), // Required by better-auth for apiKey plugin - FRONTEND_URL: z.string(), - DATABASE_URL: z.url(), - CLOUDFLARE_ACCOUNT_ID: z.string(), - CLOUDFLARE_API_TOKEN: z.string(), - JWT_SECRET: z.string(), - UPSTASH_REDIS_REST_URL: z.string().optional(), // Upstash Redis REST URL - UPSTASH_REDIS_REST_TOKEN: z.string().optional(), // Upstash Redis REST Token - CF_EMBEDDING_API_KEY: z.string(), - CF_EMBEDDING_MODEL: z.string(), - CF_ACCOUNT_ID: z.string() + BETTER_AUTH_SECRET: z.string(), // Required by better-auth for apiKey plugin + FRONTEND_URL: z.string(), + DATABASE_URL: z.url(), + CLOUDFLARE_ACCOUNT_ID: z.string(), + CLOUDFLARE_API_TOKEN: z.string(), + JWT_SECRET: z.string(), + UPSTASH_REDIS_REST_URL: z.string().optional(), // Upstash Redis REST URL + UPSTASH_REDIS_REST_TOKEN: z.string().optional(), // Upstash Redis REST Token + CF_EMBEDDING_API_KEY: z.string(), + CF_EMBEDDING_MODEL: z.string(), + CF_ACCOUNT_ID: z.string(), + GOOGLE_CLIENT_ID: z.string(), + GOOGLE_CLIENT_SECRET: z.string(), + GOOGLE_REDIRECT_URI: z.string(), }); export const config = env.parse(process.env); diff --git a/src/lib/google/index.ts b/src/lib/google/index.ts new file mode 100644 index 0000000..becb34b --- /dev/null +++ b/src/lib/google/index.ts @@ -0,0 +1,89 @@ +import { google, Auth } from "googleapis"; +import crypto from "node:crypto"; +import { config } from "../env"; + +interface GoogleUserProfile { + email: string; + name: string; + profilePic: string; +} + +class GoogleOAuth { + private readonly client: Auth.OAuth2Client; + private readonly scopes = [ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + ]; + + constructor(clientId: string, clientSecret: string, redirectUri: string) { + this.client = new google.auth.OAuth2(clientId, clientSecret, redirectUri); + } + + public getAuthUrl(): { authUrl: string; state: string } { + const state = crypto.randomBytes(32).toString("hex"); + const authUrl = this.client.generateAuthUrl({ + access_type: "offline", + scope: this.scopes, + include_granted_scopes: true, + state, + }); + return { authUrl, state }; + } + + public async getTokens(code: string) { + const { tokens } = await this.client.getToken(code); + if (!tokens.access_token) { + throw new Error("Google OAuth: Failed to retrieve access token"); + } + return tokens; + } + + public async fetchProfile(accessToken: string): Promise { + // const oauth2 = google.oauth2({ + // version: "v2", + // auth: accessToken, + // }); + + // const { data } = await oauth2.userinfo.get(); + // if (!data?.email) { + // throw new Error("Google OAuth: Invalid user info response"); + // } + + // console.log("user recievedd :",data) + // return { + // email: data.email, + // name: data.name ?? "Anonymous", + // profilePic: data.picture ?? "", + // }; + + // may require to change + // const tempAuth = new google.auth.OAuth2(); + // tempAuth.setCredentials({ access_token: accessToken }); + // const oauth2 = google.oauth2({ + // version: "v2", + // auth: tempAuth, + // }); + const oauth2 = google.oauth2({ + version: "v2", + auth: accessToken, + }); + const { data } = await oauth2.userinfo.get(); + console.log("mee",data) + if (!data?.email) { + throw new Error("Google OAuth: Invalid user info response"); + } + return { + email: data.email, + name: data.name ?? "Anonymous", + profilePic: data.picture ?? "", + }; + } +} + +export const googleOAuth = new GoogleOAuth( + config.GOOGLE_CLIENT_ID, + config.GOOGLE_CLIENT_SECRET, + config.GOOGLE_REDIRECT_URI, +); + +export default GoogleOAuth; diff --git a/src/middlewares/jwt.middleware.ts b/src/middlewares/jwt.middleware.ts index 8acff7b..b75166e 100644 --- a/src/middlewares/jwt.middleware.ts +++ b/src/middlewares/jwt.middleware.ts @@ -3,38 +3,38 @@ import { config } from "../lib/env"; import { decode, verify } from "hono/jwt"; interface JwtPayload { - payload: { - sub: string; - role: string; - }; + payload: { + sub: string; + role: string; + }; } const verifyJwt = async (c: Context, next: Next): Promise => { - const authHeader = await c.req.header("Authorization"); + const authHeader = c.req.header("Authorization"); - if (!authHeader) { - return c.json({ error: "Unauthorized" }, 401); - } + if (!authHeader) { + return c.json({ error: "Unauthorized" }, 401); + } - // Extract token from "Bearer " format - const token = authHeader.startsWith("Bearer ") - ? authHeader.slice(7) - : authHeader; + // Extract token from "Bearer " format + const token = authHeader.startsWith("Bearer ") + ? authHeader.slice(7) + : authHeader; - try { - const isValid = await verify(token, config.JWT_SECRET, { alg: 'HS256' }); - if (!isValid) { - return c.json({ error: "Unauthorized" }, 401); - } - const { payload } = decode(token) as unknown as { payload: JwtPayload }; + try { + const isValid = await verify(token, config.JWT_SECRET, { alg: "HS256" }); + if (!isValid) { + return c.json({ error: "Unauthorized" }, 401); + } + const { payload } = decode(token) as unknown as { payload: JwtPayload }; - c.set("user", { id: payload.payload.sub, role: payload.payload.role }); + c.set("F", { id: payload.payload.sub, role: payload.payload.role }); - return next(); - } catch (error) { - console.error('error', error); - return c.json({ error: "Invalid token" }, 401); - } + return next(); + } catch (error) { + console.error("error", error); + return c.json({ error: "Invalid token" }, 401); + } }; export default verifyJwt; diff --git a/src/routes/auth.route.ts b/src/routes/auth.route.ts index 199eb36..f633124 100644 --- a/src/routes/auth.route.ts +++ b/src/routes/auth.route.ts @@ -3,7 +3,11 @@ import { AuthController } from "../controllers/auth.controller"; const app = new Hono(); -app.post("/nonce", AuthController.sendNonce); -app.post("/verify", AuthController.verifyNonce); +// app.post("/nonce", AuthController.sendNonce); +// app.post("/verify", AuthController.verifyNonce); +// app.post("/signin", AuthController.Signin); +// app.post("/signup", AuthController.Signup); +app.get("/google", AuthController.GoogleOAuth); +app.get("/google/callback", AuthController.GoogleCallback); export default app; diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 75f5692..c5e634c 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,116 +1,73 @@ -import { checkSignature, DataSignature, generateNonce } from '@meshsdk/core'; -import { prisma } from '../lib/db'; -import { NONCE_MESSAGE } from '../lib/utils/constants'; -import { decode, sign, verify } from 'hono/jwt' -import { config } from '../lib/env'; -import { setCookie } from 'hono/cookie'; -import { Context } from 'hono'; +import { checkSignature, DataSignature, generateNonce } from "@meshsdk/core"; +import { prisma } from "../lib/db"; +import { NONCE_MESSAGE } from "../lib/utils/constants"; +import { decode, sign, verify } from "hono/jwt"; +import { config } from "../lib/env"; +import { setCookie } from "hono/cookie"; +import { Context } from "hono"; +import GoogleOAuth, { googleOAuth } from "../lib/google"; export class AuthService { - public static readonly sendNonce = async (address: string) => { - const existingUser = await prisma.user.findFirst({ - where: { - walletAddress: { - address: address - } - }, - include: { - walletAddress: true - } - }); - - const nonce = generateNonce(NONCE_MESSAGE); - - if (existingUser) { - await prisma.user.update({ - where: { id: existingUser.id }, - data: { - nonce, - updatedAt: new Date() - } - }); - - return { nonce }; - } - - // Create new user and wallet address in a transaction - const userId = crypto.randomUUID(); - const now = new Date(); - - await prisma.$transaction(async (tx) => { - await tx.user.create({ - data: { - id: userId, - name: address, - email: address, - emailVerified: false, - nonce, - createdAt: now, - updatedAt: now - } - }); - - await tx.walletAddress.create({ - data: { - address: address, - chainId: 1, - isPrimary: true, - userId, - createdAt: now - } - }); - }); - - return { nonce }; + public static readonly GoogleSignin = async (c: Context) => { + const { authUrl, state } = googleOAuth.getAuthUrl(); + if (!authUrl || !state) { + throw new Error("Unable to generatte oauth url"); } + return { url: authUrl }; + }; - public static readonly verifyNonce = async (c: Context, address: string, signature: DataSignature) => { - const user = await prisma.user.findFirst({ - where: { - walletAddress: { - address: address - } - }, - select: { - nonce: true, - id: true - } - }); - - if (!user?.nonce) { - throw new Error('User not found or nonce not set'); - } - - const verified = await checkSignature( - user.nonce, - signature, - address as `0x${string}` - ); - - if (!verified) { - throw new Error('Invalid signature'); - } - else { - const payload = { - sub: user.id, - role: 'user', - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expires in 30 days - } - - // create a bearer access token - const token = await sign({ - payload, - alg: 'HS256' - }, config.JWT_SECRET); + public static readonly GoogleCallback = async ( + c: Context, + code: string, + state: string, + ) => { + try { + const tokens = await googleOAuth.getTokens(code); + console.log("tokens ala", tokens); - setCookie(c, 'token', token, { - httpOnly: true, - secure: true, - sameSite: 'strict', - maxAge: 60 * 60 * 24 * 30, - }); - return { token: token, userId: user.id }; - } + if (!tokens || !tokens.access_token) { + throw new Error("Failed to retrieve Google tokens."); + } + const profile = await googleOAuth.fetchProfile(tokens.access_token); + if (!profile || !profile.email) { + throw new Error("Failed to retrieve user info from Google."); + } + console.log("Profilee", profile); + const user = await prisma.user.upsert({ + where: { email: profile.email }, + update: { + name: profile.name, + image: profile.profilePic, + }, + create: { + name: profile.name, + email: profile.email, + image: profile.profilePic, + emailVerified: true, + }, + }); + const payload = { + sub: user.id, + role: "user", + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, + }; + const token = await sign( + { + payload, + alg: "HS256", + }, + config.JWT_SECRET, + ); + setCookie(c, "token", token, { + httpOnly: true, + secure: true, + sameSite: "strict", + maxAge: 60 * 60 * 24 * 30, + }); + return { token: token, userId: user.id }; + } catch (error) { + console.log(error); } -} \ No newline at end of file + }; +}