From 4a5ca0c96fca01180ed99a3b93c0a0c1a603b93a Mon Sep 17 00:00:00 2001 From: fawwazabrials <18221067@std.stei.itb.ac.id> Date: Wed, 12 Jun 2024 12:03:23 +0700 Subject: [PATCH 1/4] feat: add CRUD routes for google calendar api --- .gitignore | 3 +- package.json | 5 + pnpm-lock.yaml | 247 +++++++++++++++++++++++++ src/configs/env.config.ts | 2 + src/controllers/api.controller.ts | 2 + src/controllers/calendar.controller.ts | 134 ++++++++++++++ src/index.ts | 1 + src/lib/googleapi.ts | 10 + src/routes/calendar.route.ts | 113 +++++++++++ src/types/calendar.types.ts | 46 +++++ 10 files changed, 562 insertions(+), 1 deletion(-) create mode 100644 src/controllers/calendar.controller.ts create mode 100644 src/lib/googleapi.ts create mode 100644 src/routes/calendar.route.ts create mode 100644 src/types/calendar.types.ts diff --git a/.gitignore b/.gitignore index a4fadeb..f01a79b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .env +google_credentials.json # Ignore seed csv data src/db/seed/database.csv -src/db/seed/database-all.csv +src/db/seed/database-all.csv \ No newline at end of file diff --git a/package.json b/package.json index 95f2253..a941683 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@hono/swagger-ui": "^0.2.1", "@hono/zod-openapi": "^0.9.9", "@paralleldrive/cuid2": "^2.2.2", + "@types/jsonwebtoken": "^9.0.6", "aws-sdk": "^2.1607.0", "cross-env": "^7.0.3", "csv-parse": "^5.5.5", @@ -28,7 +29,11 @@ "drizzle-orm": "^0.30.9", "drizzle-zod": "^0.5.1", "eslint-config-love": "^43.1.0", + "gaxios": "^6.6.0", + "googleapis": "^140.0.0", "hono": "^4.2.9", + "install": "^0.13.0", + "jsonwebtoken": "^9.0.2", "open-graph-scraper": "^6.5.1", "pg": "^8.11.3", "postgres": "^3.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00d4edd..9457971 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 + '@types/jsonwebtoken': + specifier: ^9.0.6 + version: 9.0.6 aws-sdk: specifier: ^2.1607.0 version: 2.1608.0 @@ -50,9 +53,21 @@ importers: eslint-config-love: specifier: ^43.1.0 version: 43.1.0(@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint-plugin-n@16.6.2(eslint@8.57.0))(eslint-plugin-promise@6.1.1(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5) + gaxios: + specifier: ^6.6.0 + version: 6.6.0 + googleapis: + specifier: ^140.0.0 + version: 140.0.0 hono: specifier: ^4.2.9 version: 4.2.9 + install: + specifier: ^0.13.0 + version: 0.13.0 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 open-graph-scraper: specifier: ^6.5.1 version: 6.5.1 @@ -838,6 +853,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.6': + resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} + '@types/node@20.11.30': resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} @@ -981,6 +999,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} @@ -1468,6 +1489,9 @@ packages: ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1531,6 +1555,14 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gaxios@6.6.0: + resolution: {integrity: sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ==} + engines: {node: '>=14'} + + gcp-metadata@6.1.0: + resolution: {integrity: sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==} + engines: {node: '>=14'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -1569,12 +1601,28 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} + google-auth-library@9.10.0: + resolution: {integrity: sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==} + engines: {node: '>=14'} + + googleapis-common@7.2.0: + resolution: {integrity: sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==} + engines: {node: '>=14.0.0'} + + googleapis@140.0.0: + resolution: {integrity: sha512-r8i++0lnexrvRA0/uogz3N3eJprddjxAcueTO5f09D/U5yxaOm5G+a892QkHsV+o15NP9whlLUiJr9zazb9ePg==} + engines: {node: '>=14.0.0'} + gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gtoken@7.1.0: + resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} + engines: {node: '>=14.0.0'} + hanji@0.0.5: resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} @@ -1647,6 +1695,10 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + install@0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -1724,6 +1776,10 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -1756,6 +1812,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1773,9 +1832,19 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} @@ -1790,9 +1859,30 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -1843,6 +1933,15 @@ packages: next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -2002,6 +2101,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.12.1: + resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} + engines: {node: '>=0.6'} + querystring@0.2.0: resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} engines: {node: '>=0.4.x'} @@ -2148,6 +2251,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -2212,6 +2318,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-template@2.0.8: + resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} + url@0.10.3: resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==} @@ -2235,6 +2344,12 @@ packages: engines: {node: '>= 16'} hasBin: true + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -3348,6 +3463,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.6': + dependencies: + '@types/node': 20.11.30 + '@types/node@20.11.30': dependencies: undici-types: 5.26.5 @@ -3551,6 +3670,8 @@ snapshots: base64-js@1.5.1: {} + bignumber.js@9.1.2: {} + bn.js@4.12.0: {} boolbase@1.0.0: {} @@ -4143,6 +4264,8 @@ snapshots: dependencies: type: 2.7.2 + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -4208,6 +4331,25 @@ snapshots: functions-have-names@1.2.3: {} + gaxios@6.6.0: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.4 + is-stream: 2.0.1 + node-fetch: 2.7.0 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + gcp-metadata@6.1.0: + dependencies: + gaxios: 6.6.0 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -4268,12 +4410,52 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 + google-auth-library@9.10.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.6.0 + gcp-metadata: 6.1.0 + gtoken: 7.1.0 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + googleapis-common@7.2.0: + dependencies: + extend: 3.0.2 + gaxios: 6.6.0 + google-auth-library: 9.10.0 + qs: 6.12.1 + url-template: 2.0.8 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + + googleapis@140.0.0: + dependencies: + google-auth-library: 9.10.0 + googleapis-common: 7.2.0 + transitivePeerDependencies: + - encoding + - supports-color + gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 graphemer@1.4.0: {} + gtoken@7.1.0: + dependencies: + gaxios: 6.6.0 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + hanji@0.0.5: dependencies: lodash.throttle: 4.1.1 @@ -4341,6 +4523,8 @@ snapshots: inherits@2.0.4: {} + install@0.13.0: {} + internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -4415,6 +4599,8 @@ snapshots: dependencies: call-bind: 1.0.7 + is-stream@2.0.1: {} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -4443,6 +4629,10 @@ snapshots: dependencies: argparse: 2.0.1 + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.1.2 + json-buffer@3.0.1: {} json-diff@0.9.0: @@ -4459,12 +4649,36 @@ snapshots: dependencies: minimist: 1.2.8 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.0 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + jwa@2.0.0: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + jws@4.0.0: dependencies: jwa: 2.0.0 @@ -4483,8 +4697,22 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.throttle@4.1.1: {} lru-cache@6.0.0: @@ -4537,6 +4765,10 @@ snapshots: next-tick@1.1.0: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -4690,6 +4922,10 @@ snapshots: punycode@2.3.1: {} + qs@6.12.1: + dependencies: + side-channel: 1.0.6 + querystring@0.2.0: {} queue-microtask@1.2.3: {} @@ -4839,6 +5075,8 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + ts-api-utils@1.3.0(typescript@5.4.5): dependencies: typescript: 5.4.5 @@ -4919,6 +5157,8 @@ snapshots: dependencies: punycode: 2.3.1 + url-template@2.0.8: {} + url@0.10.3: dependencies: punycode: 1.3.2 @@ -4948,6 +5188,13 @@ snapshots: transitivePeerDependencies: - supports-color + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 diff --git a/src/configs/env.config.ts b/src/configs/env.config.ts index f0e16f6..c65d4f3 100644 --- a/src/configs/env.config.ts +++ b/src/configs/env.config.ts @@ -23,6 +23,8 @@ const EnvSchema = z.object({ R2_PUBLIC_URL: z.string().url().default('not-specified'), R2_BUCKET_NAME: z.string().default('hmifapp'), LOGIN_BYPASS_KEY: z.string().default('default-bypass-key'), + GOOGLE_CALENDAR_SECRET_PATH: z.string().default('google_credentials.json'), + GOOGLE_CALENDAR_ID: z.string().default('primary'), }); const result = EnvSchema.safeParse(process.env); diff --git a/src/controllers/api.controller.ts b/src/controllers/api.controller.ts index 780650e..61110e3 100644 --- a/src/controllers/api.controller.ts +++ b/src/controllers/api.controller.ts @@ -9,6 +9,7 @@ import { openGraphScrapeRoute } from './open-graph.controller'; import { pushPubRouter, pushRouter } from './push.controller'; import { reactionRouter } from './reaction.controller'; import { userUnsubscribeRouter } from './user-unsubscribe.controller'; +import { calendarRouter } from './calendar.controller'; const unprotectedApiRouter = new OpenAPIHono(); unprotectedApiRouter.route('/', loginRouter); @@ -25,6 +26,7 @@ protectedApiRouter.route('/', reactionRouter); protectedApiRouter.route('/', userUnsubscribeRouter); protectedApiRouter.route('/', categoryRouter); protectedApiRouter.route('/', infoRouter); +protectedApiRouter.route('/', calendarRouter); export const apiRouter = new OpenAPIHono(); apiRouter.route('/', unprotectedApiRouter); diff --git a/src/controllers/calendar.controller.ts b/src/controllers/calendar.controller.ts new file mode 100644 index 0000000..87c4ad8 --- /dev/null +++ b/src/controllers/calendar.controller.ts @@ -0,0 +1,134 @@ +import { + deleteCalendarEventRoute, + getCalendarEventRoute, + postCalendarEventRoute, + updateCalendarEventRoute, +} from '~/routes/calendar.route'; +import { createAuthRouter } from './router-factory'; +import { google, calendar_v3 } from 'googleapis'; +import { GaxiosError } from 'gaxios'; +import { googleAuth } from '~/lib/googleapi'; +import { env } from '~/configs/env.config'; + +export const calendarRouter = createAuthRouter(); + +const START_OF_6_MONTHS_AGO = new Date(); +START_OF_6_MONTHS_AGO.setMonth(START_OF_6_MONTHS_AGO.getMonth() - 6); + +calendarRouter.openapi(postCalendarEventRoute, async (c) => { + const { title, description, start, end } = c.req.valid('json'); + + const event: calendar_v3.Schema$Event = { + summary: title, + description, + start: { + dateTime: start?.toISOString(), + timeZone: 'Asia/Jakarta', + }, + end: { + dateTime: end?.toISOString(), + timeZone: 'Asia/Jakarta', + }, + }; + + try { + const response = await google.calendar('v3').events.insert({ + auth: googleAuth, + calendarId: env.GOOGLE_CALENDAR_ID, + requestBody: event, + }); + + return c.json(response.data, 201); + } catch (error) { + if (error instanceof GaxiosError) { + return c.json({ error: error.message }, 400); + } + return c.json({ error: 'Something went wrong' }, 500); + } +}); + +calendarRouter.openapi(getCalendarEventRoute, async (c) => { + const { search, startTime, endTime } = c.req.valid('query'); + + try { + const response = await google.calendar('v3').events.list({ + auth: googleAuth, + eventTypes: ['default'], + calendarId: env.GOOGLE_CALENDAR_ID, + maxResults: 2500, + + q: search, + timeMax: endTime?.toISOString(), + timeMin: startTime?.toISOString() ?? START_OF_6_MONTHS_AGO.toISOString(), + }); + + console.log(response.data.items?.length); + return c.json(response.data.items?.reverse(), 200); + } catch (error) { + if (error instanceof GaxiosError) { + return c.json({ error: error.message }, 400); + } + return c.json({ error: 'Something went wrong' }, 500); + } +}); + +calendarRouter.openapi(updateCalendarEventRoute, async (c) => { + const { eventId } = c.req.valid('param'); + const { title, description, start, end } = c.req.valid('json'); + + const response = await google.calendar('v3').events.get({ + auth: googleAuth, + calendarId: env.GOOGLE_CALENDAR_ID, + eventId, + }); + if (response.status !== 200) { + return c.json({ error: 'Event not found' }, 404); + } + + const event: calendar_v3.Schema$Event = { + summary: title ?? response.data.summary, + description: description ?? response.data.description, + start: { + dateTime: start?.toISOString() ?? response.data.start?.dateTime, + timeZone: 'Asia/Jakarta', + }, + end: { + dateTime: end?.toISOString() ?? response.data.end?.dateTime, + timeZone: 'Asia/Jakarta', + }, + }; + + try { + const response = await google.calendar('v3').events.update({ + auth: googleAuth, + calendarId: env.GOOGLE_CALENDAR_ID, + eventId, + requestBody: event, + }); + + return c.json(response.data, 200); + } catch (error) { + console.error(error); + if (error instanceof GaxiosError) { + return c.json({ error: error.message }, 400); + } + return c.json({ error: 'Something went wrong' }, 500); + } +}); + +calendarRouter.openapi(deleteCalendarEventRoute, async (c) => { + try { + const { eventId } = c.req.valid('param'); + await google.calendar('v3').events.delete({ + auth: googleAuth, + calendarId: env.GOOGLE_CALENDAR_ID, + eventId, + }); + return c.body(null, 204); + } catch (error) { + if (error instanceof GaxiosError) { + return c.json({ error: error.message }, 400); + } + return c.json({ error: 'Something went wrong' }, 500); + } +}); diff --git a/src/index.ts b/src/index.ts index 9edfeb1..a872076 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,7 @@ app.doc('/doc', { { name: 'open-graph', description: 'Scrape Open Graph API' }, { name: 'category', description: 'Category API' }, { name: 'unsubscribe', description: 'Unsubscribe API' }, + { name: 'calendar', description: 'Calendar API' }, ], }); app.get('/swagger', swaggerUI({ url: '/doc' })); diff --git a/src/lib/googleapi.ts b/src/lib/googleapi.ts new file mode 100644 index 0000000..d77f631 --- /dev/null +++ b/src/lib/googleapi.ts @@ -0,0 +1,10 @@ +import { google } from 'googleapis'; +import { env } from '~/configs/env.config'; + +export const googleAuth = new google.auth.GoogleAuth({ + keyFile: env.GOOGLE_CALENDAR_SECRET_PATH, + scopes: [ + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/calendar.events', + ], +}); diff --git a/src/routes/calendar.route.ts b/src/routes/calendar.route.ts new file mode 100644 index 0000000..5e7a036 --- /dev/null +++ b/src/routes/calendar.route.ts @@ -0,0 +1,113 @@ +import { createRoute, z } from '@hono/zod-openapi'; +import { + CreateCalendarEventBodySchema, + CalendarEventIdParamsSchema, + GetCalendarEventParamsSchema, + UpdateCalendarEventBodySchema, +} from '~/types/calendar.types'; +import { ErrorSchema, ValidationErrorSchema } from '~/types/responses.type'; + +export const postCalendarEventRoute = createRoute({ + operationId: 'postCalendarEvent', + tags: ['calendar'], + method: 'post', + path: '/calendar', + request: { + body: { + content: { + 'application/json': { + schema: CreateCalendarEventBodySchema, + }, + }, + }, + }, + responses: { + 201: { + description: 'Event succesfully created', + }, + 400: { + description: 'Bad request', + content: { + 'application/json': { + schema: z.union([ErrorSchema, ValidationErrorSchema]), + }, + }, + }, + }, +}); + +export const getCalendarEventRoute = createRoute({ + operationId: 'getCalendarEvent', + tags: ['calendar'], + method: 'get', + path: '/calendar', + request: { + query: GetCalendarEventParamsSchema, + }, + responses: { + 200: { + description: 'Get list of categories', + }, + 400: { + description: 'Bad request', + content: { + 'application/json': { + schema: z.union([ErrorSchema, ValidationErrorSchema]), + }, + }, + }, + }, +}); + +export const updateCalendarEventRoute = createRoute({ + operationId: 'updateCalendarEvent', + tags: ['calendar'], + method: 'put', + path: '/calendar/{eventId}', + request: { + params: CalendarEventIdParamsSchema, + body: { + content: { + 'application/json': { + schema: UpdateCalendarEventBodySchema, + }, + }, + }, + }, + responses: { + 200: { + description: 'Event succesfully updated', + }, + 400: { + description: 'Bad request', + content: { + 'application/json': { + schema: z.union([ErrorSchema, ValidationErrorSchema]), + }, + }, + }, + }, +}); + +export const deleteCalendarEventRoute = createRoute({ + operationId: 'deleteCalendarEvent', + tags: ['calendar'], + method: 'delete', + path: '/calendar/{eventId}', + request: { + params: CalendarEventIdParamsSchema, + }, + responses: { + 204: { + description: 'Event succesfully deleted', + }, + 400: { + description: 'Bad request', + content: { + 'application/json': { + schema: z.union([ErrorSchema, ValidationErrorSchema]), + }, + }, + }, + }, +}); diff --git a/src/types/calendar.types.ts b/src/types/calendar.types.ts new file mode 100644 index 0000000..a4c5340 --- /dev/null +++ b/src/types/calendar.types.ts @@ -0,0 +1,46 @@ +import { z } from '@hono/zod-openapi'; + +// Helpers +const optionalDateCoerce = z.preprocess((arg) => { + if (typeof arg === 'string' || typeof arg === 'number') { + const date = new Date(arg); + return isNaN(date.getTime()) ? undefined : date; + } + return undefined; +}, z.date().optional()); + +const addHours = (date: Date, hours: number) => { + const newDate = new Date(date); + newDate.setHours(newDate.getHours() + hours); + return newDate; +}; + +export const CreateCalendarEventBodySchema = z.object({ + title: z.string().openapi({ example: 'Meeting' }), + description: z.string().optional().openapi({ example: 'Meeting with team' }), + start: z.coerce.date().openapi({ example: new Date().toISOString() }), + end: z.coerce + .date() + .openapi({ example: addHours(new Date(), 2).toISOString() }), +}); + +export const GetCalendarEventParamsSchema = z.object({ + search: z.string().optional().openapi({ example: 'Meeting' }), + startTime: optionalDateCoerce.openapi({ example: new Date().toISOString() }), + endTime: optionalDateCoerce.openapi({ + example: addHours(new Date(), 24).toISOString(), + }), +}); + +export const CalendarEventIdParamsSchema = z.object({ + eventId: z.string().openapi({ example: 'hlp70594b43hcn866d291i8jm0' }), +}); + +export const UpdateCalendarEventBodySchema = z.object({ + title: z.string().optional().openapi({ example: 'Meeting' }), + description: z.string().optional().openapi({ example: 'Meeting with team' }), + start: optionalDateCoerce.openapi({ example: new Date().toISOString() }), + end: optionalDateCoerce.openapi({ + example: addHours(new Date(), 2).toISOString(), + }), +}); From 034835f82b4ab7236e3d254a612f9773272f4c7b Mon Sep 17 00:00:00 2001 From: fawwazabrials <18221067@std.stei.itb.ac.id> Date: Wed, 12 Jun 2024 15:50:45 +0700 Subject: [PATCH 2/4] feat: add get event by id route --- src/controllers/calendar.controller.ts | 20 ++++++++++++++++++++ src/routes/calendar.route.ts | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/controllers/calendar.controller.ts b/src/controllers/calendar.controller.ts index 87c4ad8..31450b1 100644 --- a/src/controllers/calendar.controller.ts +++ b/src/controllers/calendar.controller.ts @@ -1,5 +1,6 @@ import { deleteCalendarEventRoute, + getCalendarEventByIdRoute, getCalendarEventRoute, postCalendarEventRoute, updateCalendarEventRoute, @@ -72,6 +73,25 @@ calendarRouter.openapi(getCalendarEventRoute, async (c) => { } }); +calendarRouter.openapi(getCalendarEventByIdRoute, async (c) => { + const { eventId } = c.req.valid('param'); + + try { + const response = await google.calendar('v3').events.get({ + auth: googleAuth, + calendarId: env.GOOGLE_CALENDAR_ID, + eventId, + }); + + return c.json(response.data, 200); + } catch (error) { + if (error instanceof GaxiosError) { + return c.json({ error: error.message }, 400); + } + return c.json({ error: 'Something went wrong' }, 500); + } +}); + calendarRouter.openapi(updateCalendarEventRoute, async (c) => { const { eventId } = c.req.valid('param'); const { title, description, start, end } = c.req.valid('json'); diff --git a/src/routes/calendar.route.ts b/src/routes/calendar.route.ts index 5e7a036..65e5075 100644 --- a/src/routes/calendar.route.ts +++ b/src/routes/calendar.route.ts @@ -59,6 +59,29 @@ export const getCalendarEventRoute = createRoute({ }, }); +export const getCalendarEventByIdRoute = createRoute({ + operationId: 'getCalendarEventById', + tags: ['calendar'], + method: 'get', + path: '/calendar/{eventId}', + request: { + params: CalendarEventIdParamsSchema, + }, + responses: { + 200: { + description: 'Get event by id', + }, + 400: { + description: 'Bad request', + content: { + 'application/json': { + schema: z.union([ErrorSchema, ValidationErrorSchema]), + }, + }, + }, + }, +}); + export const updateCalendarEventRoute = createRoute({ operationId: 'updateCalendarEvent', tags: ['calendar'], From f8c36cddce3f8f7da4dd935e7498b4e11f24746a Mon Sep 17 00:00:00 2001 From: fawwazabrials <18221067@std.stei.itb.ac.id> Date: Wed, 12 Jun 2024 16:11:39 +0700 Subject: [PATCH 3/4] feat: typing on calendar events --- src/controllers/calendar.controller.ts | 2 +- src/routes/calendar.route.ts | 22 ++++++++++++ src/types/calendar.types.ts | 47 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/controllers/calendar.controller.ts b/src/controllers/calendar.controller.ts index 31450b1..0467542 100644 --- a/src/controllers/calendar.controller.ts +++ b/src/controllers/calendar.controller.ts @@ -64,7 +64,7 @@ calendarRouter.openapi(getCalendarEventRoute, async (c) => { }); console.log(response.data.items?.length); - return c.json(response.data.items?.reverse(), 200); + return c.json(response.data.items?.reverse() ?? [], 200); } catch (error) { if (error instanceof GaxiosError) { return c.json({ error: error.message }, 400); diff --git a/src/routes/calendar.route.ts b/src/routes/calendar.route.ts index 65e5075..bcdd71f 100644 --- a/src/routes/calendar.route.ts +++ b/src/routes/calendar.route.ts @@ -4,6 +4,8 @@ import { CalendarEventIdParamsSchema, GetCalendarEventParamsSchema, UpdateCalendarEventBodySchema, + CalendarEvent, + CalendarEventList, } from '~/types/calendar.types'; import { ErrorSchema, ValidationErrorSchema } from '~/types/responses.type'; @@ -24,6 +26,11 @@ export const postCalendarEventRoute = createRoute({ responses: { 201: { description: 'Event succesfully created', + content: { + 'application/json': { + schema: CalendarEvent, + }, + }, }, 400: { description: 'Bad request', @@ -47,6 +54,11 @@ export const getCalendarEventRoute = createRoute({ responses: { 200: { description: 'Get list of categories', + content: { + 'application/json': { + schema: CalendarEventList, + }, + }, }, 400: { description: 'Bad request', @@ -70,6 +82,11 @@ export const getCalendarEventByIdRoute = createRoute({ responses: { 200: { description: 'Get event by id', + content: { + 'application/json': { + schema: CalendarEvent, + }, + }, }, 400: { description: 'Bad request', @@ -100,6 +117,11 @@ export const updateCalendarEventRoute = createRoute({ responses: { 200: { description: 'Event succesfully updated', + content: { + 'application/json': { + schema: CalendarEvent, + }, + }, }, 400: { description: 'Bad request', diff --git a/src/types/calendar.types.ts b/src/types/calendar.types.ts index a4c5340..9dfc598 100644 --- a/src/types/calendar.types.ts +++ b/src/types/calendar.types.ts @@ -15,6 +15,53 @@ const addHours = (date: Date, hours: number) => { return newDate; }; +const EventDate = z.object({ + dateTime: z + .string() + .nullable() + .optional() + .openapi({ example: new Date().toISOString() }), + timeZone: z + .string() + .nullable() + .optional() + .openapi({ example: 'Asia/Jakarta' }), +}); + +export const CalendarEvent = z + .object({ + id: z + .string() + .nullable() + .optional() + .openapi({ example: 'hlp70594b43hcn866d291i8jm0' }), + created: z + .string() + .nullable() + .optional() + .openapi({ example: new Date().toISOString() }), + updated: z + .string() + .nullable() + .optional() + .openapi({ example: new Date().toISOString() }), + summary: z + .string() + .nullable() + .optional() + .openapi({ example: 'Event title' }), + description: z + .string() + .nullable() + .optional() + .openapi({ example: 'Event description' }), + start: EventDate.optional(), + end: EventDate.optional(), + }) + .openapi('CalendarEvent'); + +export const CalendarEventList = z.array(CalendarEvent); + export const CreateCalendarEventBodySchema = z.object({ title: z.string().openapi({ example: 'Meeting' }), description: z.string().optional().openapi({ example: 'Meeting with team' }), From 71f450e511d3c34898bfb196c9c995d898930c80 Mon Sep 17 00:00:00 2001 From: fawwazabrials <18221067@std.stei.itb.ac.id> Date: Wed, 12 Jun 2024 16:16:47 +0700 Subject: [PATCH 4/4] chore: remove jsonwebtoken --- package.json | 1 - pnpm-lock.yaml | 72 -------------------------------------------------- 2 files changed, 73 deletions(-) diff --git a/package.json b/package.json index a941683..f2ddd54 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "googleapis": "^140.0.0", "hono": "^4.2.9", "install": "^0.13.0", - "jsonwebtoken": "^9.0.2", "open-graph-scraper": "^6.5.1", "pg": "^8.11.3", "postgres": "^3.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9457971..f8686ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,9 +65,6 @@ importers: install: specifier: ^0.13.0 version: 0.13.0 - jsonwebtoken: - specifier: ^9.0.2 - version: 9.0.2 open-graph-scraper: specifier: ^6.5.1 version: 6.5.1 @@ -1832,19 +1829,9 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} @@ -1859,30 +1846,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -4649,36 +4615,12 @@ snapshots: dependencies: minimist: 1.2.8 - jsonwebtoken@9.0.2: - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.6.0 - - jwa@1.4.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - jwa@2.0.0: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jws@3.2.2: - dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 - jws@4.0.0: dependencies: jwa: 2.0.0 @@ -4697,22 +4639,8 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.includes@4.3.0: {} - - lodash.isboolean@3.0.3: {} - - lodash.isinteger@4.0.4: {} - - lodash.isnumber@3.0.3: {} - - lodash.isplainobject@4.0.6: {} - - lodash.isstring@4.0.1: {} - lodash.merge@4.6.2: {} - lodash.once@4.1.1: {} - lodash.throttle@4.1.1: {} lru-cache@6.0.0: