From 4476a7618095783e321a8c0112b00abafce15801 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sun, 5 Oct 2025 10:45:39 +0200 Subject: [PATCH] Fix email --- api/.env.development | 9 +- api/.env.production | 10 +- api/.env.staging | 10 +- api/package-lock.json | 368 +++++++++++++++++++++++++++++++++++++++++ api/package.json | 1 + api/src/config.ts | 17 +- api/src/middleware.ts | 15 -- api/src/tablo.ts | 56 +++---- api/src/transporter.ts | 33 ++++ api/src/user.ts | 9 +- ui/src/hooks/public.ts | 6 +- ui/src/hooks/tablos.ts | 2 + 12 files changed, 471 insertions(+), 65 deletions(-) create mode 100644 api/src/transporter.ts diff --git a/api/.env.development b/api/.env.development index 887d2a4..8e2eb2b 100644 --- a/api/.env.development +++ b/api/.env.development @@ -4,8 +4,6 @@ SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhY STREAM_CHAT_API_KEY=t5vvvddteapa STREAM_CHAT_API_SECRET=zrr32sqenw3atpv9rnz2nhhyyncf7bunr7fmfqy9r7e69fcw978dhzevmhpxa2jj -EMAIL_USER="baptiste@xtablo.com" -EMAIL_KEY="jayf pzpj nrsv vtim" XTABLO_URL="https://app-staging.xtablo.com" CORS_ORIGIN="http://localhost:5173" @@ -14,4 +12,9 @@ R2_ACCOUNT_ID="9715fa14c5e5d1612301572cf1c6bbee" R2_ACCESS_KEY_ID="caeb987bbcd601708a93c6aa562064ef" R2_SECRET_ACCESS_KEY="42e455b25804687f7cff3d15be23c1f0f47ca742d7a41b6fa1a05a91041e0215" -SYNC_CALS_SECRET="hello" \ No newline at end of file +SYNC_CALS_SECRET="hello" + +EMAIL_USER="baptiste@xtablo.com" +EMAIL_CLIENT_ID="904332563417-e2n7pchtgnkrkp360baaebfeig55maig.apps.googleusercontent.com" +EMAIL_CLIENT_SECRET="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" +EMAIL_REFRESH_TOKEN="1//04dRsWFVjr0mqCgYIARAAGAQSNwF-L9IrN3JicCv2ib4F6AQlactB4CE6Q4ST_tEVVdmmECly_-05INeTeqidxmpRHHDJDM8UFBk" \ No newline at end of file diff --git a/api/.env.production b/api/.env.production index 5c36144..b4edc7a 100644 --- a/api/.env.production +++ b/api/.env.production @@ -4,9 +4,6 @@ SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhY STREAM_CHAT_API_KEY=v4yf8rs94aa8 STREAM_CHAT_API_SECRET=jq2szvv73ua7sz9tvr9y24dxg37sw8ue8t576fu7ggr4h6wvcmunby4gvte8tm8f -EMAIL_USER="baptiste@xtablo.com" -EMAIL_KEY="jayf pzpj nrsv vtim" - XTABLO_URL=https://app.xtablo.com CORS_ORIGIN="https://app.xtablo.com" @@ -14,4 +11,9 @@ R2_ACCOUNT_ID="9715fa14c5e5d1612301572cf1c6bbee" R2_ACCESS_KEY_ID="caeb987bbcd601708a93c6aa562064ef" R2_SECRET_ACCESS_KEY="42e455b25804687f7cff3d15be23c1f0f47ca742d7a41b6fa1a05a91041e0215" -SYNC_CALS_SECRET="gT3BAytmNwhe1wKmvgREBlWcqK0=" \ No newline at end of file +SYNC_CALS_SECRET="gT3BAytmNwhe1wKmvgREBlWcqK0=" + +EMAIL_USER="baptiste@xtablo.com" +EMAIL_CLIENT_ID="904332563417-e2n7pchtgnkrkp360baaebfeig55maig.apps.googleusercontent.com" +EMAIL_CLIENT_SECRET="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" +EMAIL_REFRESH_TOKEN="1//04dRsWFVjr0mqCgYIARAAGAQSNwF-L9IrN3JicCv2ib4F6AQlactB4CE6Q4ST_tEVVdmmECly_-05INeTeqidxmpRHHDJDM8UFBk" \ No newline at end of file diff --git a/api/.env.staging b/api/.env.staging index dd39b3f..13e0862 100644 --- a/api/.env.staging +++ b/api/.env.staging @@ -4,12 +4,14 @@ SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhY STREAM_CHAT_API_KEY=t5vvvddteapa STREAM_CHAT_API_SECRET=zrr32sqenw3atpv9rnz2nhhyyncf7bunr7fmfqy9r7e69fcw978dhzevmhpxa2jj -EMAIL_USER="baptiste@xtablo.com" -EMAIL_KEY="jayf pzpj nrsv vtim" - XTABLO_URL="https://app-staging.xtablo.com" CORS_ORIGIN="https://app-staging.xtablo.com" R2_ACCOUNT_ID="9715fa14c5e5d1612301572cf1c6bbee" R2_ACCESS_KEY_ID="caeb987bbcd601708a93c6aa562064ef" -R2_SECRET_ACCESS_KEY="42e455b25804687f7cff3d15be23c1f0f47ca742d7a41b6fa1a05a91041e0215" \ No newline at end of file +R2_SECRET_ACCESS_KEY="42e455b25804687f7cff3d15be23c1f0f47ca742d7a41b6fa1a05a91041e0215" + +EMAIL_USER="baptiste@xtablo.com" +EMAIL_CLIENT_ID="904332563417-e2n7pchtgnkrkp360baaebfeig55maig.apps.googleusercontent.com" +EMAIL_CLIENT_SECRET="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" +EMAIL_REFRESH_TOKEN="1//04dRsWFVjr0mqCgYIARAAGAQSNwF-L9IrN3JicCv2ib4F6AQlactB4CE6Q4ST_tEVVdmmECly_-05INeTeqidxmpRHHDJDM8UFBk" \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 7660817..8a4a3f0 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,6 +11,7 @@ "@supabase/supabase-js": "^2.49.4", "cors": "^2.8.5", "dotenv": "^16.5.0", + "googleapis": "^161.0.0", "graphile-worker": "^0.16.6", "hono": "^4.7.7", "hono-sessions": "^0.7.2", @@ -2279,6 +2280,14 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -2389,6 +2398,14 @@ ], "license": "MIT" }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2468,6 +2485,21 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2654,6 +2686,14 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2865,6 +2905,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/fast-xml-parser": { "version": "5.2.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", @@ -2882,6 +2927,28 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2954,6 +3021,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2984,6 +3062,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.2.tgz", + "integrity": "sha512-/Szrn8nr+2TsQT1Gp8iIe/BEytJmbyfrbFh419DfGQSkEgNEhbPi7JRJuughjkTzPWgU9gBQf5AVu3DbHt0OXA==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", + "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3083,6 +3187,77 @@ "node": ">= 6" } }, + "node_modules/google-auth-library": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.4.0.tgz", + "integrity": "sha512-CmIrSy1bqMQUsPmA9+hcSbAXL80cFhu40cGMUjCaLpNKVzzvi+0uAHq8GNZxkoGYIsTX4ZQ7e4aInAqWxgn4fg==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^7.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/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/google-logging-utils": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.1.tgz", + "integrity": "sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "161.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-161.0.0.tgz", + "integrity": "sha512-JZy2cWMxgUF8E09KHzplI+z+FVG8NWDB/bsf4xevt9Um4bInb0X1qaG9qpDn49DHT5HsU0mOp3EOBGb8+AdE3Q==", + "dependencies": { + "google-auth-library": "^10.2.0", + "googleapis-common": "^8.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/googleapis-common": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.0.tgz", + "integrity": "sha512-66if47It7y+Sab3HMkwEXx1kCq9qUC9px8ZXoj1CMrmLmUw81GpbnsNlXnlyZyGbGPGcj+tDD9XsZ23m7GLaJQ==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^7.0.0-rc.4", + "google-auth-library": "^10.1.0", + "qs": "^6.7.0", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3144,6 +3319,37 @@ "node": ">=14.0.0" } }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/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/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3218,6 +3424,18 @@ "iron-webcrypto": "^1.2.1" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3367,6 +3585,14 @@ "js-yaml": "bin/js-yaml.js" } }, + "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-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", @@ -3731,6 +3957,42 @@ "path-to-regexp": "^6.2.1" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/nodemailer": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.4.tgz", @@ -3756,6 +4018,17 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3994,6 +4267,20 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4095,6 +4382,74 @@ "randombytes": "^2.1.0" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -4354,6 +4709,11 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "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/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4384,6 +4744,14 @@ "node": ">= 0.8" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/api/package.json b/api/package.json index c796b2a..9afe6f5 100644 --- a/api/package.json +++ b/api/package.json @@ -14,6 +14,7 @@ "@supabase/supabase-js": "^2.49.4", "cors": "^2.8.5", "dotenv": "^16.5.0", + "googleapis": "^161.0.0", "graphile-worker": "^0.16.6", "hono": "^4.7.7", "hono-sessions": "^0.7.2", diff --git a/api/src/config.ts b/api/src/config.ts index b7fafe6..ad15324 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -9,7 +9,9 @@ export interface AppConfig { STREAM_CHAT_API_KEY: string; STREAM_CHAT_API_SECRET: string; EMAIL_USER: string; - EMAIL_KEY: string; + EMAIL_CLIENT_ID: string; + EMAIL_CLIENT_SECRET: string; + EMAIL_REFRESH_TOKEN: string; XTABLO_URL: string; R2_ACCOUNT_ID: string; R2_ACCESS_KEY_ID: string; @@ -54,7 +56,18 @@ function createConfig(): AppConfig { process.env.STREAM_CHAT_API_SECRET ), EMAIL_USER: validateEnvVar("EMAIL_USER", process.env.EMAIL_USER), - EMAIL_KEY: validateEnvVar("EMAIL_KEY", process.env.EMAIL_KEY), + EMAIL_CLIENT_ID: validateEnvVar( + "EMAIL_CLIENT_ID", + process.env.EMAIL_CLIENT_ID + ), + EMAIL_CLIENT_SECRET: validateEnvVar( + "EMAIL_CLIENT_SECRET", + process.env.EMAIL_CLIENT_SECRET + ), + EMAIL_REFRESH_TOKEN: validateEnvVar( + "EMAIL_REFRESH_TOKEN", + process.env.EMAIL_REFRESH_TOKEN + ), CORS_ORIGIN: [process.env.CORS_ORIGIN || "https://app.xtablo.com"], XTABLO_URL: process.env.XTABLO_URL || "https://app.xtablo.com", R2_ACCOUNT_ID: validateEnvVar("R2_ACCOUNT_ID", process.env.R2_ACCOUNT_ID), diff --git a/api/src/middleware.ts b/api/src/middleware.ts index b2a5d78..8b192fb 100644 --- a/api/src/middleware.ts +++ b/api/src/middleware.ts @@ -1,7 +1,6 @@ import { S3Client } from "@aws-sdk/client-s3"; import { createClient, type User } from "@supabase/supabase-js"; import type { Context, Next } from "hono"; -import nodemailer from "nodemailer"; import { StreamChat } from "stream-chat"; import { config } from "./config.js"; @@ -40,20 +39,6 @@ export const supabaseMiddleware = async (c: Context, next: Next) => { await next(); }; -export const emailMiddleware = async (c: Context, next: Next) => { - const transporter = nodemailer.createTransport({ - host: "smtp.gmail.com", - port: 465, - secure: true, - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_KEY, - }, - }); - c.set("transporter", transporter); - await next(); -}; - export const streamChatMiddleware = async (c: Context, next: Next) => { const serverClient = StreamChat.getInstance( process.env.STREAM_CHAT_API_KEY as string, diff --git a/api/src/tablo.ts b/api/src/tablo.ts index 16fa032..ef15726 100644 --- a/api/src/tablo.ts +++ b/api/src/tablo.ts @@ -1,7 +1,6 @@ import { Hono } from "hono"; import { authMiddleware, - emailMiddleware, r2Middleware, streamChatMiddleware, } from "./middleware.js"; @@ -22,6 +21,7 @@ import type { } from "./types.ts"; import { PutObjectCommand, type S3Client } from "@aws-sdk/client-s3"; import { generateICSFromEvents, writeCalendarFileToR2 } from "./helpers.js"; +import { transporter } from "./transporter.js"; export const tabloRouter = new Hono<{ Variables: { @@ -44,7 +44,6 @@ export const tabloRouter = new Hono<{ // webcalRouter.use(r2Middleware); tabloRouter.use(authMiddleware); -tabloRouter.use(emailMiddleware); tabloRouter.use(streamChatMiddleware); tabloRouter.use(r2Middleware); @@ -172,8 +171,6 @@ tabloRouter.post("/create-and-invite", async (c) => { return c.json({ error: existingTabloError.message }, 500); } - console.log("existingTablo", existingTablo); - let tabloData: { id: string; name: string } | null = null; if (!existingTablo.length) { @@ -203,15 +200,20 @@ tabloRouter.post("/create-and-invite", async (c) => { // Grant access to the current user (invited user) as a non-admin member const { error: tabloAccessError } = await supabase .from("tablo_access") - .insert({ - tablo_id: tabloData.id, - user_id: user.id, - // ** IMPORTANT ** - is_admin: false, - // ------------- - is_active: true, - granted_by: ownerId, - }); + .insert( + { + tablo_id: tabloData.id, + user_id: user.id, + // ** IMPORTANT ** + is_admin: false, + // ------------- + is_active: true, + granted_by: ownerId, + } + // { + // onConflict: "tablo_id, user_id", + // } + ); if (tabloAccessError) { console.error("tabloAccessError", tabloAccessError); @@ -240,14 +242,11 @@ tabloRouter.post("/create-and-invite", async (c) => { }); // Send email notifications to both owner and invited user - const transporter = c.get("transporter"); - - if (transporter) { - // Send email to the owner - await transporter.sendMail({ - to: ownerDataTyped.email, - subject: "Nouveau tablo créé - Réservation confirmée", - html: ` + // Send email to the owner + await transporter.sendMail({ + to: ownerDataTyped.email, + subject: "Nouveau tablo créé - Réservation confirmée", + html: `

Votre tablo a été créé avec succès !

Bonjour ${ownerDataTyped.name},

Un nouveau tablo "${tabloData.name}" a été créé suite à une réservation.

@@ -261,13 +260,13 @@ tabloRouter.post("/create-and-invite", async (c) => {

Participant : ${invitedUserDataTyped.name} (${invitedUserDataTyped.email})

Vous pouvez gérer ce tablo depuis votre tableau de bord.

`, - }); + }); - // Send email to the invited user - await transporter.sendMail({ - to: invitedUserDataTyped.email, - subject: "Réservation confirmée - Nouveau tablo créé", - html: ` + // Send email to the invited user + await transporter.sendMail({ + to: invitedUserDataTyped.email, + subject: "Réservation confirmée - Nouveau tablo créé", + html: `

Votre réservation est confirmée !

Bonjour ${invitedUserDataTyped.name},

Votre réservation a été confirmée et un tablo "${tabloData.name}" a été créé.

@@ -281,8 +280,7 @@ tabloRouter.post("/create-and-invite", async (c) => {

Avec : ${ownerDataTyped.name}

Vous recevrez bientôt plus d'informations pour accéder à votre espace de collaboration.

`, - }); - } + }); return c.json({ id: tabloData.id }); }); diff --git a/api/src/transporter.ts b/api/src/transporter.ts new file mode 100644 index 0000000..b4cf283 --- /dev/null +++ b/api/src/transporter.ts @@ -0,0 +1,33 @@ +import nodemailer from "nodemailer"; +import { google } from "googleapis"; +import { config } from "./config.js"; +const OAuth2 = google.auth.OAuth2; + +export const createTransporter = async () => { + const oauth2Client = new OAuth2( + config.EMAIL_CLIENT_ID, + config.EMAIL_CLIENT_SECRET, + "https://developers.google.com/oauthplayground" + ); + + oauth2Client.setCredentials({ + refresh_token: config.EMAIL_REFRESH_TOKEN, + }); + + const transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: "OAuth2", + user: config.EMAIL_USER, + clientId: config.EMAIL_CLIENT_ID, + clientSecret: config.EMAIL_CLIENT_SECRET, + refreshToken: config.EMAIL_REFRESH_TOKEN, + }, + }); + + return transporter; +}; + +export const transporter = await createTransporter(); diff --git a/api/src/user.ts b/api/src/user.ts index 16e185b..8e047b5 100644 --- a/api/src/user.ts +++ b/api/src/user.ts @@ -1,13 +1,10 @@ import { Hono } from "hono"; -import { - authMiddleware, - emailMiddleware, - streamChatMiddleware, -} from "./middleware.js"; +import { authMiddleware, streamChatMiddleware } from "./middleware.js"; import type { SupabaseClient, User } from "@supabase/supabase-js"; import { StreamChat } from "stream-chat"; import type { Transporter } from "nodemailer"; import type { Tables } from "./database.types.ts"; +import { transporter } from "./transporter.js"; export const userRouter = new Hono<{ Variables: { @@ -20,7 +17,6 @@ export const userRouter = new Hono<{ userRouter.use(authMiddleware); userRouter.use(streamChatMiddleware); -userRouter.use(emailMiddleware); userRouter.post("/sign-up-to-stream", async (c) => { const { id } = c.get("user"); @@ -79,7 +75,6 @@ userRouter.get("/me", async (c) => { userRouter.post("/mark-temporary", async (c) => { const user = c.get("user"); const supabase = c.get("supabase"); - const transporter = c.get("transporter"); const body = await c.req.json(); const { temporary_password } = body; diff --git a/ui/src/hooks/public.ts b/ui/src/hooks/public.ts index ebd4448..2556245 100644 --- a/ui/src/hooks/public.ts +++ b/ui/src/hooks/public.ts @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { api } from "@ui/lib/api"; +import { api, queryClient } from "@ui/lib/api"; import { EventTypeConfig } from "./event-types"; export type TimeSlot = { @@ -28,3 +28,7 @@ export function usePublicSlots(shortUserId: string, standardName: string) { enabled: !!shortUserId && !!standardName, }); } + +export const invalidatePublicSlots = () => { + queryClient.invalidateQueries({ queryKey: ["public-slots"] }); +}; diff --git a/ui/src/hooks/tablos.ts b/ui/src/hooks/tablos.ts index 9ad4fdb..79a94ef 100644 --- a/ui/src/hooks/tablos.ts +++ b/ui/src/hooks/tablos.ts @@ -9,6 +9,7 @@ import { useUser } from "@ui/providers/UserStoreProvider"; import { CreateTablo } from "@ui/types/tablos.types"; import { EventInsertInTablo } from "@ui/types/events.types"; import { useNavigate } from "react-router-dom"; +import { invalidatePublicSlots } from "@ui/hooks/public"; type Tablo = Database["public"]["Tables"]["tablos"]; @@ -141,6 +142,7 @@ export const useCreateTabloWithOwner = () => { }, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ["tablos"] }); + invalidatePublicSlots(); navigate(`/chat/${data.id}`, { replace: true }); navigate(0); },