From 14f621aa4fbac6cf529d635937a21fee9f48bae4 Mon Sep 17 00:00:00 2001 From: AbdBarho Date: Sat, 17 Jun 2023 19:53:44 +0200 Subject: [PATCH 1/2] Add code to merge frontend users --- website/package-lock.json | 32 ++++---- website/package.json | 4 +- .../20230617160951_backend_ids/migration.sql | 17 +++++ website/prisma/schema.prisma | 15 +++- website/src/lib/oasst_api_client.ts | 6 +- website/src/lib/users.ts | 74 +++++++++++++++++++ 6 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 website/prisma/migrations/20230617160951_backend_ids/migration.sql diff --git a/website/package-lock.json b/website/package-lock.json index 25825c4c32..e11024ccf7 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -25,7 +25,7 @@ "@next-auth/prisma-adapter": "^1.0.6", "@next/bundle-analyzer": "^13.4.4", "@nikolovlazar/chakra-ui-prose": "^1.2.1", - "@prisma/client": "^4.13.0", + "@prisma/client": "^4.15.0", "@tailwindcss/forms": "^0.5.3", "@tanstack/react-table": "^8.9.1", "autoprefixer": "^10.4.14", @@ -101,7 +101,7 @@ "path-browserify": "^1.0.1", "pino-pretty": "^10.0.0", "prettier": "^2.8.8", - "prisma": "^4.14.0", + "prisma": "^4.15.0", "storybook": "^7.0.9", "ts-essentials": "^9.3.2", "ts-node": "^10.9.1", @@ -5490,12 +5490,12 @@ } }, "node_modules/@prisma/client": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.13.0.tgz", - "integrity": "sha512-YaiiICcRB2hatxsbnfB66uWXjcRw3jsZdlAVxmx0cFcTc/Ad/sKdHCcWSnqyDX47vAewkjRFwiLwrOUjswVvmA==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.15.0.tgz", + "integrity": "sha512-xnROvyABcGiwqRNdrObHVZkD9EjkJYHOmVdlKy1yGgI+XOzvMzJ4tRg3dz1pUlsyhKxXGCnjIQjWW+2ur+YXuw==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a" + "@prisma/engines-version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944" }, "engines": { "node": ">=14.17" @@ -5510,16 +5510,16 @@ } }, "node_modules/@prisma/engines": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.14.0.tgz", - "integrity": "sha512-PDNlhP/1vyTgmNyiucGqGCdXIp7HIkkvKO50si3y3PcceeHvqtiKPaH1iJdz63jCWMVMbj2MElSxXPOeBvEVIQ==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.15.0.tgz", + "integrity": "sha512-FTaOCGs0LL0OW68juZlGxFtYviZa4xdQj/rQEdat2txw0s3Vu/saAPKjNVXfIgUsGXmQ72HPgNr6935/P8FNAA==", "devOptional": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a.tgz", - "integrity": "sha512-fsQlbkhPJf08JOzKoyoD9atdUijuGBekwoOPZC3YOygXEml1MTtgXVpnUNchQlRSY82OQ6pSGQ9PxUe4arcSLQ==" + "version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944.tgz", + "integrity": "sha512-sVOig4tjGxxlYaFcXgE71f/rtFhzyYrfyfNFUsxCIEJyVKU9rdOWIlIwQ2NQ7PntvGnn+x0XuFo4OC1jvPJKzg==" }, "node_modules/@rushstack/eslint-patch": { "version": "1.2.0", @@ -27333,13 +27333,13 @@ } }, "node_modules/prisma": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.14.0.tgz", - "integrity": "sha512-+5dMl1uxMQb4RepndY6AwR9xi1cDcaGFICu+ws6/Nmgt93mFPNj8tYxSfTdmfg+rkNrUId9rk/Ac2vTgLe/oXA==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.15.0.tgz", + "integrity": "sha512-iKZZpobPl48gTcSZVawLMQ3lEy6BnXwtoMj7hluoGFYu2kQ6F9LBuBrUyF95zRVnNo8/3KzLXJXJ5TEnLSJFiA==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "4.14.0" + "@prisma/engines": "4.15.0" }, "bin": { "prisma": "build/index.js", diff --git a/website/package.json b/website/package.json index f9f197fe94..d56e12e062 100644 --- a/website/package.json +++ b/website/package.json @@ -46,7 +46,7 @@ "@next-auth/prisma-adapter": "^1.0.6", "@next/bundle-analyzer": "^13.4.4", "@nikolovlazar/chakra-ui-prose": "^1.2.1", - "@prisma/client": "^4.13.0", + "@prisma/client": "^4.15.0", "@tailwindcss/forms": "^0.5.3", "@tanstack/react-table": "^8.9.1", "autoprefixer": "^10.4.14", @@ -122,7 +122,7 @@ "path-browserify": "^1.0.1", "pino-pretty": "^10.0.0", "prettier": "^2.8.8", - "prisma": "^4.14.0", + "prisma": "^4.15.0", "storybook": "^7.0.9", "ts-essentials": "^9.3.2", "ts-node": "^10.9.1", diff --git a/website/prisma/migrations/20230617160951_backend_ids/migration.sql b/website/prisma/migrations/20230617160951_backend_ids/migration.sql new file mode 100644 index 0000000000..c4dd41b81f --- /dev/null +++ b/website/prisma/migrations/20230617160951_backend_ids/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "BackendInfo" ( + "id" TEXT NOT NULL, + "frontendUserId" TEXT NOT NULL, + "backendUserId" TEXT NOT NULL, + + CONSTRAINT "BackendInfo_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "BackendInfo_frontendUserId_key" ON "BackendInfo"("frontendUserId"); + +-- CreateIndex +CREATE UNIQUE INDEX "BackendInfo_backendUserId_key" ON "BackendInfo"("backendUserId"); + +-- AddForeignKey +ALTER TABLE "BackendInfo" ADD CONSTRAINT "BackendInfo_frontendUserId_fkey" FOREIGN KEY ("frontendUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/website/prisma/schema.prisma b/website/prisma/schema.prisma index f59b8e9fd6..7b2d6f666f 100644 --- a/website/prisma/schema.prisma +++ b/website/prisma/schema.prisma @@ -44,9 +44,18 @@ model User { isNew Boolean @default(true) role String @default("general") - accounts Account[] - sessions Session[] - tasks RegisteredTask[] + accounts Account[] + sessions Session[] + tasks RegisteredTask[] + backendInfo BackendInfo? +} + +model BackendInfo { + id String @id @default(cuid()) + frontendUserId String @unique + backendUserId String @unique + + user User @relation(fields: [frontendUserId], references: [id], onDelete: Cascade) } model VerificationToken { diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts index d947aaa701..6f52ec6eb6 100644 --- a/website/src/lib/oasst_api_client.ts +++ b/website/src/lib/oasst_api_client.ts @@ -434,7 +434,7 @@ export class OasstApiClient { }); } - fetch_frontend_user(user: BackendUserCore) { + fetch_frontend_user(user: Omit) { return this.get(`/api/v1/frontend_users/${user.auth_method}/${user.id}`); } @@ -481,4 +481,8 @@ export class OasstApiClient { const backendUser = await this.fetch_frontend_user(user); return this.delete(`/api/v1/users/${backendUser.user_id}`); } + + merge_backend_users(destination_user_id: string, source_user_ids: string[]) { + return this.post("/api/v1/admin/merge_users", { destination_user_id, source_user_ids }); + } } diff --git a/website/src/lib/users.ts b/website/src/lib/users.ts index 2f1a9d9705..465122714b 100644 --- a/website/src/lib/users.ts +++ b/website/src/lib/users.ts @@ -4,6 +4,8 @@ import { AuthMethod } from "src/types/Providers"; import type { BackendUserCore } from "src/types/Users"; import { logger } from "./logger"; +import { OasstError } from "./oasst_api_client"; +import { userlessApiClient } from "./oasst_client_factory"; /** * Returns a `BackendUserCore` that can be used for interacting with the Backend service. @@ -111,3 +113,75 @@ export const getBatchFrontendUserIdFromBackendUser = async (users: { username: s return outputIds; }; + +/** + * merges all backend users into one, and saves the value in the database + * + * This function is currently unused + * + * TODO: do we need to make this idempotent? should we check if the user is already merged + * before we continue with the merging? + */ +export const mergeUserAccountsInBackend = async (frontendId: string): Promise => { + const user = await prisma.user.findUnique({ + where: { id: frontendId }, + select: { id: true, name: true, emailVerified: true, accounts: true }, + }); + + const accounts = user.accounts + .map((x) => ({ + auth_method: x.provider as AuthMethod, + id: x.providerAccountId, + })) + .concat([{ auth_method: "local", id: frontendId }]); + + const backendUsers = await Promise.all( + accounts.map((account) => + userlessApiClient.fetch_frontend_user(account).catch((err) => { + // if 404, thats okay + if (!(err instanceof OasstError) || err.httpStatusCode !== 404) { + console.error(err); + } + return null; + }) + ) + ); + + const backendIds = backendUsers.filter(Boolean).map((user) => user.user_id); + if (backendIds.length === 0) { + logger.error({ message: `Wanted to merge user accounts but found none.`, frontendId, accounts }); + return null; + } + + // id after merge + let backendId: string; + + if (backendIds.length === 1) { + backendId = backendIds[0]; + logger.info({ message: `Wanted to merge user accounts, but found only one, skipping.`, frontendId, backendId }); + } else { + logger.info({ message: "Merging user accounts", frontendId, accounts, backendIds }); + let remainingIds: string[]; + + [backendId, ...remainingIds] = backendIds; + + await userlessApiClient.merge_backend_users(backendId, remainingIds); + logger.info({ message: "Merging successful", frontendId, accounts, backendIds }); + } + + // write to database + await prisma.backendInfo.upsert({ + where: { + frontendUserId: frontendId, + }, + create: { + frontendUserId: frontendId, + backendUserId: backendId, + }, + update: { + backendUserId: backendId, + }, + }); + + return backendId; +}; From 7852518df5e20a90433f90b82cd55831c71cd120 Mon Sep 17 00:00:00 2001 From: AbdBarho Date: Sat, 17 Jun 2023 20:11:32 +0200 Subject: [PATCH 2/2] add db migrations --- website/wait-for-postgres.sh | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/website/wait-for-postgres.sh b/website/wait-for-postgres.sh index 53360adacc..90e91d5e25 100755 --- a/website/wait-for-postgres.sh +++ b/website/wait-for-postgres.sh @@ -8,7 +8,7 @@ set -e # validate schema -npx prisma validate +npx --yes prisma validate # wait until the db is available until echo 'SELECT version();' | npx prisma db execute --stdin; do @@ -16,15 +16,9 @@ until echo 'SELECT version();' | npx prisma db execute --stdin; do sleep 1 done -echo >&2 "Postgres is up - executing command" +echo >&2 "Postgres is up - applying migrations" -# TODO: replace this command with applying migrations: npx prisma migrate deploy -# NOTE: because of our previous setup where we just synced the database, we have to "simulate" -# the initial migration with this command: -# npx prisma migrate resolve --applied 20230326131923_initial_migration -# prisma will fail with the above command if resolve is already applied, -# we might need to run the command with set +e -npx prisma db push --skip-generate +npx prisma migrate deploy # Print and execute all other arguments starting with `$1` # So `exec "$1" "$2" "$3" ...`