diff --git a/.env.example b/.env.example deleted file mode 100644 index c34412fe..00000000 --- a/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -```env -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -NEXTAUTH_URL= -NEXTAUTH_SECRET= -PSQL_PASS= -DATABASE_URL= -DISCORD_CLIENT_ID= -DISCORD_CLIENT_SECRET= -``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 37697006..eb5a4f2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "nebula-jupiter", "version": "0.1.0", "dependencies": { - "@auth/drizzle-adapter": "^0.3.2", + "@auth/drizzle-adapter": "^0.3.17", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", @@ -27,8 +27,10 @@ "dotenv": "^16.0.3", "drizzle-orm": "^0.28.5", "drizzle-zod": "^0.5.1", + "google-auth-library": "^9.14.0", + "googleapis": "^143.0.0", "next": "^14.2.2", - "next-auth": "^4.23.0", + "next-auth": "^4.24.7", "postgres": "^3.3.5", "react": "^18.2.0", "react-day-picker": "^8.9.1", @@ -107,14 +109,15 @@ } }, "node_modules/@auth/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.17.0.tgz", - "integrity": "sha512-YkbWuXny6sv0gF/sYm7KD3GxnnGxJomoQJzibzhJuwVOYjPUjjYBv/JMTkYcfpBuPHoHZwTVR3y1J+/W18oRxA==", - "dependencies": { - "@panva/hkdf": "^1.0.4", - "cookie": "0.5.0", - "jose": "^4.11.1", - "oauth4webapi": "^2.0.6", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.23.0.tgz", + "integrity": "sha512-vReM7RIX4BDj/EKyM6OiYnEw4u0MOEC+c2tB7V0qoZy2JZ/hn1xEuDMJwmdcvZNCjQ6kp4cW/6eTcCcEDsRbEA==", + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.4.0", "preact": "10.11.3", "preact-render-to-string": "5.2.3" }, @@ -127,12 +130,28 @@ } } }, + "node_modules/@auth/core/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@auth/core/node_modules/jose": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.7.0.tgz", + "integrity": "sha512-3P9qfTYDVnNn642LCAqIKbTGb9a1TBxZ9ti5zEVEr48aDdflgRjhspWFb6WM4PzAfFbGMJYC4+803v8riCRAKw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@auth/drizzle-adapter": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@auth/drizzle-adapter/-/drizzle-adapter-0.3.4.tgz", - "integrity": "sha512-3N734pgz7UJa+p1GBBl4OaORD7Ka6AIgfgM8W9b3UZjvIMnyT1dUZB6JleNg52Qskzh9ULtBuT1Lzb9/gKxsgQ==", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@auth/drizzle-adapter/-/drizzle-adapter-0.3.17.tgz", + "integrity": "sha512-pyHwshtINeJfUGdA6e+2lIzklfTZB2V60iLPbGXbcMMiECmsKXeEPS+xlwtJryY2ckwOoxG9a781cVX371QxUg==", "dependencies": { - "@auth/core": "0.17.0" + "@auth/core": "0.23.0" } }, "node_modules/@babel/code-frame": { @@ -2979,6 +2998,11 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/eslint": { "version": "8.44.4", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz", @@ -3359,6 +3383,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -3861,7 +3896,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -3877,6 +3911,14 @@ } ] }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3995,6 +4037,11 @@ "ieee754": "^1.1.13" } }, + "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==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4022,13 +4069,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4238,9 +4290,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", - "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -4789,7 +4841,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4870,17 +4921,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -5242,6 +5295,14 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "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==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.557", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.557.tgz", @@ -5341,6 +5402,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -6146,6 +6226,11 @@ "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", "dev": true }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -6413,7 +6498,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6445,6 +6529,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -6474,15 +6597,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6768,11 +6894,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.0.tgz", + "integrity": "sha512-Y/eq+RWVs55Io/anIsm24sDS8X79Tq948zVLGaa7+KlJYYqaGwp1YI37w48nzrNi12RgnzMrQD4NzdmCowT90g==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "143.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-143.0.0.tgz", + "integrity": "sha512-hGeNM9d9cDQAV/dm8FvdkismWIDCJRV9v11UTLq4nRPP+s/2jPuHQnpI7dR+sWmL0o3XURW0K3a3THKyDRnWVg==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -6791,6 +6972,18 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/hanji": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/hanji/-/hanji-0.0.5.tgz", @@ -6838,12 +7031,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6853,7 +7045,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6865,7 +7056,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6888,6 +7078,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/heap": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", @@ -6942,6 +7143,18 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -7457,7 +7670,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -8292,9 +8504,9 @@ } }, "node_modules/jose": { - "version": "4.15.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", - "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -8328,6 +8540,14 @@ "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==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -8433,6 +8653,25 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "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==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9017,8 +9256,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -9176,14 +9414,14 @@ } }, "node_modules/next-auth": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz", - "integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==", + "version": "4.24.7", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz", + "integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==", "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.5.0", - "jose": "^4.11.4", + "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", @@ -9208,6 +9446,25 @@ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "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-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -9271,9 +9528,9 @@ "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" }, "node_modules/oauth4webapi": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.3.0.tgz", - "integrity": "sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.12.0.tgz", + "integrity": "sha512-WFmcHzhFtq2Ar91crpGQZUD8DS0SG7Zti1AgbansUAfdpIsoRXE+hcMNi8MW6bGNNObWis0x8BZRl6K+FR4oQg==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -9296,10 +9553,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.0.tgz", - "integrity": "sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10159,6 +10418,20 @@ "teleport": ">=0.2.0" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10742,7 +11015,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -10831,6 +11103,22 @@ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-function-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", @@ -10867,14 +11155,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11610,6 +11901,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -11956,6 +12252,11 @@ "punycode": "^2.1.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, "node_modules/use-callback-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", @@ -12059,6 +12360,20 @@ "defaults": "^1.0.3" } }, + "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==" + }, + "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==", + "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", diff --git a/package.json b/package.json index d48fa4d3..46af195b 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "drizzle:push": "dotenv drizzle-kit push:pg" }, "dependencies": { - "@auth/drizzle-adapter": "^0.3.2", + "@auth/drizzle-adapter": "^0.3.17", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", @@ -32,8 +32,10 @@ "dotenv": "^16.0.3", "drizzle-orm": "^0.28.5", "drizzle-zod": "^0.5.1", + "google-auth-library": "^9.14.0", + "googleapis": "^143.0.0", "next": "^14.2.2", - "next-auth": "^4.23.0", + "next-auth": "^4.24.7", "postgres": "^3.3.5", "react": "^18.2.0", "react-day-picker": "^8.9.1", diff --git a/public/images/google_calendar_icon.png b/public/images/google_calendar_icon.png new file mode 100644 index 00000000..d646bf1b Binary files /dev/null and b/public/images/google_calendar_icon.png differ diff --git a/public/images/microsoft-outlook-icon.png b/public/images/microsoft-outlook-icon.png new file mode 100644 index 00000000..0fbc72cf Binary files /dev/null and b/public/images/microsoft-outlook-icon.png differ diff --git a/src/components/events/EventCard.tsx b/src/components/events/EventCard.tsx index 75008289..7c6fce13 100644 --- a/src/components/events/EventCard.tsx +++ b/src/components/events/EventCard.tsx @@ -7,10 +7,14 @@ import { type RouterOutputs } from '@src/trpc/shared'; import EventLikeButton from '../EventLikeButton'; import { getServerAuthSession } from '@src/server/auth'; import dynamic from 'next/dynamic'; - +import { and, eq } from 'drizzle-orm'; const EventTimeAlert = dynamic(() => import('./EventTimeAlert'), { ssr: false, }); +import AddToCalendarButton from './calendar/AddToCalendarButton'; +import AddToCalendarAuthorizedButton from './calendar/AddToCalendarAuthorizedButton'; +import { api } from '@src/trpc/server'; +import { db } from '@src/server/db'; type EventCardProps = { event: RouterOutputs['event']['findByFilters']['events'][number]; @@ -62,10 +66,21 @@ const HorizontalCard = async ({ {event.description}

-
+
{session && ( + )} + { (session && db.query.accounts.findFirst({ + where: (accounts) => + and( + eq(accounts.userId, session.user.id), + eq(accounts.provider, "google"), + ) , + }) !== undefined ) + ? + : } +
-
+
+ + {session && ( )} diff --git a/src/components/events/calendar/AddToCalendarAuthorizedButton.tsx b/src/components/events/calendar/AddToCalendarAuthorizedButton.tsx new file mode 100644 index 00000000..fcfb4daa --- /dev/null +++ b/src/components/events/calendar/AddToCalendarAuthorizedButton.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { type RouterOutputs } from '@src/trpc/shared'; +import { CalendarButton } from '@src/icons/Icons'; +import { api } from '@src/trpc/react'; + + +export const AddToCalendarAuthorizedButton = ({ + event, tokens +}: { + event: RouterOutputs['event']['findByFilters']['events'][number]; + tokens: {access_token: string, refresh_token: string | null, id_token: string | null}; +}) => { + + + const {mutate} = api.calendar.addEvent.useMutation({ + onSuccess: () => console.log("Successfully added event!"), + }) + + return ( +
+ +
+ ); +} + +export default AddToCalendarAuthorizedButton; diff --git a/src/components/events/calendar/AddToCalendarButton.tsx b/src/components/events/calendar/AddToCalendarButton.tsx new file mode 100644 index 00000000..ad7e0723 --- /dev/null +++ b/src/components/events/calendar/AddToCalendarButton.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { CalendarButton } from '@src/icons/Icons'; +import {useState} from 'react'; +import CalendarPopUp from './CalendarPopUp'; +import { type RouterOutputs } from '@src/trpc/shared'; + +export default function AddToCalendarButton( { + event, +}: { + event: RouterOutputs['event']['findByFilters']['events'][number]; +}) +{ + + const [isOpen, setIsOpen] = useState(false); + + const handlePopUp = () => { setIsOpen(!isOpen); } + + return ( + +
+ + + + +
+ ); +} diff --git a/src/components/events/calendar/CalendarPopUp.tsx b/src/components/events/calendar/CalendarPopUp.tsx new file mode 100644 index 00000000..0b488587 --- /dev/null +++ b/src/components/events/calendar/CalendarPopUp.tsx @@ -0,0 +1,82 @@ +import * as Dialog from '@radix-ui/react-dialog'; +import {CloseIcon} from '@src/icons/Icons'; +import { CalendarButton } from '@src/icons/Icons'; +import googleCalendarIcon from "public/images/google_calendar_icon.png" +import outlookCalendarIcon from "public/images/microsoft-outlook-icon.png"; +import Image from 'next/image'; +import { type RouterOutputs } from '@src/trpc/shared'; + +type CalendarPopUpProps = { + isOpen: boolean, + onClose: () => void + event: RouterOutputs['event']['findByFilters']['events'][number]; +} + +const CalendarPopUp: React.FC = ({isOpen, onClose, event}) => { + const generateGoogleCalendarLink = () => { + const startTime = event.startTime.toISOString().replace(/-|:|\.\d+/g, ''); + const endTime = event.endTime.toISOString().replace(/-|:|\.\d+/g, ''); + const googleCalendarLink = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${event.name}&dates=${startTime}/${endTime}&details=${event.description}&location=${event.location}`; + + window.open(googleCalendarLink, '_blank'); + } + + const generateOutlookCalendarLink = () => { + const startTime = event.startTime.toISOString().split('.')[0] + "Z"; + const endTime = event.endTime.toISOString().split('.')[0] + "Z"; + + const outlookCalendarLink = `https://outlook.live.com/owa/?path=/calendar/action/compose&rru=addevent&subject=${event.name}&startdt=${startTime}&enddt=${endTime}&location=${event.location}&body=${event.description}`; + window.open(outlookCalendarLink, '_blank'); + } + + return ( + + + + + + Add to Calendar + + + + + + + + + + + +
+
+
+ +
+
+
+ + +
+
+
+ ) + + +} + + +export default CalendarPopUp; \ No newline at end of file diff --git a/src/icons/Icons.tsx b/src/icons/Icons.tsx index 60cd2147..48eb50a2 100644 --- a/src/icons/Icons.tsx +++ b/src/icons/Icons.tsx @@ -368,6 +368,23 @@ export const CloseIcon = () => ( ); +export const CalendarButton = () => ( + + + +); + export const UploadIcon = () => ( { + + const { userId, provider } = input; + + try { + const account = await ctx.db.query.accounts.findFirst({ + where: and( + eq( accounts.provider, provider ), + eq( accounts.userId, userId ), + ) + }) + + if ( ! account ) { + console.error("Unable to find account in the database!") + throw new Error("Unable to find Acccount in database!") + } + if ( ! account.access_token ) { + console.log("Access token for user is empty!") + account.access_token = "" + } + + const token = { + access_token: account.access_token, + refresh_token: account.refresh_token, + id_token: account.id_token, + } + + return token + } catch ( error ) { + console.error("Error when trying to grab account from database: ", error) + throw error + } + }), +}); diff --git a/src/server/api/routers/calendar.ts b/src/server/api/routers/calendar.ts new file mode 100644 index 00000000..2f460b72 --- /dev/null +++ b/src/server/api/routers/calendar.ts @@ -0,0 +1,66 @@ + +import { z } from 'zod'; +import { createTRPCRouter, publicProcedure } from '../trpc'; +import { google } from 'googleapis'; +import { type OAuth2Client } from 'google-auth-library'; +import { env } from '@src/env.mjs'; + +const byEventDetails = z.object({ + eventName: z.string(), + startTime: z.date(), + endTime: z.date(), + location: z.string().nullable(), + description: z.string(), + tokens: z.object({ + access_token: z.string(), + refresh_token: z.string().nullable(), + id_token: z.string().nullable(), + }) + +}) + +export const calendarRouter = createTRPCRouter({ + addEvent: publicProcedure + .input(byEventDetails) + .mutation( ( {input} ) => { + const { eventName, startTime, endTime, location, tokens, description } = input; + + const oauth2Client : OAuth2Client = new google.auth.OAuth2({ + clientId: env.GOOGLE_CLIENT_ID , + clientSecret: env.GOOGLE_CLIENT_SECRET, + redirectUri: env.NEXTAUTH_URL, + }) + + + oauth2Client.setCredentials( {access_token: tokens.access_token, refresh_token: tokens.refresh_token, id_token: tokens.id_token }); + + const calendar = google.calendar({ version: 'v3', auth: oauth2Client }); + + + const response = calendar.events.insert({ + calendarId: 'primary', + requestBody: { + summary: eventName, + description: description, + location: location, + start: { + dateTime: startTime.toISOString() + }, + end: { + dateTime: endTime.toISOString() + }, + } + }, (error) => { + if (error) { + console.error("Error adding event:", error); + throw error; + } + }) + + return response + + + + + }), +}); diff --git a/src/server/auth.ts b/src/server/auth.ts index 5997249e..b597a485 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -8,11 +8,12 @@ import DiscordProvider from 'next-auth/providers/discord'; import { env } from '@src/env.mjs'; import { DrizzleAdapter } from '@auth/drizzle-adapter'; import { db } from './db'; -import { eq } from 'drizzle-orm'; +import { eq, and } from 'drizzle-orm'; import { type InsertUserMetadata } from './db/models'; import { type UserMetadata } from '@src/models/userMetadata'; import { pgTable } from 'drizzle-orm/pg-core'; import { userMetadata } from './db/schema/users'; +import { accounts } from '@src/server/db/schema/users'; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` @@ -41,6 +42,7 @@ export interface PreviewUser { name: string; email: string; image: string; + } /** @@ -56,7 +58,7 @@ export const authOptions: NextAuthOptions = { let metadata = await db.query.userMetadata.findFirst({ where: (metadata) => eq(metadata.id, user.id), }); - + if (!metadata) { const firstName = user.name?.split(' ')[0] ?? ''; const lastName = user.name?.split(' ')[1] ?? ''; @@ -71,16 +73,90 @@ export const authOptions: NextAuthOptions = { metadata = ( await db.insert(userMetadata).values(insert).returning() ).at(0); - } + } if (session.user) { - session.user = { ...session.user, ...metadata }; + session.user = { ...session.user, ...metadata }; // session.user.role = user.role; <-- put other properties on the session here } + // Do some Google's Refresh Token rotation stuff + const [googleAccount] = await db.query.accounts.findMany({ + where: (googleAccount) => and ( eq( googleAccount.userId, user.id), + eq( googleAccount.provider, "google") ) + }) + + if ( ! googleAccount ) return session // User doesn't have google Account, so no need to check + + if ( googleAccount.expires_at * 1000 < Date.now() ) return session // Token is still active + + // Reach here, access token has expired, so try to refresh it + + try { + const response = await fetch("https://oauth2.googleapis.com/token", { + method: "POST", + body: new URLSearchParams({ + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + grant_type: "refresh_token", + refresh_token: googleAccount.refresh_token! + }), + }) + + const token_or_error_status = await response.json() as { token?: string, error?: string} + + // The above variable is an error status + if ( !response.ok ) throw token_or_error_status + + // Token_or_error is a valid token, so do some parsing + const newTokens = token_or_error_status as { + access_token: string + expires_in: number + refresh_token?: string + } + + // Update the database and store the new refreshed access token in the database + await db.update(accounts).set({ + access_token: newTokens.access_token, + expires_at: Math.floor( Date.now() / 1000 + newTokens.expires_in), + refresh_token: newTokens.refresh_token ?? googleAccount.refresh_token + }).where(and( + eq( accounts.provider, "google"), + eq( accounts.providerAccountId, googleAccount.providerAccountId), + )) + } catch (error) { + console.error("Error refreshing access_token!", error) + } + return session; }, + async signIn({account}) { + // There's something wrong with NextAuth where it doesn't automatically save the Google's refresh token ( but it will save Discord refresh token ) + // into our database. So we have to manually save refreshToken into the database + + if ( account?.provider.toLowerCase() != "google" ) return account + + // Reach here, then we know the user log in with Google + // After the user Sign in, NextAuth will automatically create the session object and user object into our database for us, + // so all we just have grab the correct entry and update the refresh Token column in our database + try { + await db.update(accounts).set({ + refresh_token: account.refresh_token + }).where(and( + eq( accounts.provider, "google"), + eq( accounts.providerAccountId, account.providerAccountId), + )) + } catch ( e ) { // Honestly, this code shouldn't ever run, but just for Sanity check and future debugging + console.log("Error when trying to save refresh_token into database! Error: ", e) + + // We return true because we still want to the user to be able to sign in to our app. If we were unable to save the refresh token into our + // database, it's not the end of the world, so we just continue, and hope next time user log in, we are able to save the refresh token + } + + return account + } }, + pages: { signIn: '/auth', }, @@ -88,6 +164,15 @@ export const authOptions: NextAuthOptions = { GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, + + authorization: { + params: { + scope: "openid https://www.googleapis.com/auth/calendar", + prompt: "consent", + access_type: "offline", + response_type: "code", + } + } }), DiscordProvider({ clientId: env.DISCORD_CLIENT_ID, diff --git a/src/server/db/schema/users.ts b/src/server/db/schema/users.ts index 1da47684..f36a63c8 100644 --- a/src/server/db/schema/users.ts +++ b/src/server/db/schema/users.ts @@ -74,7 +74,7 @@ export const accounts = pgTable( providerAccountId: text('providerAccountId').notNull(), refresh_token: text('refresh_token'), access_token: text('access_token'), - expires_at: integer('expires_at'), + expires_at: integer('expires_at').notNull(), token_type: text('token_type'), scope: text('scope'), id_token: text('id_token'),