diff --git a/package-lock.json b/package-lock.json index f547fa3..5158593 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,8 @@ "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^5.0.0", + "google-auth-library": "^8.9.0", + "google-spreadsheet": "^4.0.2", "jest": "^29.5.0", "nodemon": "^3.0.1", "prettier": "^3.0.0", @@ -2822,6 +2824,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", @@ -3168,6 +3181,12 @@ "ieee754": "^1.1.4" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4056,6 +4075,15 @@ "node": ">=12" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4823,6 +4851,12 @@ "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4881,6 +4915,12 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -4987,6 +5027,26 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -5092,6 +5152,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5268,6 +5356,86 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dev": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-auth-library/node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/google-auth-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-auth-library/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/google-spreadsheet": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/google-spreadsheet/-/google-spreadsheet-4.0.2.tgz", + "integrity": "sha512-A5Y3HdgQhwnc16cwfwIdMS1FR2rAVRBa+4RLO8jbuUPYWOGMTPWqhl1mkIB5XOT+w69znoXMHeNgaNbKZp0J2g==", + "dev": true, + "dependencies": { + "axios": "^1.4.0", + "lodash": "^4.17.21" + }, + "peerDependencies": { + "google-auth-library": "^8.8.0" + }, + "peerDependenciesMeta": { + "google-auth-library": { + "optional": true + } + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5297,6 +5465,20 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dev": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -6841,6 +7023,15 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6908,6 +7099,27 @@ "node": "*" } }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -7370,6 +7582,35 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-gyp-build": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", @@ -8010,6 +8251,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -9222,6 +9469,12 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -9709,6 +9962,12 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, "node_modules/websocket": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", @@ -9725,6 +9984,16 @@ "node": ">=4.0.0" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12265,6 +12534,17 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, + "axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "babel-jest": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", @@ -12536,6 +12816,12 @@ "ieee754": "^1.1.4" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -13188,6 +13474,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13781,6 +14076,12 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -13835,6 +14136,12 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "dev": true + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -13926,6 +14233,12 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -14003,6 +14316,28 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dev": true, + "requires": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + } + }, + "gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dev": true, + "requires": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -14122,6 +14457,65 @@ "slash": "^3.0.0" } }, + "google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dev": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dev": true, + "requires": { + "node-forge": "^1.3.1" + } + }, + "google-spreadsheet": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/google-spreadsheet/-/google-spreadsheet-4.0.2.tgz", + "integrity": "sha512-A5Y3HdgQhwnc16cwfwIdMS1FR2rAVRBa+4RLO8jbuUPYWOGMTPWqhl1mkIB5XOT+w69znoXMHeNgaNbKZp0J2g==", + "dev": true, + "requires": { + "axios": "^1.4.0", + "lodash": "^4.17.21" + } + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -14148,6 +14542,17 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dev": true, + "requires": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + } + }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -15265,6 +15670,15 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dev": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -15315,6 +15729,27 @@ "through": ">=2.2.7 <3" } }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -15693,6 +16128,21 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, "node-gyp-build": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", @@ -16148,6 +16598,12 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -17045,6 +17501,12 @@ "nopt": "~1.0.10" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -17391,6 +17853,12 @@ "makeerror": "1.0.12" } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, "websocket": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", @@ -17404,6 +17872,16 @@ "yaeti": "^0.0.6" } }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index a59e452..3421923 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/server.ts", "lint": "eslint --cache .", "format": "eslint --fix . && prettier --write .", - "db:seed": "npx prisma db seed -- -p ", + "db:seed": "npx prisma db seed", "swagger:gendoc": "ts-node src/docs/swagger.ts", "db:migrate:dev": "npx prisma migrate dev --skip-seed", "db:migrate:deploy": "npx prisma migrate deploy", @@ -33,6 +33,8 @@ "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^5.0.0", + "google-auth-library": "^8.9.0", + "google-spreadsheet": "^4.0.2", "jest": "^29.5.0", "nodemon": "^3.0.1", "prettier": "^3.0.0", diff --git a/src/app.ts b/src/app.ts index 00f8f82..840b927 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,9 +4,12 @@ import helmet from 'helmet'; import httpStatus from 'http-status'; import swaggerUi from 'swagger-ui-express'; +import collectionRouter from './collections/collections.router'; import swaggerDocument from './docs/swagger.json'; +import listingRouter from './listings/listings.router'; import { morgan, errorHandler, errorConverter } from './middlewares'; -import { pingRouter, collectionRouter, listingRouter, tokenRouter } from './routes'; +import tokenRouter from './nfts/nfts.router'; +import pingRouter from './ping/ping.router'; import ApiError from './utils/api-error'; const application: Application = express(); diff --git a/src/controllers/collection.ts b/src/collections/collections.controller.ts similarity index 80% rename from src/controllers/collection.ts rename to src/collections/collections.controller.ts index 4d519f8..69796d9 100644 --- a/src/controllers/collection.ts +++ b/src/collections/collections.controller.ts @@ -1,18 +1,19 @@ import { Request, Response } from 'express'; import httpStatus from 'http-status'; -import { CollectionService } from '../services'; +import CollectionService from './collections.service'; class CollectionController { async searchCollections(req: Request, res: Response): Promise { const { q, limit } = req.query; const matchingCollections = await CollectionService.search(q as string, Number(limit)); + res.status(httpStatus.OK).json({ data: matchingCollections, }); } - async getByCollectionId(req: Request, res: Response): Promise { + async getCollectionById(req: Request, res: Response): Promise { const { id } = req.params; const collection = await CollectionService.getById(id as string); @@ -21,7 +22,8 @@ class CollectionController { }); } - async getTokensInCollection(req: Request, res: Response): Promise { + // todo: Add filters + async getNFTsInCollection(req: Request, res: Response): Promise { const { id } = req.params; const { offset, limit } = req.query; const collectionWithTokens = await CollectionService.getTokens( @@ -29,6 +31,7 @@ class CollectionController { Number(limit), Number(offset), ); + res.status(httpStatus.OK).json({ data: collectionWithTokens, }); diff --git a/src/collections/collections.router.ts b/src/collections/collections.router.ts new file mode 100644 index 0000000..5b2e77f --- /dev/null +++ b/src/collections/collections.router.ts @@ -0,0 +1,26 @@ +import { Router } from 'express'; + +import CollectionController from './collections.controller'; +import CollectionValidation from './collections.validator'; +import { validate } from '../middlewares'; +import { catchAsync } from '../utils'; + +const collectionRouter = Router(); + +collectionRouter.get( + '/search', + validate(CollectionValidation.searchCollection), + catchAsync(CollectionController.searchCollections), +); +collectionRouter.get( + '/nfts/:id', + validate(CollectionValidation.getCollectionTokens), + catchAsync(CollectionController.getNFTsInCollection), +); +collectionRouter.get( + '/:id', + validate(CollectionValidation.getCollection), + catchAsync(CollectionController.getCollectionById), +); + +export default collectionRouter; diff --git a/src/services/collection.ts b/src/collections/collections.service.ts similarity index 81% rename from src/services/collection.ts rename to src/collections/collections.service.ts index 3bdc530..ee78666 100644 --- a/src/services/collection.ts +++ b/src/collections/collections.service.ts @@ -23,22 +23,10 @@ class CollectionService { }); } - async all(limit: number, offset: number): Promise { - return this.model.findMany({ - skip: offset, - take: limit, - include: { - _count: { - select: { nfts: true }, - }, - }, - }); - } - async getById(id: string): Promise { return this.model.findUniqueOrThrow({ where: { - id: id, + collectionId: id, }, include: { _count: { @@ -51,7 +39,7 @@ class CollectionService { async getTokens(id: string, limit: number, offset: number): Promise { return this.model.findFirst({ where: { - id: id, + collectionId: id, }, select: { name: true, diff --git a/src/validators/collection.ts b/src/collections/collections.validator.ts similarity index 100% rename from src/validators/collection.ts rename to src/collections/collections.validator.ts diff --git a/src/controllers/index.ts b/src/controllers/index.ts deleted file mode 100644 index 23b6f92..0000000 --- a/src/controllers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as CollectionController } from './collection'; -export { default as ListingController } from './listing'; -export { default as TokenController } from './token'; diff --git a/src/controllers/ping.ts b/src/controllers/ping.ts deleted file mode 100644 index eca0290..0000000 --- a/src/controllers/ping.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Request, Response } from 'express'; -import httpStatus from 'http-status'; - -const index = (req: Request, res: Response): void => { - res.status(httpStatus.OK).json(); -}; - -export default index; diff --git a/src/interfaces.ts b/src/interfaces.ts index 82cf9cb..542e965 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -61,3 +61,26 @@ export interface ListingDBFilters { listedBefore?: Date; listedAfter?: Date; } + +export interface TopCollectionsFilter { + limit?: number; + offset?: number; + duration: '24h' | '7d' | '30d' | 'allTime'; +} + +enum NftStatus { + UNLIST = 'UNLIST', + LIST = 'LIST', + AUCTION = 'AUCTION', +} + +export interface NftFilter { + name?: string; + issuer?: string; + status?: NftStatus; + minPrice?: number; + maxPrice?: number; + taxon?: number; + attributesCount?: number; + attributes: Record; +} diff --git a/src/controllers/listing.ts b/src/listings/listings.controller.ts similarity index 100% rename from src/controllers/listing.ts rename to src/listings/listings.controller.ts diff --git a/src/routes/listing.ts b/src/listings/listings.router.ts similarity index 86% rename from src/routes/listing.ts rename to src/listings/listings.router.ts index 8a0e598..3b249cb 100644 --- a/src/routes/listing.ts +++ b/src/listings/listings.router.ts @@ -1,9 +1,9 @@ import { Router } from 'express'; -import { ListingController } from '../controllers'; +import ListingController from './listings.controller'; +import listingValidation from './listings.validator'; import { validate, authenticateSignature } from '../middlewares'; import { catchAsync } from '../utils'; -import listingValidation from '../validators/listing'; const listingRouter = Router(); diff --git a/src/services/listing.ts b/src/listings/listings.service.ts similarity index 100% rename from src/services/listing.ts rename to src/listings/listings.service.ts diff --git a/src/validators/listing.ts b/src/listings/listings.validator.ts similarity index 100% rename from src/validators/listing.ts rename to src/listings/listings.validator.ts diff --git a/src/controllers/token.ts b/src/nfts/nfts.controller.ts similarity index 90% rename from src/controllers/token.ts rename to src/nfts/nfts.controller.ts index 4340eba..a5a955a 100644 --- a/src/controllers/token.ts +++ b/src/nfts/nfts.controller.ts @@ -5,7 +5,7 @@ import { parseNFTokenID } from 'xrpl'; import { TokenService } from '../services'; import { XrplClient } from '../utils'; -class TokenController { +class NFTController { async getTokenById(req: Request, res: Response): Promise { const { id } = req.params; const nftData = parseNFTokenID(id); @@ -22,4 +22,4 @@ class TokenController { } } -export default new TokenController(); +export default new NFTController(); diff --git a/src/nfts/nfts.router.ts b/src/nfts/nfts.router.ts new file mode 100644 index 0000000..4d8a3e2 --- /dev/null +++ b/src/nfts/nfts.router.ts @@ -0,0 +1,12 @@ +import { Router } from 'express'; + +import NFTController from './nfts.controller'; +import tokenValidation from './nfts.validator'; +import { validate } from '../middlewares'; +import { catchAsync } from '../utils'; + +const nftRouter = Router(); + +nftRouter.get('/:id', validate(tokenValidation.getById), catchAsync(NFTController.getTokenById)); + +export default nftRouter; diff --git a/src/nfts/nfts.service.ts b/src/nfts/nfts.service.ts new file mode 100644 index 0000000..45a6b44 --- /dev/null +++ b/src/nfts/nfts.service.ts @@ -0,0 +1,124 @@ +import { Nft, Prisma } from '@prisma/client'; + +import { NftFilter } from '../interfaces'; +import prisma from '../prisma/index'; +import { XrplClient } from '../utils'; +import NFTMetadataService from '../utils/nft-metadata'; + +class NFTService { + model = prisma.nft; + + async count(): Promise { + return this.model.count(); + } + + async getById(id: string): Promise { + return this.model.findUnique({ + where: { + id: id, + }, + }); + } + + getByTokenId(id: string): Promise { + return this.model.findUnique({ + where: { + tokenId: id, + }, + }); + } + + async filter(filters: NftFilter): Promise { + const filterConditions: Prisma.NftWhereInput[] = []; + + if (filters.name || filters.taxon || filters.issuer) { + const collectionConditions: Prisma.CollectionWhereInput = {}; + + if (filters.name) { + collectionConditions.name = { contains: filters.name, mode: 'insensitive' }; + } + if (filters.taxon) { + collectionConditions.taxon = filters.taxon; + } + if (filters.issuer) { + collectionConditions.issuer = filters.issuer; + } + + filterConditions.push({ collection: collectionConditions }); + } + + if (filters.status) { + filterConditions.push({ status: filters.status }); + } + + if (filters.minPrice || filters.maxPrice) { + const priceConditions: Prisma.DecimalFilter = {}; + + if (filters.minPrice) { + priceConditions.gte = filters.minPrice; + } + if (filters.maxPrice) { + priceConditions.lte = filters.maxPrice; + } + + filterConditions.push({ price: priceConditions }); + } + + if (filters.attributes) { + for (const [key, value] of Object.entries(filters.attributes)) { + filterConditions.push({ + attributes: { + path: [key], + equals: value, + }, + }); + } + } + + if (filters.attributesCount) { + const result: { id: string }[] = + await prisma.$queryRaw`SELECT "id" FROM "Nft" WHERE jsonb_object_keys(attributes)::jsonb ?& array[${filters.attributesCount}]`; + const idsWithCorrectAttributeCount = result.map((item) => item.id); + + filterConditions.push({ + id: { + in: idsWithCorrectAttributeCount, + }, + }); + } + + const where = { AND: filterConditions }; + return prisma.nft.findMany({ where }); + } + + async createByTokenId(id: string): Promise { + const nftData = await XrplClient.getNFTInfo(id); + const metadata = await NFTMetadataService.resolveNFTMetadata(id, nftData.uri, nftData.issuer); + return this.create({ + tokenId: id, + owner: nftData.owner, + sequence: nftData.nft_sequence, + attributes: JSON.stringify(metadata.attributes), + uri: nftData.uri, + imageUrl: metadata.imageUrl, + }); + } + + async create(data: Prisma.NftCreateInput): Promise { + return this.model.create({ data: data }); + } + + async getOrCreateByTokenId(id: string): Promise { + const token = await this.getByTokenId(id); + if (!token) { + return this.createByTokenId(id); + } + return token; + } + + async update(id: string, data: Prisma.NftUpdateInput): Promise { + return this.model.update({ where: { id: id }, data: data }); + } +} + +export default new NFTService(); diff --git a/src/validators/token.ts b/src/nfts/nfts.validator.ts similarity index 100% rename from src/validators/token.ts rename to src/nfts/nfts.validator.ts diff --git a/src/controllers/offer.ts b/src/offers/offers.controller.ts similarity index 100% rename from src/controllers/offer.ts rename to src/offers/offers.controller.ts diff --git a/src/services/offer.ts b/src/offers/offers.service.ts similarity index 100% rename from src/services/offer.ts rename to src/offers/offers.service.ts diff --git a/src/ping/ping.controller.ts b/src/ping/ping.controller.ts new file mode 100644 index 0000000..7613f84 --- /dev/null +++ b/src/ping/ping.controller.ts @@ -0,0 +1,10 @@ +import { Request, Response } from 'express'; +import httpStatus from 'http-status'; + +class PingController { + async get(req: Request, res: Response): Promise { + res.status(httpStatus.OK).json(); + } +} + +export default new PingController(); diff --git a/src/routes/ping.ts b/src/ping/ping.router.ts similarity index 50% rename from src/routes/ping.ts rename to src/ping/ping.router.ts index bc1f60a..05657e8 100644 --- a/src/routes/ping.ts +++ b/src/ping/ping.router.ts @@ -1,9 +1,9 @@ import { Router } from 'express'; -import pingController from '../controllers/ping'; +import PingController from './ping.controller'; const pingRouter = Router(); -pingRouter.get('/ping', pingController); +pingRouter.get('/ping', PingController.get); export default pingRouter; diff --git a/src/prisma/migrations/20230722144712_enum_add/migration.sql b/src/prisma/migrations/20230722144712_enum_add/migration.sql deleted file mode 100644 index 9509e40..0000000 --- a/src/prisma/migrations/20230722144712_enum_add/migration.sql +++ /dev/null @@ -1,36 +0,0 @@ -/* - Warnings: - - - Added the required column `status` to the `AuctionBid` table without a default value. This is not possible if the table is not empty. - - Added the required column `dateCreated` to the `Collection` table without a default value. This is not possible if the table is not empty. - - Added the required column `status` to the `Listing` table without a default value. This is not possible if the table is not empty. - - Added the required column `status` to the `ListingOffer` table without a default value. This is not possible if the table is not empty. - - Added the required column `sequence` to the `Nft` table without a default value. This is not possible if the table is not empty. - - Added the required column `status` to the `Offer` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateEnum -CREATE TYPE "OfferStatus" AS ENUM ('CANCELLED', 'PENDING', 'ACCEPTED', 'REJECTED'); - --- CreateEnum -CREATE TYPE "ListingStatus" AS ENUM ('ONGOING', 'CANCELLED'); - --- AlterTable -ALTER TABLE "AuctionBid" ADD COLUMN "status" "OfferStatus" NOT NULL; - --- AlterTable -ALTER TABLE "Collection" ADD COLUMN "dateCreated" TIMESTAMP(3) NOT NULL, -ADD COLUMN "floorPrice" DECIMAL(65,30) NOT NULL DEFAULT 0; - --- AlterTable -ALTER TABLE "Listing" ADD COLUMN "status" "ListingStatus" NOT NULL; - --- AlterTable -ALTER TABLE "ListingOffer" ADD COLUMN "status" "OfferStatus" NOT NULL; - --- AlterTable -ALTER TABLE "Nft" ADD COLUMN "price" DECIMAL(65,30) NOT NULL DEFAULT 0, -ADD COLUMN "sequence" INTEGER NOT NULL; - --- AlterTable -ALTER TABLE "Offer" ADD COLUMN "status" "OfferStatus" NOT NULL; diff --git a/src/prisma/migrations/20230723154324_jaybeechanges/migration.sql b/src/prisma/migrations/20230723154324_jaybeechanges/migration.sql deleted file mode 100644 index 521878a..0000000 --- a/src/prisma/migrations/20230723154324_jaybeechanges/migration.sql +++ /dev/null @@ -1,25 +0,0 @@ --- AlterEnum -ALTER TYPE "ListingStatus" ADD VALUE 'COMPLETED'; - --- AlterTable -ALTER TABLE "Auction" ADD COLUMN "status" "OfferStatus" NOT NULL DEFAULT 'PENDING'; - --- AlterTable -ALTER TABLE "AuctionBid" ALTER COLUMN "status" SET DEFAULT 'PENDING'; - --- AlterTable -ALTER TABLE "Collection" ALTER COLUMN "dateCreated" DROP NOT NULL; - --- AlterTable -ALTER TABLE "Listing" ALTER COLUMN "status" SET DEFAULT 'ONGOING'; - --- AlterTable -ALTER TABLE "ListingOffer" ALTER COLUMN "status" SET DEFAULT 'PENDING'; - --- AlterTable -ALTER TABLE "Nft" ALTER COLUMN "uri" DROP NOT NULL, -ALTER COLUMN "imageUrl" DROP NOT NULL, -ALTER COLUMN "attributes" DROP NOT NULL; - --- AlterTable -ALTER TABLE "Offer" ALTER COLUMN "status" SET DEFAULT 'PENDING'; diff --git a/src/prisma/migrations/20230727203423_add_txn_hash/migration.sql b/src/prisma/migrations/20230727203423_add_txn_hash/migration.sql deleted file mode 100644 index 55cc702..0000000 --- a/src/prisma/migrations/20230727203423_add_txn_hash/migration.sql +++ /dev/null @@ -1,69 +0,0 @@ -/* - Warnings: - - - The `status` column on the `Auction` table would be dropped and recreated. This will lead to data loss if there is data in the column. - - You are about to drop the column `dateCreated` on the `Collection` table. All the data in the column will be lost. - - A unique constraint covering the columns `[createTxnHash]` on the table `Auction` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[updateTxnHash]` on the table `Auction` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[createTxnHash]` on the table `AuctionBid` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[updateTxnHash]` on the table `AuctionBid` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[createTxnHash]` on the table `ListingOffer` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[updateTxnHash]` on the table `ListingOffer` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[createTxnHash]` on the table `Offer` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[updateTxnHash]` on the table `Offer` will be added. If there are existing duplicate values, this will fail. - - Added the required column `createTxnHash` to the `Auction` table without a default value. This is not possible if the table is not empty. - - Added the required column `createTxnHash` to the `AuctionBid` table without a default value. This is not possible if the table is not empty. - - Added the required column `createTxnHash` to the `Listing` table without a default value. This is not possible if the table is not empty. - - Added the required column `createTxnHash` to the `ListingOffer` table without a default value. This is not possible if the table is not empty. - - Added the required column `createTxnHash` to the `Offer` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Auction" ADD COLUMN "createTxnHash" TEXT NOT NULL, -ADD COLUMN "updateTxnHash" TEXT, -DROP COLUMN "status", -ADD COLUMN "status" "ListingStatus" NOT NULL DEFAULT 'ONGOING'; - --- AlterTable -ALTER TABLE "AuctionBid" ADD COLUMN "createTxnHash" TEXT NOT NULL, -ADD COLUMN "updateTxnHash" TEXT; - --- AlterTable -ALTER TABLE "Collection" DROP COLUMN "dateCreated", -ADD COLUMN "createdAt" TIMESTAMP(3); - --- AlterTable -ALTER TABLE "Listing" ADD COLUMN "createTxnHash" TEXT NOT NULL, -ADD COLUMN "updateTxnHash" TEXT; - --- AlterTable -ALTER TABLE "ListingOffer" ADD COLUMN "createTxnHash" TEXT NOT NULL, -ADD COLUMN "updateTxnHash" TEXT; - --- AlterTable -ALTER TABLE "Offer" ADD COLUMN "createTxnHash" TEXT NOT NULL, -ADD COLUMN "updateTxnHash" TEXT; - --- CreateIndex -CREATE UNIQUE INDEX "Auction_createTxnHash_key" ON "Auction"("createTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "Auction_updateTxnHash_key" ON "Auction"("updateTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "AuctionBid_createTxnHash_key" ON "AuctionBid"("createTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "AuctionBid_updateTxnHash_key" ON "AuctionBid"("updateTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "ListingOffer_createTxnHash_key" ON "ListingOffer"("createTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "ListingOffer_updateTxnHash_key" ON "ListingOffer"("updateTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "Offer_createTxnHash_key" ON "Offer"("createTxnHash"); - --- CreateIndex -CREATE UNIQUE INDEX "Offer_updateTxnHash_key" ON "Offer"("updateTxnHash"); diff --git a/src/prisma/migrations/20230717220603_init/migration.sql b/src/prisma/migrations/20230818150507_init/migration.sql similarity index 70% rename from src/prisma/migrations/20230717220603_init/migration.sql rename to src/prisma/migrations/20230818150507_init/migration.sql index 1bf1085..167879f 100644 --- a/src/prisma/migrations/20230717220603_init/migration.sql +++ b/src/prisma/migrations/20230818150507_init/migration.sql @@ -1,3 +1,12 @@ +-- CreateEnum +CREATE TYPE "OfferStatus" AS ENUM ('CANCELLED', 'PENDING', 'ACCEPTED', 'REJECTED'); + +-- CreateEnum +CREATE TYPE "ListingStatus" AS ENUM ('ONGOING', 'CANCELLED', 'COMPLETED'); + +-- CreateEnum +CREATE TYPE "NftStatus" AS ENUM ('UNLIST', 'LIST', 'AUCTION'); + -- CreateTable CREATE TABLE "User" ( "id" TEXT NOT NULL, @@ -15,6 +24,9 @@ CREATE TABLE "Listing" ( "price" DECIMAL(65,30) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, + "status" "ListingStatus" NOT NULL DEFAULT 'ONGOING', + "createTxnHash" TEXT NOT NULL, + "updateTxnHash" TEXT, CONSTRAINT "Listing_pkey" PRIMARY KEY ("id") ); @@ -28,6 +40,9 @@ CREATE TABLE "ListingOffer" ( "amount" DECIMAL(65,30) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, + "status" "OfferStatus" NOT NULL DEFAULT 'PENDING', + "createTxnHash" TEXT NOT NULL, + "updateTxnHash" TEXT, CONSTRAINT "ListingOffer_pkey" PRIMARY KEY ("id") ); @@ -41,6 +56,9 @@ CREATE TABLE "Auction" ( "minBid" DECIMAL(65,30) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, + "status" "ListingStatus" NOT NULL DEFAULT 'ONGOING', + "createTxnHash" TEXT NOT NULL, + "updateTxnHash" TEXT, CONSTRAINT "Auction_pkey" PRIMARY KEY ("id") ); @@ -54,6 +72,9 @@ CREATE TABLE "AuctionBid" ( "amount" DECIMAL(65,30) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, + "status" "OfferStatus" NOT NULL DEFAULT 'PENDING', + "createTxnHash" TEXT NOT NULL, + "updateTxnHash" TEXT, CONSTRAINT "AuctionBid_pkey" PRIMARY KEY ("id") ); @@ -67,6 +88,9 @@ CREATE TABLE "Offer" ( "amount" DECIMAL(65,30) NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, + "status" "OfferStatus" NOT NULL DEFAULT 'PENDING', + "createTxnHash" TEXT NOT NULL, + "updateTxnHash" TEXT, CONSTRAINT "Offer_pkey" PRIMARY KEY ("id") ); @@ -79,6 +103,17 @@ CREATE TABLE "Collection" ( "issuer" TEXT NOT NULL, "description" TEXT NOT NULL, "collectionId" TEXT NOT NULL, + "floorPrice" DECIMAL(65,30) NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3), + "dailyVolume" DECIMAL(65,30) NOT NULL DEFAULT 0, + "weeklyVolume" DECIMAL(65,30) NOT NULL DEFAULT 0, + "monthlyVolume" DECIMAL(65,30) NOT NULL DEFAULT 0, + "allTimeVolume" DECIMAL(65,30) NOT NULL DEFAULT 0, + "pictureUrl" TEXT, + "bannerUrl" TEXT, + "discordLink" TEXT, + "twitterLink" TEXT, + "instagramLink" TEXT, CONSTRAINT "Collection_pkey" PRIMARY KEY ("id") ); @@ -86,12 +121,15 @@ CREATE TABLE "Collection" ( -- CreateTable CREATE TABLE "Nft" ( "id" TEXT NOT NULL, - "uri" TEXT NOT NULL, + "uri" TEXT, "tokenId" TEXT NOT NULL, - "owner" TEXT NOT NULL, - "imageUrl" TEXT NOT NULL, - "attributes" JSONB NOT NULL, + "owner" TEXT, + "imageUrl" TEXT, + "attributes" JSONB, "collectionId" TEXT, + "price" DECIMAL(65,30) NOT NULL DEFAULT 0, + "sequence" INTEGER NOT NULL, + "status" "NftStatus" NOT NULL DEFAULT 'UNLIST', CONSTRAINT "Nft_pkey" PRIMARY KEY ("id") ); @@ -105,9 +143,33 @@ CREATE UNIQUE INDEX "User_address_key" ON "User"("address"); -- CreateIndex CREATE UNIQUE INDEX "Listing_nftId_key" ON "Listing"("nftId"); +-- CreateIndex +CREATE UNIQUE INDEX "ListingOffer_createTxnHash_key" ON "ListingOffer"("createTxnHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "ListingOffer_updateTxnHash_key" ON "ListingOffer"("updateTxnHash"); + -- CreateIndex CREATE UNIQUE INDEX "Auction_nftId_key" ON "Auction"("nftId"); +-- CreateIndex +CREATE UNIQUE INDEX "Auction_createTxnHash_key" ON "Auction"("createTxnHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "Auction_updateTxnHash_key" ON "Auction"("updateTxnHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "AuctionBid_createTxnHash_key" ON "AuctionBid"("createTxnHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "AuctionBid_updateTxnHash_key" ON "AuctionBid"("updateTxnHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "Offer_createTxnHash_key" ON "Offer"("createTxnHash"); + +-- CreateIndex +CREATE UNIQUE INDEX "Offer_updateTxnHash_key" ON "Offer"("updateTxnHash"); + -- CreateIndex CREATE UNIQUE INDEX "Collection_collectionId_key" ON "Collection"("collectionId"); diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 107649a..e439a05 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -10,19 +10,24 @@ datasource db { url = env("DATABASE_URL") } -enum OfferStatus{ +enum OfferStatus { CANCELLED PENDING ACCEPTED REJECTED } -enum ListingStatus{ +enum ListingStatus { ONGOING CANCELLED COMPLETED } +enum NftStatus { + UNLIST + LIST + AUCTION +} model User { id String @id @default(cuid()) @@ -43,14 +48,14 @@ model User { } model Listing { - id String @id @default(cuid()) - creatorAddr String - nftId String @unique - price Decimal - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt() - status ListingStatus @default(ONGOING) - createTxnHash String + id String @id @default(cuid()) + creatorAddr String + nftId String @unique + price Decimal + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() + status ListingStatus @default(ONGOING) + createTxnHash String updateTxnHash String? offers ListingOffer[] @@ -60,16 +65,16 @@ model Listing { } model ListingOffer { - id String @id @default(cuid()) - offerorAddr String - offereeAddr String - listingId String - amount Decimal - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt() - status OfferStatus @default(PENDING) - createTxnHash String @unique() - updateTxnHash String? @unique() + id String @id @default(cuid()) + offerorAddr String + offereeAddr String + listingId String + amount Decimal + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() + status OfferStatus @default(PENDING) + createTxnHash String @unique() + updateTxnHash String? @unique() listing Listing @relation(fields: [listingId], references: [id], onDelete: Cascade) offeree User @relation(name: "receivedListingOffers", fields: [offereeAddr], references: [address], onDelete: Cascade) @@ -77,16 +82,16 @@ model ListingOffer { } model Auction { - id String @id @default(cuid()) - creatorAddr String - nftId String @unique - duration Decimal - minBid Decimal - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt() - status ListingStatus @default(ONGOING) - createTxnHash String @unique() - updateTxnHash String? @unique() + id String @id @default(cuid()) + creatorAddr String + nftId String @unique + duration Decimal + minBid Decimal + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() + status ListingStatus @default(ONGOING) + createTxnHash String @unique() + updateTxnHash String? @unique() bids AuctionBid[] @@ -95,16 +100,16 @@ model Auction { } model AuctionBid { - id String @id @default(cuid()) - bidderAddr String - receiverAddr String - auctionId String - amount Decimal - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt() - status OfferStatus @default(PENDING) - createTxnHash String @unique() - updateTxnHash String? @unique() + id String @id @default(cuid()) + bidderAddr String + receiverAddr String + auctionId String + amount Decimal + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() + status OfferStatus @default(PENDING) + createTxnHash String @unique() + updateTxnHash String? @unique() auction Auction @relation(fields: [auctionId], references: [id], onDelete: Cascade) bidder User @relation(name: "initiatedAuctionBids", fields: [bidderAddr], references: [address], onDelete: Cascade) @@ -112,16 +117,16 @@ model AuctionBid { } model Offer { - id String @id @default(cuid()) - senderAddr String - receiverAddr String - nftId String - amount Decimal - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt() - status OfferStatus @default(PENDING) - createTxnHash String @unique() - updateTxnHash String? @unique() + id String @id @default(cuid()) + senderAddr String + receiverAddr String + nftId String + amount Decimal + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() + status OfferStatus @default(PENDING) + createTxnHash String @unique() + updateTxnHash String? @unique() nft Nft @relation(fields: [nftId], references: [tokenId], onDelete: Cascade) sender User @relation(name: "sentOffers", fields: [senderAddr], references: [address], onDelete: Cascade) @@ -129,15 +134,28 @@ model Offer { } model Collection { - id String @id @default(cuid()) - name String - taxon Int - issuer String - description String + id String @id @default(cuid()) + name String + taxon Int + issuer String + description String + // gotten from a combination of `issuer-taxon` - collectionId String @unique - floorPrice Decimal @default(0) - createdAt DateTime? + collectionId String @unique + floorPrice Decimal @default(0) + createdAt DateTime? + + dailyVolume Decimal @default(0) + weeklyVolume Decimal @default(0) + monthlyVolume Decimal @default(0) + allTimeVolume Decimal @default(0) + + pictureUrl String? + bannerUrl String? + + discordLink String? + twitterLink String? + instagramLink String? nfts Nft[] @@ -145,15 +163,16 @@ model Collection { } model Nft { - id String @id @default(cuid()) + id String @id @default(cuid()) uri String? - tokenId String @unique - owner String + tokenId String @unique + owner String? imageUrl String? attributes Json? collectionId String? - price Decimal @default(0) - sequence Int + price Decimal @default(0) + sequence Int + status NftStatus @default(UNLIST) listing Listing[] auction Auction[] diff --git a/src/prisma/seeds/index.ts b/src/prisma/seeds/index.ts index e176b12..939741c 100644 --- a/src/prisma/seeds/index.ts +++ b/src/prisma/seeds/index.ts @@ -1,87 +1,64 @@ -import fs from 'fs'; -import path from 'path'; - -import { parse } from 'csv-parse'; +import { JWT } from 'google-auth-library'; +import { GoogleSpreadsheet, GoogleSpreadsheetWorksheet } from 'google-spreadsheet'; import { NftService } from '../../utils'; import prisma from '../index'; -/** - * Validates command-line arguments for a specific use case. - * - * @param args - An array of strings representing the command-line arguments. - * It is expected to contain two elements: the first element - * should be `-p` or `--path`, and the second element should be - * the path to a directory. - * - * @throws {Error} If the number of arguments is not exactly 2, or if the first - * argument is not `-p` or `--path`, or if the second argument - * does not represent a valid directory path. - * - * @returns {string[]} The original array of command-line arguments if they pass - * the validation. - */ -function validateCommandLineArgs(args: string[]): string[] { - if (args.length !== 4) { - throw new Error('Arguments should be two e.g. `--path /home/johndoe/seeders'); - } +async function getSheet(sheetName: string): Promise { + const serviceAccountAuth = new JWT({ + email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL, + key: process.env.GOOGLE_PRIVATE_KEY, + scopes: ['https://www.googleapis.com/auth/spreadsheets'], + }); + + const doc = new GoogleSpreadsheet( + 'https://docs.google.com/spreadsheets/d/1K8CtvbgwpC4KpfPQJDM5GzP2_iZ2XRvKSQfufYwKBKk', + serviceAccountAuth, + ); + await doc.loadInfo(); + return doc.sheetsByTitle[sheetName]; +} - if (!['-p', '--path'].includes(args[2])) { - throw new Error('The first argument must be either `-p` or `--path`'); - } +async function dumpIssuersIntoDB(): Promise { + const issuers = []; + const issuersSheet = await getSheet('issuers'); - if (!fs.statSync(path.resolve(args[3])).isDirectory()) { - throw new Error('The second argument provided is not a valid directory'); + for (const entry of await issuersSheet.getRows()) { + issuers.push({ address: entry.get('Address') }); } - return args.splice(2); + await prisma.user.createMany({ data: issuers, skipDuplicates: true }); } -/** - * Loads issuer data from a CSV file and dumps it into the database. - * @param csvPath - The path to the CSV file containing issuer data. - * @returns A Promise that resolves once the data is dumped into the database. - */ -async function dumpIssuersIntoDB(csvPath: string): Promise { - const dataToDump = []; - const parser = fs.createReadStream(csvPath).pipe(parse({ from: 2 })); - - for await (const entry of parser) { - const [, issuer] = entry; - dataToDump.push({ address: issuer }); +async function dumpCollectionsIntoDB(): Promise { + const collections = []; + const collectionsMetadataSheet = await getSheet('collections_metadata'); + for (const entry of await collectionsMetadataSheet.getRows()) { + collections.push({ address: entry.get('Address') }); } - - await prisma.user.createMany({ data: dataToDump, skipDuplicates: true }); + // if (parseInt(taxon) !== 0) { + // collections.push({ + // name: metadata.name, + // taxon: parseInt(taxon), + // issuer: issuer, + // collectionId: collectionId, + // description: metadata.description, + // }); + // } + await prisma.collection.createMany({ data: collections, skipDuplicates: true }); } -/** - * Loads NFT data from a CSV file and dumps it into the database. - * @param csvPath - The path to the CSV file containing NFT data. - * @returns A Promise that resolves once the data is dumped into the database. - */ -async function dumpNFTsIntoDB(csvPath: string): Promise { +async function dumpNFTsIntoDB(): Promise { const nfts = []; const owners = []; - const collections = []; - const parser = fs.createReadStream(csvPath).pipe(parse({ from: 2 })); + const allNftsSheet = await getSheet('all_nfts'); - for await (const entry of parser) { + for (const entry of await allNftsSheet.getRows()) { const [tokenId, issuer, owner, taxon, , , sequence, uri] = entry; const collectionId = issuer + '-' + taxon; const metadata = await NftService.resolveNFTMetadata(tokenId, uri, issuer); owners.push({ address: owner }); - - if (parseInt(taxon) !== 0) { - collections.push({ - name: metadata.name, - taxon: parseInt(taxon), - issuer: issuer, - collectionId: collectionId, - description: metadata.description, - }); - } - nfts.push({ uri: uri, owner: owner, @@ -94,17 +71,13 @@ async function dumpNFTsIntoDB(csvPath: string): Promise { } await prisma.user.createMany({ data: owners, skipDuplicates: true }); - await prisma.collection.createMany({ data: collections, skipDuplicates: true }); - await prisma.nft.createMany({ data: nfts }); + await prisma.nft.createMany({ data: nfts, skipDuplicates: true }); } async function main(): Promise { - const [, seedDir] = validateCommandLineArgs(process.argv); - const issuersSeed = path.join(seedDir, 'issuers.csv'); - const nftsSeed = path.join(seedDir, 'nfts.csv'); - - await dumpIssuersIntoDB(issuersSeed); - await dumpNFTsIntoDB(nftsSeed); + await dumpIssuersIntoDB(); + await dumpCollectionsIntoDB(); + await dumpNFTsIntoDB(); } main(); diff --git a/src/routes/collection.ts b/src/routes/collection.ts deleted file mode 100644 index a9742bd..0000000 --- a/src/routes/collection.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Router } from 'express'; - -import { CollectionController } from '../controllers'; -import { validate } from '../middlewares'; -import { catchAsync } from '../utils'; -import collectionValidation from '../validators/collection'; - -const collectionRouter = Router(); - -collectionRouter.get( - '/search', - validate(collectionValidation.searchCollection), - catchAsync(CollectionController.searchCollections), -); -collectionRouter.get( - '/tokens/:id', - validate(collectionValidation.getCollectionTokens), - catchAsync(CollectionController.getTokensInCollection), -); -collectionRouter.get( - '/:id', - validate(collectionValidation.getCollection), - catchAsync(CollectionController.getByCollectionId), -); - -export default collectionRouter; diff --git a/src/routes/index.ts b/src/routes/index.ts deleted file mode 100644 index 94f631e..0000000 --- a/src/routes/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import collectionRouter from './collection'; -import listingRouter from './listing'; -import pingRouter from './ping'; -import tokenRouter from './token'; - -export { pingRouter, collectionRouter, listingRouter, tokenRouter }; diff --git a/src/routes/token.ts b/src/routes/token.ts deleted file mode 100644 index 25bfa91..0000000 --- a/src/routes/token.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Router } from 'express'; - -import { TokenController } from '../controllers'; -import { validate } from '../middlewares'; -import { catchAsync } from '../utils'; -import tokenValidation from '../validators/token'; - -const tokenRouter = Router(); - -tokenRouter.get('/:id', validate(tokenValidation.getById), catchAsync(TokenController.getTokenById)); - -export default tokenRouter; diff --git a/src/services/index.ts b/src/services/index.ts index fe2c99a..b86c26f 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,8 +1,8 @@ export { default as AuctionService } from './auction'; export { default as AuctionBidService } from './auction-bid'; -export { default as CollectionService } from './collection'; -export { default as ListingService } from './listing'; +export { default as CollectionService } from '../collections/collections.service'; +export { default as ListingService } from '../listings/listings.service'; export { default as ListingOfferService } from './listing-offer'; -export { default as OfferService } from './offer'; -export { default as TokenService } from './token'; +export { default as OfferService } from '../offers/offers.service'; +export { default as TokenService } from '../nfts/nfts.service'; export { default as UserService } from './user'; diff --git a/src/services/token.ts b/src/services/token.ts deleted file mode 100644 index f646302..0000000 --- a/src/services/token.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Nft, Prisma } from '@prisma/client'; - -import prisma from '../prisma/index'; -import { XrplClient } from '../utils'; -import NFTMetadataService from '../utils/nft-metadata'; - -class NftService { - model = prisma.nft; - - async count(): Promise { - return this.model.count(); - } - - async getById(id: string): Promise { - return this.model.findUnique({ - where: { - id: id, - }, - }); - } - - getByTokenId(id: string): Promise { - return this.model.findUnique({ - where: { - tokenId: id, - }, - }); - } - - async createByTokenId(id: string): Promise { - const nftData = await XrplClient.getNFTInfo(id); - const metadata = await NFTMetadataService.resolveNFTMetadata(id, nftData.uri, nftData.issuer); - return this.create({ - tokenId: id, - owner: nftData.owner, - sequence: nftData.nft_sequence, - attributes: JSON.stringify(metadata.attributes), - uri: nftData.uri, - imageUrl: metadata.imageUrl, - }); - } - - async create(data: Prisma.NftCreateInput): Promise { - return this.model.create({ data: data }); - } - - async getOrCreateByTokenId(id: string): Promise { - const token = await this.getByTokenId(id); - if (!token) { - return this.createByTokenId(id); - } - return token; - } - - async update(id: string, data: Prisma.NftUpdateInput): Promise { - return this.model.update({ where: { id: id }, data: data }); - } -} - -export default new NftService(); diff --git a/tsconfig.json b/tsconfig.json index 162d18c..b7bceed 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,7 +31,7 @@ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - "typeRoots": ["./src/@types", "./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ + "typeRoots": ["src/@types", "./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */