From 26ee24396c92b11ee99e704ab69266a3f2b06338 Mon Sep 17 00:00:00 2001 From: Omer Adeel Date: Sun, 2 Jun 2024 02:15:50 -0400 Subject: [PATCH 1/7] started migration --- backend/typescript/models/prisma/schema.prisma | 14 ++++++++++++++ backend/typescript/package.json | 1 + 2 files changed, 15 insertions(+) create mode 100644 backend/typescript/models/prisma/schema.prisma diff --git a/backend/typescript/models/prisma/schema.prisma b/backend/typescript/models/prisma/schema.prisma new file mode 100644 index 0000000..ccc84fb --- /dev/null +++ b/backend/typescript/models/prisma/schema.prisma @@ -0,0 +1,14 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_DEV}` +} diff --git a/backend/typescript/package.json b/backend/typescript/package.json index 70318ca..4554461 100644 --- a/backend/typescript/package.json +++ b/backend/typescript/package.json @@ -37,6 +37,7 @@ "node-fetch": "^2.6.1", "nodemailer": "^6.5.0", "pg": "^8.5.1", + "prisma": "^5.14.0", "reflect-metadata": "^0.1.13", "sequelize": "^6.5.0", "sequelize-typescript": "^2.1.0", From b13d3029062af34676b17e2f35b6754fcb98b1e6 Mon Sep 17 00:00:00 2001 From: Cynthia Shen <105977188+shencynthia@users.noreply.github.com> Date: Fri, 21 Jun 2024 22:17:54 -0400 Subject: [PATCH 2/7] Migrate Models To Prisma (#190) * Add all primsa models * Install primsa dependencies * Apply initial migraiton * added port to docker compose --------- Co-authored-by: Omer Adeel --- .../20240603001642_init/migration.sql | 54 +++++++++++++++++++ .../prisma/migrations/migration_lock.toml | 3 ++ .../typescript/models/prisma/schema.prisma | 51 +++++++++++++++++- backend/typescript/package.json | 3 +- backend/typescript/yarn.lock | 48 +++++++++++++++++ docker-compose.yml | 1 + 6 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql create mode 100644 backend/typescript/models/prisma/migrations/migration_lock.toml diff --git a/backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql b/backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql new file mode 100644 index 0000000..f2c6f3e --- /dev/null +++ b/backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql @@ -0,0 +1,54 @@ +-- CreateEnum +CREATE TYPE "enum_entities_enum_field" AS ENUM ('A', 'B', 'C', 'D'); + +-- CreateEnum +CREATE TYPE "enum_users_role" AS ENUM ('User', 'Admin'); + +-- CreateTable +CREATE TABLE "SequelizeMeta" ( + "name" VARCHAR(255) NOT NULL, + + CONSTRAINT "SequelizeMeta_pkey" PRIMARY KEY ("name") +); + +-- CreateTable +CREATE TABLE "entities" ( + "id" SERIAL NOT NULL, + "string_field" VARCHAR(255) NOT NULL, + "int_field" INTEGER NOT NULL, + "enum_field" "enum_entities_enum_field" NOT NULL, + "string_array_field" VARCHAR(255)[], + "bool_field" BOOLEAN NOT NULL, + "file_name" VARCHAR(255) NOT NULL, + "createdAt" TIMESTAMPTZ(6), + "updatedAt" TIMESTAMPTZ(6), + + CONSTRAINT "entities_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "simple_entities" ( + "id" SERIAL NOT NULL, + "string_field" VARCHAR(255) NOT NULL, + "int_field" INTEGER NOT NULL, + "enum_field" "enum_entities_enum_field" NOT NULL, + "string_array_field" VARCHAR(255)[], + "bool_field" BOOLEAN NOT NULL, + "createdAt" TIMESTAMPTZ(6), + "updatedAt" TIMESTAMPTZ(6), + + CONSTRAINT "simple_entities_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "users" ( + "id" SERIAL NOT NULL, + "first_name" VARCHAR(255) NOT NULL, + "last_name" VARCHAR(255) NOT NULL, + "auth_id" VARCHAR(255) NOT NULL, + "role" "enum_users_role" NOT NULL, + "createdAt" TIMESTAMPTZ(6), + "updatedAt" TIMESTAMPTZ(6), + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); diff --git a/backend/typescript/models/prisma/migrations/migration_lock.toml b/backend/typescript/models/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/backend/typescript/models/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/backend/typescript/models/prisma/schema.prisma b/backend/typescript/models/prisma/schema.prisma index ccc84fb..afe661f 100644 --- a/backend/typescript/models/prisma/schema.prisma +++ b/backend/typescript/models/prisma/schema.prisma @@ -10,5 +10,54 @@ generator client { datasource db { provider = "postgresql" - url = `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_DEV}` + url = env("DATABASE_URL") +} + +model SequelizeMeta { + name String @id @db.VarChar(255) +} + +model entities { + id Int @id @default(autoincrement()) + string_field String @db.VarChar(255) + int_field Int + enum_field enum_entities_enum_field + string_array_field String[] @db.VarChar(255) + bool_field Boolean + file_name String @db.VarChar(255) + createdAt DateTime? @db.Timestamptz(6) + updatedAt DateTime? @db.Timestamptz(6) +} + +model simple_entities { + id Int @id @default(autoincrement()) + string_field String @db.VarChar(255) + int_field Int + enum_field enum_entities_enum_field + string_array_field String[] @db.VarChar(255) + bool_field Boolean + createdAt DateTime? @db.Timestamptz(6) + updatedAt DateTime? @db.Timestamptz(6) +} + +model users { + id Int @id @default(autoincrement()) + first_name String @db.VarChar(255) + last_name String @db.VarChar(255) + auth_id String @db.VarChar(255) + role enum_users_role + createdAt DateTime? @db.Timestamptz(6) + updatedAt DateTime? @db.Timestamptz(6) +} + +enum enum_entities_enum_field { + A + B + C + D +} + +enum enum_users_role { + User + Admin } diff --git a/backend/typescript/package.json b/backend/typescript/package.json index 4554461..dd275a2 100644 --- a/backend/typescript/package.json +++ b/backend/typescript/package.json @@ -14,6 +14,7 @@ "author": "", "license": "ISC", "dependencies": { + "@prisma/client": "5.14.0", "@types/graphql-upload": "^8.0.6", "@types/json2csv": "^5.0.3", "@types/multer": "^1.4.6", @@ -37,7 +38,7 @@ "node-fetch": "^2.6.1", "nodemailer": "^6.5.0", "pg": "^8.5.1", - "prisma": "^5.14.0", + "prisma": "5.14.0", "reflect-metadata": "^0.1.13", "sequelize": "^6.5.0", "sequelize-typescript": "^2.1.0", diff --git a/backend/typescript/yarn.lock b/backend/typescript/yarn.lock index 7d1027c..94a4c36 100644 --- a/backend/typescript/yarn.lock +++ b/backend/typescript/yarn.lock @@ -809,6 +809,47 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" +"@prisma/client@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.14.0.tgz#dadca5bb1137ddcebb454bbdaf89423823d3363f" + integrity sha512-akMSuyvLKeoU4LeyBAUdThP/uhVP3GuLygFE3MlYzaCb3/J8SfsYBE5PkaFuLuVpLyA6sFoW+16z/aPhNAESqg== + +"@prisma/debug@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.14.0.tgz#1227c705893c38284f7c63d72441480ebaa12605" + integrity sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w== + +"@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48": + version "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48.tgz#019c3c75a5c3276e580685fe48cdbfd181176858" + integrity sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA== + +"@prisma/engines@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.14.0.tgz#2ee91dd2220a726c27c906fbea788bbb3efdac6e" + integrity sha512-lgxkKZ6IEygVcw6IZZUlPIfLQ9hjSYAtHjZ5r64sCLDgVzsPFCi2XBBJgzPMkOQ5RHzUD4E/dVdpn9+ez8tk1A== + dependencies: + "@prisma/debug" "5.14.0" + "@prisma/engines-version" "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48" + "@prisma/fetch-engine" "5.14.0" + "@prisma/get-platform" "5.14.0" + +"@prisma/fetch-engine@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.14.0.tgz#45297c118d4ec3fea55129886edd5a429da1f6da" + integrity sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ== + dependencies: + "@prisma/debug" "5.14.0" + "@prisma/engines-version" "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48" + "@prisma/get-platform" "5.14.0" + +"@prisma/get-platform@5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.14.0.tgz#69112d3dde61905f59a65ed818f153e153ca40f0" + integrity sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw== + dependencies: + "@prisma/debug" "5.14.0" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -5628,6 +5669,13 @@ pretty-format@^27.0.6: ansi-styles "^5.0.0" react-is "^17.0.1" +prisma@5.14.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.14.0.tgz#ffc4696a43b044b636c3303b7aa98c13c2ade4dd" + integrity sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q== + dependencies: + "@prisma/engines" "5.14.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" diff --git a/docker-compose.yml b/docker-compose.yml index 0e7dd15..b5c94e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: - /app/node_modules ports: - 8080:8080 + - 5555:5555 dns: - 8.8.8.8 # postgresql { From 74a5fd034de3250509dd57d29e10604ca61d6ea0 Mon Sep 17 00:00:00 2001 From: Omer Adeel Date: Sun, 23 Jun 2024 15:30:43 -0400 Subject: [PATCH 3/7] added output for client --- backend/typescript/models/prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/typescript/models/prisma/schema.prisma b/backend/typescript/models/prisma/schema.prisma index afe661f..d6c1ebb 100644 --- a/backend/typescript/models/prisma/schema.prisma +++ b/backend/typescript/models/prisma/schema.prisma @@ -6,6 +6,7 @@ generator client { provider = "prisma-client-js" + output = "../../node_modules/.prisma/client" } datasource db { From d152b93bf2c4b1d8661e22b74e872da28b950991 Mon Sep 17 00:00:00 2001 From: Rodrigo Tiscareno <68658805+rodrigotiscareno@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:21:22 -0400 Subject: [PATCH 4/7] Prisma Migration - User Service (#197) * user service * restate mongodb logic * fixes to interface and implementation * include unique constraint on auth_id --- .../migration.sql | 3 + .../typescript/models/prisma/schema.prisma | 2 +- .../services/implementations/userService.ts | 854 ++++++++---------- .../services/interfaces/userService.ts | 81 +- 4 files changed, 419 insertions(+), 521 deletions(-) rename backend/typescript/models/prisma/migrations/{20240603001642_init => 20240804194819_init}/migration.sql (94%) diff --git a/backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql b/backend/typescript/models/prisma/migrations/20240804194819_init/migration.sql similarity index 94% rename from backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql rename to backend/typescript/models/prisma/migrations/20240804194819_init/migration.sql index f2c6f3e..13f5ca5 100644 --- a/backend/typescript/models/prisma/migrations/20240603001642_init/migration.sql +++ b/backend/typescript/models/prisma/migrations/20240804194819_init/migration.sql @@ -52,3 +52,6 @@ CREATE TABLE "users" ( CONSTRAINT "users_pkey" PRIMARY KEY ("id") ); + +-- CreateIndex +CREATE UNIQUE INDEX "users_auth_id_key" ON "users"("auth_id"); diff --git a/backend/typescript/models/prisma/schema.prisma b/backend/typescript/models/prisma/schema.prisma index d6c1ebb..59e7c7c 100644 --- a/backend/typescript/models/prisma/schema.prisma +++ b/backend/typescript/models/prisma/schema.prisma @@ -45,7 +45,7 @@ model users { id Int @id @default(autoincrement()) first_name String @db.VarChar(255) last_name String @db.VarChar(255) - auth_id String @db.VarChar(255) + auth_id String @unique @db.VarChar(255) role enum_users_role createdAt DateTime? @db.Timestamptz(6) updatedAt DateTime? @db.Timestamptz(6) diff --git a/backend/typescript/services/implementations/userService.ts b/backend/typescript/services/implementations/userService.ts index 1255ec5..445e14f 100644 --- a/backend/typescript/services/implementations/userService.ts +++ b/backend/typescript/services/implementations/userService.ts @@ -1,367 +1,369 @@ // mongodb { -import * as firebaseAdmin from "firebase-admin"; - -import IUserService from "../interfaces/userService"; -import MgUser, { User } from "../../models/user.model"; -import { CreateUserDTO, Role, UpdateUserDTO, UserDTO } from "../../types"; -import { getErrorMessage } from "../../utilities/errorUtils"; -import logger from "../../utilities/logger"; - -const Logger = logger(__filename); - -const getMongoUserByAuthId = async (authId: string): Promise => { - const user: User | null = await MgUser.findOne({ authId }); - if (!user) { - throw new Error(`user with authId ${authId} not found.`); - } - return user; -}; - -class UserService implements IUserService { - /* eslint-disable class-methods-use-this */ - async getUserById(userId: string): Promise { - let user: User | null; - let firebaseUser: firebaseAdmin.auth.UserRecord; - - try { - user = await MgUser.findById(userId); - - if (!user) { - throw new Error(`userId ${userId} not found.`); - } - - firebaseUser = await firebaseAdmin.auth().getUser(user.authId); - } catch (error: unknown) { - Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); - throw error; + import * as firebaseAdmin from "firebase-admin"; + + import IUserService from "../interfaces/userService"; + import MgUser, { User } from "../../models/user.model"; + import { CreateUserDTO, Role, UpdateUserDTO, UserDTO } from "../../types"; + import { getErrorMessage } from "../../utilities/errorUtils"; + import logger from "../../utilities/logger"; + + const Logger = logger(__filename); + + const getMongoUserByAuthId = async (authId: string): Promise => { + const user: User | null = await MgUser.findOne({ authId }); + if (!user) { + throw new Error(`user with authId ${authId} not found.`); } - - return { - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - email: firebaseUser.email ?? "", - role: user.role, - }; - } - - async getUserByEmail(email: string): Promise { - let user: User | null; - let firebaseUser: firebaseAdmin.auth.UserRecord; - - try { - firebaseUser = await firebaseAdmin.auth().getUserByEmail(email); - user = await MgUser.findOne({ authId: firebaseUser.uid }); - - if (!user) { - throw new Error(`userId with authId ${firebaseUser.uid} not found.`); + return user; + }; + + class UserService implements IUserService { + /* eslint-disable class-methods-use-this */ + async getUserById(userId: string): Promise { + let user: User | null; + let firebaseUser: firebaseAdmin.auth.UserRecord; + + try { + user = await MgUser.findById(userId); + + if (!user) { + throw new Error(`userId ${userId} not found.`); + } + + firebaseUser = await firebaseAdmin.auth().getUser(user.authId); + } catch (error: unknown) { + Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); + throw error; } - } catch (error: unknown) { - Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); - throw error; + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: firebaseUser.email ?? "", + role: user.role, + }; } - - return { - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - email: firebaseUser.email ?? "", - role: user.role, - }; - } - - async getUserRoleByAuthId(authId: string): Promise { - try { - const { role } = await getMongoUserByAuthId(authId); - return role; - } catch (error: unknown) { - Logger.error( - `Failed to get user role. Reason = ${getErrorMessage(error)}`, - ); - throw error; + + async getUserByEmail(email: string): Promise { + let user: User | null; + let firebaseUser: firebaseAdmin.auth.UserRecord; + + try { + firebaseUser = await firebaseAdmin.auth().getUserByEmail(email); + user = await MgUser.findOne({ authId: firebaseUser.uid }); + + if (!user) { + throw new Error(`userId with authId ${firebaseUser.uid} not found.`); + } + } catch (error: unknown) { + Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); + throw error; + } + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: firebaseUser.email ?? "", + role: user.role, + }; } - } - - async getUserIdByAuthId(authId: string): Promise { - try { - const { id } = await getMongoUserByAuthId(authId); - return id; - } catch (error: unknown) { - Logger.error(`Failed to get user id. Reason = ${getErrorMessage(error)}`); - throw error; + + async getUserRoleByAuthId(authId: string): Promise { + try { + const { role } = await getMongoUserByAuthId(authId); + return role; + } catch (error: unknown) { + Logger.error( + `Failed to get user role. Reason = ${getErrorMessage(error)}`, + ); + throw error; + } } - } - - async getAuthIdById(userId: string): Promise { - try { - const user = await MgUser.findById(userId); - if (!user) { - throw new Error(`userId ${userId} not found.`); + + async getUserIdByAuthId(authId: string): Promise { + try { + const { id } = await getMongoUserByAuthId(authId); + return id; + } catch (error: unknown) { + Logger.error(`Failed to get user id. Reason = ${getErrorMessage(error)}`); + throw error; } - return user.authId; - } catch (error: unknown) { - Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); - throw error; } - } - - async getUsers(): Promise> { - let userDtos: Array = []; - - try { - const users: Array = await MgUser.find(); - - userDtos = await Promise.all( - users.map(async (user) => { - let firebaseUser: firebaseAdmin.auth.UserRecord; - - try { - firebaseUser = await firebaseAdmin.auth().getUser(user.authId); - } catch (error) { - Logger.error( - `user with authId ${user.authId} could not be fetched from Firebase`, - ); - throw error; - } - - return { - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - email: firebaseUser.email ?? "", - role: user.role, - }; - }), - ); - } catch (error: unknown) { - Logger.error(`Failed to get users. Reason = ${getErrorMessage(error)}`); - throw error; + + async getAuthIdById(userId: string): Promise { + try { + const user = await MgUser.findById(userId); + if (!user) { + throw new Error(`userId ${userId} not found.`); + } + return user.authId; + } catch (error: unknown) { + Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); + throw error; + } } - - return userDtos; - } - - async createUser( - user: CreateUserDTO, - authId?: string, - signUpMethod = "PASSWORD", - ): Promise { - let newUser: User; - let firebaseUser: firebaseAdmin.auth.UserRecord; - - try { - if (signUpMethod === "GOOGLE") { - /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ - firebaseUser = await firebaseAdmin.auth().getUser(authId!); - } else { - // signUpMethod === PASSWORD - firebaseUser = await firebaseAdmin.auth().createUser({ - email: user.email, - password: user.password, - }); + + async getUsers(): Promise> { + let userDtos: Array = []; + + try { + const users: Array = await MgUser.find(); + + userDtos = await Promise.all( + users.map(async (user) => { + let firebaseUser: firebaseAdmin.auth.UserRecord; + + try { + firebaseUser = await firebaseAdmin.auth().getUser(user.authId); + } catch (error) { + Logger.error( + `user with authId ${user.authId} could not be fetched from Firebase`, + ); + throw error; + } + + return { + id: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: firebaseUser.email ?? "", + role: user.role, + }; + }), + ); + } catch (error: unknown) { + Logger.error(`Failed to get users. Reason = ${getErrorMessage(error)}`); + throw error; } - + + return userDtos; + } + + async createUser( + user: CreateUserDTO, + authId?: string, + signUpMethod = "PASSWORD", + ): Promise { + let newUser: User; + let firebaseUser: firebaseAdmin.auth.UserRecord; + try { - newUser = await MgUser.create({ - firstName: user.firstName, - lastName: user.lastName, - authId: firebaseUser.uid, - role: user.role, - }); - } catch (mongoDbError) { - // rollback user creation in Firebase + if (signUpMethod === "GOOGLE") { + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + firebaseUser = await firebaseAdmin.auth().getUser(authId!); + } else { + // signUpMethod === PASSWORD + firebaseUser = await firebaseAdmin.auth().createUser({ + email: user.email, + password: user.password, + }); + } + try { - await firebaseAdmin.auth().deleteUser(firebaseUser.uid); - } catch (firebaseError: unknown) { - const errorMessage = [ - "Failed to rollback Firebase user creation after MongoDB user creation failure. Reason =", - getErrorMessage(firebaseError), - "Orphaned authId (Firebase uid) =", - firebaseUser.uid, - ]; - Logger.error(errorMessage.join(" ")); + newUser = await MgUser.create({ + firstName: user.firstName, + lastName: user.lastName, + authId: firebaseUser.uid, + role: user.role, + }); + } catch (mongoDbError) { + // rollback user creation in Firebase + try { + await firebaseAdmin.auth().deleteUser(firebaseUser.uid); + } catch (firebaseError: unknown) { + const errorMessage = [ + "Failed to rollback Firebase user creation after MongoDB user creation failure. Reason =", + getErrorMessage(firebaseError), + "Orphaned authId (Firebase uid) =", + firebaseUser.uid, + ]; + Logger.error(errorMessage.join(" ")); + } + + throw mongoDbError; } - - throw mongoDbError; + } catch (error: unknown) { + Logger.error(`Failed to create user. Reason = ${getErrorMessage(error)}`); + throw error; } - } catch (error: unknown) { - Logger.error(`Failed to create user. Reason = ${getErrorMessage(error)}`); - throw error; + + return { + id: newUser.id, + firstName: newUser.firstName, + lastName: newUser.lastName, + email: firebaseUser.email ?? "", + role: newUser.role, + }; } - - return { - id: newUser.id, - firstName: newUser.firstName, - lastName: newUser.lastName, - email: firebaseUser.email ?? "", - role: newUser.role, - }; - } - - async updateUserById(userId: string, user: UpdateUserDTO): Promise { - let oldUser: User | null; - let updatedFirebaseUser: firebaseAdmin.auth.UserRecord; - - try { - // must explicitly specify runValidators when updating through findByIdAndUpdate - oldUser = await MgUser.findByIdAndUpdate( - userId, - { firstName: user.firstName, lastName: user.lastName, role: user.role }, - { runValidators: true }, - ); - - if (!oldUser) { - throw new Error(`userId ${userId} not found.`); - } - + + async updateUserById(userId: string, user: UpdateUserDTO): Promise { + let oldUser: User | null; + let updatedFirebaseUser: firebaseAdmin.auth.UserRecord; + try { - updatedFirebaseUser = await firebaseAdmin - .auth() - .updateUser(oldUser.authId, { email: user.email }); - } catch (error) { - // rollback MongoDB user updates + // must explicitly specify runValidators when updating through findByIdAndUpdate + oldUser = await MgUser.findByIdAndUpdate( + userId, + { firstName: user.firstName, lastName: user.lastName, role: user.role }, + { runValidators: true }, + ); + + if (!oldUser) { + throw new Error(`userId ${userId} not found.`); + } + try { - await MgUser.findByIdAndUpdate( - userId, - { - firstName: oldUser.firstName, - lastName: oldUser.lastName, - role: oldUser.role, - }, - { runValidators: true }, - ); - } catch (mongoDbError: unknown) { - const errorMessage = [ - "Failed to rollback MongoDB user update after Firebase user update failure. Reason =", - getErrorMessage(mongoDbError), - "MongoDB user id with possibly inconsistent data =", - oldUser.id, - ]; - Logger.error(errorMessage.join(" ")); + updatedFirebaseUser = await firebaseAdmin + .auth() + .updateUser(oldUser.authId, { email: user.email }); + } catch (error) { + // rollback MongoDB user updates + try { + await MgUser.findByIdAndUpdate( + userId, + { + firstName: oldUser.firstName, + lastName: oldUser.lastName, + role: oldUser.role, + }, + { runValidators: true }, + ); + } catch (mongoDbError: unknown) { + const errorMessage = [ + "Failed to rollback MongoDB user update after Firebase user update failure. Reason =", + getErrorMessage(mongoDbError), + "MongoDB user id with possibly inconsistent data =", + oldUser.id, + ]; + Logger.error(errorMessage.join(" ")); + } + + throw error; } - + } catch (error: unknown) { + Logger.error(`Failed to update user. Reason = ${getErrorMessage(error)}`); throw error; } - } catch (error: unknown) { - Logger.error(`Failed to update user. Reason = ${getErrorMessage(error)}`); - throw error; + + return { + id: userId, + firstName: user.firstName, + lastName: user.lastName, + email: updatedFirebaseUser.email ?? "", + role: user.role, + }; } - - return { - id: userId, - firstName: user.firstName, - lastName: user.lastName, - email: updatedFirebaseUser.email ?? "", - role: user.role, - }; - } - - async deleteUserById(userId: string): Promise { - try { - const deletedUser: User | null = await MgUser.findByIdAndDelete(userId); - - if (!deletedUser) { - throw new Error(`userId ${userId} not found.`); - } - + + async deleteUserById(userId: string): Promise { try { - await firebaseAdmin.auth().deleteUser(deletedUser.authId); - } catch (error) { - // rollback user deletion in MongoDB + const deletedUser: User | null = await MgUser.findByIdAndDelete(userId); + + if (!deletedUser) { + throw new Error(`userId ${userId} not found.`); + } + try { - await MgUser.create({ - firstName: deletedUser.firstName, - lastName: deletedUser.lastName, - authId: deletedUser.authId, - role: deletedUser.role, - }); - } catch (mongoDbError: unknown) { - const errorMessage = [ - "Failed to rollback MongoDB user deletion after Firebase user deletion failure. Reason =", - getErrorMessage(mongoDbError), - "Firebase uid with non-existent MongoDB record =", - deletedUser.authId, - ]; - Logger.error(errorMessage.join(" ")); + await firebaseAdmin.auth().deleteUser(deletedUser.authId); + } catch (error) { + // rollback user deletion in MongoDB + try { + await MgUser.create({ + firstName: deletedUser.firstName, + lastName: deletedUser.lastName, + authId: deletedUser.authId, + role: deletedUser.role, + }); + } catch (mongoDbError: unknown) { + const errorMessage = [ + "Failed to rollback MongoDB user deletion after Firebase user deletion failure. Reason =", + getErrorMessage(mongoDbError), + "Firebase uid with non-existent MongoDB record =", + deletedUser.authId, + ]; + Logger.error(errorMessage.join(" ")); + } + + throw error; } - + } catch (error: unknown) { + Logger.error(`Failed to delete user. Reason = ${getErrorMessage(error)}`); throw error; } - } catch (error: unknown) { - Logger.error(`Failed to delete user. Reason = ${getErrorMessage(error)}`); - throw error; } - } - - async deleteUserByEmail(email: string): Promise { - try { - const firebaseUser: firebaseAdmin.auth.UserRecord = await firebaseAdmin - .auth() - .getUserByEmail(email); - const deletedUser: User | null = await MgUser.findOneAndDelete({ - authId: firebaseUser.uid, - }); - - if (!deletedUser) { - throw new Error(`authId (Firebase uid) ${firebaseUser.uid} not found.`); - } - + + async deleteUserByEmail(email: string): Promise { try { - await firebaseAdmin.auth().deleteUser(firebaseUser.uid); - } catch (error) { + const firebaseUser: firebaseAdmin.auth.UserRecord = await firebaseAdmin + .auth() + .getUserByEmail(email); + const deletedUser: User | null = await MgUser.findOneAndDelete({ + authId: firebaseUser.uid, + }); + + if (!deletedUser) { + throw new Error(`authId (Firebase uid) ${firebaseUser.uid} not found.`); + } + try { - // rollback user deletion in MongoDB - await MgUser.create({ - firstName: deletedUser.firstName, - lastName: deletedUser.lastName, - authId: deletedUser.authId, - role: deletedUser.role, - }); - } catch (mongoDbError: unknown) { - const errorMessage = [ - "Failed to rollback MongoDB user deletion after Firebase user deletion failure. Reason =", - getErrorMessage(mongoDbError), - "Firebase uid with non-existent MongoDB record =", - deletedUser.authId, - ]; - Logger.error(errorMessage.join(" ")); + await firebaseAdmin.auth().deleteUser(firebaseUser.uid); + } catch (error) { + try { + // rollback user deletion in MongoDB + await MgUser.create({ + firstName: deletedUser.firstName, + lastName: deletedUser.lastName, + authId: deletedUser.authId, + role: deletedUser.role, + }); + } catch (mongoDbError: unknown) { + const errorMessage = [ + "Failed to rollback MongoDB user deletion after Firebase user deletion failure. Reason =", + getErrorMessage(mongoDbError), + "Firebase uid with non-existent MongoDB record =", + deletedUser.authId, + ]; + Logger.error(errorMessage.join(" ")); + } + + throw error; } - + } catch (error: unknown) { + Logger.error(`Failed to delete user. Reason = ${getErrorMessage(error)}`); throw error; } - } catch (error: unknown) { - Logger.error(`Failed to delete user. Reason = ${getErrorMessage(error)}`); - throw error; } } -} - -export default UserService; - -// } mongodb + + export default UserService; + + // } mongodb // postgresql { import * as firebaseAdmin from "firebase-admin"; +import { PrismaClient } from "@prisma/client"; import IUserService from "../interfaces/userService"; import { CreateUserDTO, Role, UpdateUserDTO, UserDTO } from "../../types"; import { getErrorMessage } from "../../utilities/errorUtils"; import logger from "../../utilities/logger"; -import User from "../../models/user.model"; const Logger = logger(__filename); +const prisma = new PrismaClient(); class UserService implements IUserService { - /* eslint-disable class-methods-use-this */ - async getUserById(userId: string): Promise { - let user: User | null; + let user; let firebaseUser: firebaseAdmin.auth.UserRecord; try { - user = await User.findByPk(Number(userId)); + user = await prisma.users.findUnique({ + where: { id: Number(userId) }, + }); if (!user) { throw new Error(`userId ${userId} not found.`); } + firebaseUser = await firebaseAdmin.auth().getUser(user.auth_id); } catch (error: unknown) { Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); @@ -378,17 +380,17 @@ class UserService implements IUserService { } async getUserByEmail(email: string): Promise { - let user: User | null; + let user; let firebaseUser: firebaseAdmin.auth.UserRecord; try { firebaseUser = await firebaseAdmin.auth().getUserByEmail(email); - user = await User.findOne({ + user = await prisma.users.findUnique({ where: { auth_id: firebaseUser.uid }, }); if (!user) { - throw new Error(`userId with authID ${firebaseUser.uid} not found.`); + throw new Error(`User with authId ${firebaseUser.uid} not found.`); } } catch (error: unknown) { Logger.error(`Failed to get user. Reason = ${getErrorMessage(error)}`); @@ -406,11 +408,12 @@ class UserService implements IUserService { async getUserRoleByAuthId(authId: string): Promise { try { - const user: User | null = await User.findOne({ + const user = await prisma.users.findUnique({ where: { auth_id: authId }, }); + if (!user) { - throw new Error(`userId with authId ${authId} not found.`); + throw new Error(`User with authId ${authId} not found.`); } return user.role; } catch (error: unknown) { @@ -423,11 +426,12 @@ class UserService implements IUserService { async getUserIdByAuthId(authId: string): Promise { try { - const user: User | null = await User.findOne({ + const user = await prisma.users.findUnique({ where: { auth_id: authId }, }); + if (!user) { - throw new Error(`user with authId ${authId} not found.`); + throw new Error(`User with authId ${authId} not found.`); } return String(user.id); } catch (error: unknown) { @@ -438,9 +442,12 @@ class UserService implements IUserService { async getAuthIdById(userId: string): Promise { try { - const user: User | null = await User.findByPk(Number(userId)); + const user = await prisma.users.findUnique({ + where: { id: Number(userId) }, + }); + if (!user) { - throw new Error(`userId ${userId} not found.`); + throw new Error(`User ID ${userId} not found.`); } return user.auth_id; } catch (error: unknown) { @@ -451,8 +458,9 @@ class UserService implements IUserService { async getUsers(): Promise> { let userDtos: Array = []; + try { - const users: Array = await User.findAll(); + const users = await prisma.users.findMany(); userDtos = await Promise.all( users.map(async (user) => { @@ -462,7 +470,7 @@ class UserService implements IUserService { firebaseUser = await firebaseAdmin.auth().getUser(user.auth_id); } catch (error) { Logger.error( - `user with authId ${user.auth_id} could not be fetched from Firebase`, + `User with authId ${user.auth_id} could not be fetched from Firebase`, ); throw error; } @@ -486,167 +494,85 @@ class UserService implements IUserService { async createUser( user: CreateUserDTO, - authId?: string, + authId: string, signUpMethod = "PASSWORD", ): Promise { - let newUser: User; let firebaseUser: firebaseAdmin.auth.UserRecord; try { if (signUpMethod === "GOOGLE") { - /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ - firebaseUser = await firebaseAdmin.auth().getUser(authId!); + firebaseUser = await firebaseAdmin.auth().getUser(authId); } else { - // signUpMethod === PASSWORD firebaseUser = await firebaseAdmin.auth().createUser({ email: user.email, password: user.password, }); } - try { - newUser = await User.create({ + const newUser = await prisma.users.create({ + data: { first_name: user.firstName, last_name: user.lastName, auth_id: firebaseUser.uid, role: user.role, - }); - } catch (postgresError) { - try { - await firebaseAdmin.auth().deleteUser(firebaseUser.uid); - } catch (firebaseError: unknown) { - const errorMessage = [ - "Failed to rollback Firebase user creation after Postgres user creation failure. Reason =", - getErrorMessage(firebaseError), - "Orphaned authId (Firebase uid) =", - firebaseUser.uid, - ]; - Logger.error(errorMessage.join(" ")); - } + }, + }); - throw postgresError; - } + return { + id: String(newUser.id), + firstName: newUser.first_name, + lastName: newUser.last_name, + email: firebaseUser.email ?? "", + role: newUser.role, + }; } catch (error: unknown) { Logger.error(`Failed to create user. Reason = ${getErrorMessage(error)}`); throw error; } - - return { - id: String(newUser.id), - firstName: newUser.first_name, - lastName: newUser.last_name, - email: firebaseUser.email ?? "", - role: newUser.role, - }; } async updateUserById(userId: string, user: UpdateUserDTO): Promise { - let updatedFirebaseUser: firebaseAdmin.auth.UserRecord; - try { - const updateResult = await User.update( - { + const updateResult = await prisma.users.update({ + where: { id: Number(userId) }, + data: { first_name: user.firstName, last_name: user.lastName, role: user.role, }, - { - where: { id: userId }, - returning: true, - }, - ); - - // check number of rows affected - if (updateResult[0] < 1) { - throw new Error(`userId ${userId} not found.`); - } - - // the cast to "any" is needed due to a missing property in the Sequelize type definitions - // https://github.com/sequelize/sequelize/issues/9978#issuecomment-426342219 - /* eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any */ - const oldUser: User = (updateResult[1][0] as any)._previousDataValues; + }); - try { - updatedFirebaseUser = await firebaseAdmin - .auth() - .updateUser(oldUser.auth_id, { email: user.email }); - } catch (error) { - // rollback Postgres user updates - try { - await User.update( - { - first_name: oldUser.first_name, - last_name: oldUser.last_name, - role: oldUser.role, - }, - { - where: { id: userId }, - }, - ); - } catch (postgresError: unknown) { - const errorMessage = [ - "Failed to rollback Postgres user update after Firebase user update failure. Reason =", - getErrorMessage(postgresError), - "Postgres user id with possibly inconsistent data =", - oldUser.id, - ]; - Logger.error(errorMessage.join(" ")); - } + let firebaseUser = await firebaseAdmin + .auth() + .updateUser(updateResult.auth_id, { + email: user.email, + }); - throw error; - } + return { + id: String(updateResult.id), + firstName: updateResult.first_name, + lastName: updateResult.last_name, + email: firebaseUser.email ?? "", + role: updateResult.role, + }; } catch (error: unknown) { Logger.error(`Failed to update user. Reason = ${getErrorMessage(error)}`); throw error; } - - return { - id: userId, - firstName: user.firstName, - lastName: user.lastName, - email: updatedFirebaseUser.email ?? "", - role: user.role, - }; } async deleteUserById(userId: string): Promise { try { - // Sequelize doesn't provide a way to atomically find, delete, and return deleted row - const deletedUser: User | null = await User.findByPk(Number(userId)); - - if (!deletedUser) { - throw new Error(`userid ${userId} not found.`); - } - - const numDestroyed: number = await User.destroy({ - where: { id: userId }, + const user = await prisma.users.delete({ + where: { id: Number(userId) }, }); - if (numDestroyed <= 0) { - throw new Error(`userid ${userId} was not deleted in Postgres.`); - } - try { - await firebaseAdmin.auth().deleteUser(deletedUser.auth_id); + await firebaseAdmin.auth().deleteUser(user.auth_id); } catch (error) { - // rollback user deletion in Postgres - try { - await User.create({ - first_name: deletedUser.first_name, - last_name: deletedUser.last_name, - auth_id: deletedUser.auth_id, - role: deletedUser.role, - }); - } catch (postgresError: unknown) { - const errorMessage = [ - "Failed to rollback Postgres user deletion after Firebase user deletion failure. Reason =", - getErrorMessage(postgresError), - "Firebase uid with non-existent Postgres record =", - deletedUser.auth_id, - ]; - Logger.error(errorMessage.join(" ")); - } - + Logger.error( + `Failed to delete Firebase user after deleting user in database. AuthId: ${user.auth_id}`, + ); throw error; } } catch (error: unknown) { @@ -657,57 +583,27 @@ class UserService implements IUserService { async deleteUserByEmail(email: string): Promise { try { - const firebaseUser: firebaseAdmin.auth.UserRecord = await firebaseAdmin - .auth() - .getUserByEmail(email); - const deletedUser: User | null = await User.findOne({ + const firebaseUser = await firebaseAdmin.auth().getUserByEmail(email); + const user = await prisma.users.delete({ where: { auth_id: firebaseUser.uid }, }); - if (!deletedUser) { - throw new Error(`userid ${firebaseUser.uid} not found.`); - } - - const numDestroyed: number = await User.destroy({ - where: { auth_id: firebaseUser.uid }, - }); - - if (numDestroyed <= 0) { - throw new Error( - `userid ${firebaseUser.uid} was not deleted in Postgres.`, - ); - } - try { - await firebaseAdmin.auth().deleteUser(deletedUser.auth_id); + await firebaseAdmin.auth().deleteUser(user.auth_id); } catch (error) { - // rollback user deletion in Postgres - try { - await User.create({ - first_name: deletedUser.first_name, - last_name: deletedUser.last_name, - auth_id: deletedUser.auth_id, - role: deletedUser.role, - }); - } catch (postgresError: unknown) { - const errorMessage = [ - "Failed to rollback Postgres user deletion after Firebase user deletion failure. Reason =", - getErrorMessage(postgresError), - "Firebase uid with non-existent Postgres record =", - deletedUser.auth_id, - ]; - Logger.error(errorMessage.join(" ")); - } - + Logger.error( + `Failed to delete Firebase user after deleting user in database. AuthId: ${user.auth_id}`, + ); throw error; } } catch (error: unknown) { - Logger.error(`Failed to delete user. Reason = ${getErrorMessage(error)}`); + Logger.error( + `Failed to delete user by email. Reason = ${getErrorMessage(error)}`, + ); throw error; } } } export default UserService; - // } postgresql \ No newline at end of file diff --git a/backend/typescript/services/interfaces/userService.ts b/backend/typescript/services/interfaces/userService.ts index 7b00d01..a1beb12 100644 --- a/backend/typescript/services/interfaces/userService.ts +++ b/backend/typescript/services/interfaces/userService.ts @@ -8,59 +8,59 @@ import { interface IUserService { /** - * Get user associated with id - * @param id user's id - * @returns a UserDTO with user's information - * @throws Error if user retrieval fails + * Retrieves a user by their ID. + * @param userId The ID of the user to retrieve. + * @returns A UserDTO containing the user's information or null if no user is found. + * @throws Error if the retrieval process fails. */ getUserById(userId: string): Promise; /** - * Get user associated with email - * @param email user's email - * @returns a UserDTO with user's information - * @throws Error if user retrieval fails + * Retrieves a user by their email address. + * @param email The email address of the user to retrieve. + * @returns A UserDTO containing the user's information or null if no user is found. + * @throws Error if the retrieval process fails. */ getUserByEmail(email: string): Promise; /** - * Get role of user associated with authId - * @param authId user's authId - * @returns role of the user - * @throws Error if user role retrieval fails + * Retrieves the role of a user by their authentication ID. + * @param authId The authentication ID associated with the user. + * @returns The role of the user or null if the user cannot be found. + * @throws Error if the retrieval process fails. */ getUserRoleByAuthId(authId: string): Promise; /** - * Get id of user associated with authId - * @param authId user's authId - * @returns id of the user - * @throws Error if user id retrieval fails + * Retrieves the ID of a user by their authentication ID. + * @param authId The authentication ID associated with the user. + * @returns The user's ID or null if the user cannot be found. + * @throws Error if the retrieval process fails. */ getUserIdByAuthId(authId: string): Promise; /** - * Get authId of user associated with id - * @param userId user's id - * @returns user's authId - * @throws Error if user authId retrieval fails + * Retrieves the authentication ID associated with a user ID. + * @param userId The user's ID to lookup the authentication ID. + * @returns The authentication ID or null if the user cannot be found. + * @throws Error if the retrieval process fails. */ getAuthIdById(userId: string): Promise; /** - * Get all user information (possibly paginated in the future) - * @returns array of UserDTOs - * @throws Error if user retrieval fails + * Retrieves all users in the system. + * @returns An array of UserDTOs, which could be empty if no users are found. + * @throws Error if the retrieval process fails. */ getUsers(): Promise>; /** - * Create a user, email verification configurable - * @param user the user to be created - * @param authId the user's firebase auth id, optional - * @param signUpMethod the method user used to signup - * @returns a UserDTO with the created user's information - * @throws Error if user creation fails + * Creates a user with the specified details. + * @param user The details of the user to create. + * @param authId Optional; the user's Firebase authentication ID. + * @param signUpMethod Optional; the method the user used to sign up. + * @returns A UserDTO containing the created user's information. + * @throws Error if the user creation process fails. */ createUser( user: CreateUserDTO, @@ -69,26 +69,25 @@ interface IUserService { ): Promise; /** - * Update a user. - * Note: the password cannot be updated using this method, use IAuthService.resetPassword instead - * @param userId user's id - * @param user the user to be updated - * @returns a UserDTO with the updated user's information - * @throws Error if user update fails + * Updates the information of a user by their ID. + * @param userId The ID of the user to update. + * @param user The new details to update the user with. + * @returns A UserDTO containing the updated user's information. + * @throws Error if the update process fails. */ updateUserById(userId: string, user: UpdateUserDTO): Promise; /** - * Delete a user by id - * @param userId user's userId - * @throws Error if user deletion fails + * Deletes a user by their user ID. + * @param userId The ID of the user to delete. + * @throws Error if the deletion process fails. */ deleteUserById(userId: string): Promise; /** - * Delete a user by email - * @param email user's email - * @throws Error if user deletion fails + * Deletes a user by their email address. + * @param email The email of the user to delete. + * @throws Error if the deletion process fails. */ deleteUserByEmail(email: string): Promise; } From 549cf55227ec4bb0ca1c1b858cad2b1847ade088 Mon Sep 17 00:00:00 2001 From: RohanNankani <59024335+RohanNankani@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:22:17 -0400 Subject: [PATCH 5/7] migrate simple entity service to prisma (#195) --- backend/typescript/Dockerfile | 2 + .../implementations/simpleEntityService.ts | 70 ++++++++----------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/backend/typescript/Dockerfile b/backend/typescript/Dockerfile index 6b86116..7073a78 100644 --- a/backend/typescript/Dockerfile +++ b/backend/typescript/Dockerfile @@ -13,5 +13,7 @@ RUN yarn install COPY . ./ +RUN npx prisma generate --schema=models/prisma/schema.prisma + EXPOSE 8080 ENTRYPOINT ["yarn", "dev"] diff --git a/backend/typescript/services/implementations/simpleEntityService.ts b/backend/typescript/services/implementations/simpleEntityService.ts index da18bb9..b6d7367 100644 --- a/backend/typescript/services/implementations/simpleEntityService.ts +++ b/backend/typescript/services/implementations/simpleEntityService.ts @@ -126,7 +126,10 @@ export default SimpleEntityService; // } mongodb // postgresql { -import PgSimpleEntity from "../../models/simpleEntity.model"; +import { PrismaClient, simple_entities, enum_entities_enum_field } from '@prisma/client'; + +const prisma = new PrismaClient(); + import { ISimpleEntityService, SimpleEntityRequestDTO, @@ -140,9 +143,9 @@ const Logger = logger(__filename); class SimpleEntityService implements ISimpleEntityService { /* eslint-disable class-methods-use-this */ async getEntity(id: string): Promise { - let entity: PgSimpleEntity | null; + let entity: simple_entities | null; try { - entity = await PgSimpleEntity.findByPk(id, { raw: true }); + entity = await prisma.simple_entities.findUnique({ where: { id: Number(id) } }); if (!entity) { throw new Error(`Entity id ${id} not found`); } @@ -155,7 +158,7 @@ class SimpleEntityService implements ISimpleEntityService { id: String(entity.id), stringField: entity.string_field, intField: entity.int_field, - enumField: entity.enum_field, + enumField: entity.enum_field as enum_entities_enum_field, stringArrayField: entity.string_array_field, boolField: entity.bool_field, }; @@ -163,21 +166,17 @@ class SimpleEntityService implements ISimpleEntityService { async getEntities(): Promise { try { - const entities: Array = await PgSimpleEntity.findAll({ - raw: true, - }); + const entities: Array = await prisma.simple_entities.findMany(); return entities.map((entity) => ({ id: String(entity.id), stringField: entity.string_field, intField: entity.int_field, - enumField: entity.enum_field, + enumField: entity.enum_field as enum_entities_enum_field, stringArrayField: entity.string_array_field, boolField: entity.bool_field, })); } catch (error: unknown) { - Logger.error( - `Failed to get entities. Reason = ${getErrorMessage(error)}`, - ); + Logger.error(`Failed to get entities. Reason = ${getErrorMessage(error)}`); throw error; } } @@ -185,26 +184,26 @@ class SimpleEntityService implements ISimpleEntityService { async createEntity( entity: SimpleEntityRequestDTO, ): Promise { - let newEntity: PgSimpleEntity | null; + let newEntity: simple_entities | null; try { - newEntity = await PgSimpleEntity.create({ - string_field: entity.stringField, - int_field: entity.intField, - enum_field: entity.enumField, - string_array_field: entity.stringArrayField, - bool_field: entity.boolField, + newEntity = await prisma.simple_entities.create({ + data: { + string_field: entity.stringField, + int_field: entity.intField, + enum_field: entity.enumField as enum_entities_enum_field, + string_array_field: entity.stringArrayField, + bool_field: entity.boolField, + }, }); } catch (error: unknown) { - Logger.error( - `Failed to create entity. Reason = ${getErrorMessage(error)}`, - ); + Logger.error(`Failed to create entity. Reason = ${getErrorMessage(error)}`); throw error; } return { id: String(newEntity.id), stringField: newEntity.string_field, intField: newEntity.int_field, - enumField: newEntity.enum_field, + enumField: newEntity.enum_field as enum_entities_enum_field, stringArrayField: newEntity.string_array_field, boolField: newEntity.bool_field, }; @@ -214,24 +213,18 @@ class SimpleEntityService implements ISimpleEntityService { id: string, entity: SimpleEntityRequestDTO, ): Promise { - let resultingEntity: PgSimpleEntity | null; - let updateResult: [number, PgSimpleEntity[]] | null; + let resultingEntity: simple_entities | null; try { - updateResult = await PgSimpleEntity.update( - { + resultingEntity = await prisma.simple_entities.update( + { where: { id: Number(id) }, + data: { string_field: entity.stringField, int_field: entity.intField, - enum_field: entity.enumField, + enum_field: entity.enumField as enum_entities_enum_field, string_array_field: entity.stringArrayField, bool_field: entity.boolField, }, - { where: { id }, returning: true }, - ); - - if (!updateResult[0]) { - throw new Error(`Entity id ${id} not found`); - } - [, [resultingEntity]] = updateResult; + }); } catch (error: unknown) { Logger.error( `Failed to update entity. Reason = ${getErrorMessage(error)}`, @@ -242,7 +235,7 @@ class SimpleEntityService implements ISimpleEntityService { id: String(resultingEntity.id), stringField: resultingEntity.string_field, intField: resultingEntity.int_field, - enumField: resultingEntity.enum_field, + enumField: resultingEntity.enum_field as enum_entities_enum_field, stringArrayField: resultingEntity.string_array_field, boolField: resultingEntity.bool_field, }; @@ -250,12 +243,9 @@ class SimpleEntityService implements ISimpleEntityService { async deleteEntity(id: string): Promise { try { - const deleteResult: number | null = await PgSimpleEntity.destroy({ - where: { id }, + await prisma.simple_entities.delete({ + where: { id: Number(id) } }); - if (!deleteResult) { - throw new Error(`Entity id ${id} not found`); - } return id; } catch (error: unknown) { Logger.error( From fb2ef266e892df16cf5d62bf584fe80c7aa8bd41 Mon Sep 17 00:00:00 2001 From: RohanNankani <59024335+RohanNankani@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:22:41 -0400 Subject: [PATCH 6/7] migrate entity service to prisma (#194) --- .../services/implementations/entityService.ts | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/backend/typescript/services/implementations/entityService.ts b/backend/typescript/services/implementations/entityService.ts index bc02653..190398a 100644 --- a/backend/typescript/services/implementations/entityService.ts +++ b/backend/typescript/services/implementations/entityService.ts @@ -173,7 +173,10 @@ export default EntityService; // postgresql { import { v4 as uuidv4 } from "uuid"; -import PgEntity from "../../models/entity.model"; +import { PrismaClient, entities, enum_entities_enum_field} from '@prisma/client'; + +const prisma = new PrismaClient(); + import { IEntityService, EntityRequestDTO, @@ -182,6 +185,7 @@ import { import IFileStorageService from "../interfaces/fileStorageService"; import { getErrorMessage } from "../../utilities/errorUtils"; import logger from "../../utilities/logger"; +import { EnumType } from "typescript"; const Logger = logger(__filename); @@ -194,9 +198,9 @@ class EntityService implements IEntityService { /* eslint-disable class-methods-use-this */ async getEntity(id: string): Promise { - let entity: PgEntity | null; + let entity: entities | null; try { - entity = await PgEntity.findByPk(id, { raw: true }); + entity = await prisma.entities.findUnique({ where: { id: Number(id) } }); if (!entity) { throw new Error(`Entity id ${id} not found`); } @@ -209,7 +213,7 @@ class EntityService implements IEntityService { id: String(entity.id), stringField: entity.string_field, intField: entity.int_field, - enumField: entity.enum_field, + enumField: entity.enum_field as enum_entities_enum_field, stringArrayField: entity.string_array_field, boolField: entity.bool_field, fileName: entity.file_name, @@ -218,12 +222,12 @@ class EntityService implements IEntityService { async getEntities(): Promise { try { - const entities: Array = await PgEntity.findAll({ raw: true }); + const entities: Array = await prisma.entities.findMany(); return entities.map((entity) => ({ id: String(entity.id), stringField: entity.string_field, intField: entity.int_field, - enumField: entity.enum_field, + enumField: entity.enum_field as enum_entities_enum_field, stringArrayField: entity.string_array_field, boolField: entity.bool_field, fileName: entity.file_name, @@ -237,7 +241,7 @@ class EntityService implements IEntityService { } async createEntity(entity: EntityRequestDTO): Promise { - let newEntity: PgEntity | null; + let newEntity: entities | null; const fileName = entity.filePath ? uuidv4() : ""; try { if (entity.filePath) { @@ -247,13 +251,15 @@ class EntityService implements IEntityService { entity.fileContentType, ); } - newEntity = await PgEntity.create({ - string_field: entity.stringField, - int_field: entity.intField, - enum_field: entity.enumField, - string_array_field: entity.stringArrayField, - bool_field: entity.boolField, - file_name: fileName, + newEntity = await prisma.entities.create({ + data: { + string_field: entity.stringField, + int_field: entity.intField, + enum_field: entity.enumField as enum_entities_enum_field, + string_array_field: entity.stringArrayField, + bool_field: entity.boolField, + file_name: fileName, + }, }); } catch (error: unknown) { Logger.error( @@ -265,7 +271,7 @@ class EntityService implements IEntityService { id: String(newEntity.id), stringField: newEntity.string_field, intField: newEntity.int_field, - enumField: newEntity.enum_field, + enumField: newEntity.enum_field as enum_entities_enum_field, stringArrayField: newEntity.string_array_field, boolField: newEntity.bool_field, fileName, @@ -276,14 +282,15 @@ class EntityService implements IEntityService { id: string, entity: EntityRequestDTO, ): Promise { - let resultingEntity: PgEntity | null; - let updateResult: [number, PgEntity[]] | null; + let resultingEntity: entities | null; let fileName = ""; try { - const currentEntity = await PgEntity.findByPk(id, { - raw: true, - attributes: ["file_name"], - }); + const currentEntity = await prisma.entities.findUnique({ + where: { id: Number(id) }, + select: { + file_name: true, + } + }) as { file_name: string } | null ; const currentFileName = currentEntity?.file_name; if (entity.filePath) { fileName = currentFileName || uuidv4(); @@ -303,33 +310,32 @@ class EntityService implements IEntityService { } else if (currentFileName) { await this.storageService.deleteFile(currentFileName); } - updateResult = await PgEntity.update( + resultingEntity = await prisma.entities.update({ + where: { id: Number(id) }, + data : { string_field: entity.stringField, int_field: entity.intField, - enum_field: entity.enumField, + enum_field: entity.enumField as enum_entities_enum_field, string_array_field: entity.stringArrayField, bool_field: entity.boolField, file_name: fileName, }, - { where: { id }, returning: true }, - ); - - if (!updateResult[0]) { - throw new Error(`Entity id ${id} not found`); - } - [, [resultingEntity]] = updateResult; + }); } catch (error: unknown) { Logger.error( `Failed to update entity. Reason = ${getErrorMessage(error)}`, ); throw error; } + if (!resultingEntity) { + throw new Error(`Failed to update entity with id ${id}`); + } return { id: String(resultingEntity.id), stringField: resultingEntity.string_field, intField: resultingEntity.int_field, - enumField: resultingEntity.enum_field, + enumField: resultingEntity.enum_field as enum_entities_enum_field, stringArrayField: resultingEntity.string_array_field, boolField: resultingEntity.bool_field, fileName, @@ -338,12 +344,10 @@ class EntityService implements IEntityService { async deleteEntity(id: string): Promise { try { - const entityToDelete = await PgEntity.findByPk(id, { raw: true }); - const deleteResult: number | null = await PgEntity.destroy({ - where: { id }, - }); + const entityToDelete = await prisma.entities.findUnique({ where: { id: Number(id) } }); + await prisma.entities.delete({ where: { id: Number(id) } }); - if (!entityToDelete || !deleteResult) { + if (!entityToDelete) { throw new Error(`Entity id ${id} not found`); } if (entityToDelete.file_name) { @@ -360,5 +364,4 @@ class EntityService implements IEntityService { } export default EntityService; - // } postgresql \ No newline at end of file From 4368df3270a04ca778bbff0b9535f84f63745c47 Mon Sep 17 00:00:00 2001 From: Omer Adeel Date: Sat, 10 Aug 2024 22:39:00 -0400 Subject: [PATCH 7/7] removed sequilize --- ...2022.01.10T17.43.07.create-entity-table.ts | 46 ------------------- .../2022.01.10T17.43.41.create-user-table.ts | 38 --------------- backend/typescript/models/entity.model.ts | 27 ----------- backend/typescript/models/index.ts | 16 ------- .../typescript/models/simpleEntity.model.ts | 26 +---------- backend/typescript/models/user.model.ts | 22 +-------- backend/typescript/package.json | 2 - .../__tests__/userService.test.ts | 15 +++--- backend/typescript/testUtils/testDb.ts | 22 +-------- backend/typescript/umzug.ts | 27 ----------- 10 files changed, 10 insertions(+), 231 deletions(-) delete mode 100644 backend/typescript/migrations/2022.01.10T17.43.07.create-entity-table.ts delete mode 100644 backend/typescript/migrations/2022.01.10T17.43.41.create-user-table.ts delete mode 100644 backend/typescript/umzug.ts diff --git a/backend/typescript/migrations/2022.01.10T17.43.07.create-entity-table.ts b/backend/typescript/migrations/2022.01.10T17.43.07.create-entity-table.ts deleted file mode 100644 index 7f4d37f..0000000 --- a/backend/typescript/migrations/2022.01.10T17.43.07.create-entity-table.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { DataType } from "sequelize-typescript"; - -import { Migration } from "../umzug"; - -const TABLE_NAME = "entities"; - -export const up: Migration = async ({ context: sequelize }) => { - await sequelize.getQueryInterface().createTable(TABLE_NAME, { - id: { - type: DataType.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true, - }, - string_field: { - type: DataType.STRING, - allowNull: false, - }, - int_field: { - type: DataType.INTEGER, - allowNull: false, - }, - enum_field: { - type: DataType.ENUM("A", "B", "C", "D"), - allowNull: false, - }, - string_array_field: { - type: DataType.ARRAY(DataType.STRING), - allowNull: false, - }, - bool_field: { - type: DataType.BOOLEAN, - allowNull: false, - }, - file_name: { - type: DataType.STRING, - allowNull: false, - }, - createdAt: DataType.DATE, - updatedAt: DataType.DATE, - }); -}; - -export const down: Migration = async ({ context: sequelize }) => { - await sequelize.getQueryInterface().dropTable(TABLE_NAME); -}; diff --git a/backend/typescript/migrations/2022.01.10T17.43.41.create-user-table.ts b/backend/typescript/migrations/2022.01.10T17.43.41.create-user-table.ts deleted file mode 100644 index 4e7e8d0..0000000 --- a/backend/typescript/migrations/2022.01.10T17.43.41.create-user-table.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { DataType } from "sequelize-typescript"; - -import { Migration } from "../umzug"; - -const TABLE_NAME = "users"; - -export const up: Migration = async ({ context: sequelize }) => { - await sequelize.getQueryInterface().createTable(TABLE_NAME, { - id: { - type: DataType.INTEGER, - allowNull: false, - primaryKey: true, - autoIncrement: true, - }, - first_name: { - type: DataType.STRING, - allowNull: false, - }, - last_name: { - type: DataType.STRING, - allowNull: false, - }, - auth_id: { - type: DataType.STRING, - allowNull: false, - }, - role: { - type: DataType.ENUM("User", "Admin"), - allowNull: false, - }, - createdAt: DataType.DATE, - updatedAt: DataType.DATE, - }); -}; - -export const down: Migration = async ({ context: sequelize }) => { - await sequelize.getQueryInterface().dropTable(TABLE_NAME); -}; diff --git a/backend/typescript/models/entity.model.ts b/backend/typescript/models/entity.model.ts index 3983e6c..7d13955 100644 --- a/backend/typescript/models/entity.model.ts +++ b/backend/typescript/models/entity.model.ts @@ -41,30 +41,3 @@ const EntitySchema: Schema = new Schema({ export default model("Entity", EntitySchema); // } mongodb -// postgresql { -import { Column, Model, Table, DataType } from "sequelize-typescript"; - -import { Letters } from "../types"; - -@Table({ tableName: "entities" }) -export default class Entity extends Model { - @Column - string_field!: string; - - @Column - int_field!: number; - - @Column({ type: DataType.ENUM("A", "B", "C", "D") }) - enum_field!: Letters; - - @Column({ type: DataType.ARRAY(DataType.STRING) }) - string_array_field!: string[]; - - @Column - bool_field!: boolean; - - @Column - file_name!: string; -} - -// } postgresql diff --git a/backend/typescript/models/index.ts b/backend/typescript/models/index.ts index 8420568..3fcabcf 100644 --- a/backend/typescript/models/index.ts +++ b/backend/typescript/models/index.ts @@ -1,19 +1,3 @@ -// postgresql { -import * as path from "path"; -import { Sequelize } from "sequelize-typescript"; - -const DATABASE_URL = - process.env.NODE_ENV === "production" - ? /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ - process.env.DATABASE_URL! - : `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_DEV}`; - -/* eslint-disable-next-line import/prefer-default-export */ -export const sequelize = new Sequelize(DATABASE_URL, { - models: [path.join(__dirname, "/*.model.ts")], -}); - -// } postgresql // mongodb { import mongoose from "mongoose"; diff --git a/backend/typescript/models/simpleEntity.model.ts b/backend/typescript/models/simpleEntity.model.ts index 0b45d2e..abde5b5 100644 --- a/backend/typescript/models/simpleEntity.model.ts +++ b/backend/typescript/models/simpleEntity.model.ts @@ -36,28 +36,4 @@ const SimpleEntitySchema: Schema = new Schema({ export default model("SimpleEntity", SimpleEntitySchema); -// } mongodb -// postgresql { -import { Column, Model, Table, DataType } from "sequelize-typescript"; - -import { Letters } from "../types"; - -@Table({ tableName: "simple_entities" }) -export default class SimpleEntity extends Model { - @Column - string_field!: string; - - @Column - int_field!: number; - - @Column({ type: DataType.ENUM("A", "B", "C", "D") }) - enum_field!: Letters; - - @Column({ type: DataType.ARRAY(DataType.STRING) }) - string_array_field!: string[]; - - @Column - bool_field!: boolean; -} - -// } postgresql \ No newline at end of file +// } mongodb \ No newline at end of file diff --git a/backend/typescript/models/user.model.ts b/backend/typescript/models/user.model.ts index a7f2873..3225d3a 100644 --- a/backend/typescript/models/user.model.ts +++ b/backend/typescript/models/user.model.ts @@ -33,24 +33,4 @@ const UserSchema: Schema = new Schema({ export default mongoose.model("User", UserSchema); -// } mongodb -// postgresql { -import { Column, DataType, Model, Table } from "sequelize-typescript"; -import { Role } from "../types"; - -@Table({ tableName: "users" }) -export default class User extends Model { - @Column({ type: DataType.STRING }) - first_name!: string; - - @Column({ type: DataType.STRING }) - last_name!: string; - - @Column({ type: DataType.STRING }) - auth_id!: string; - - @Column({ type: DataType.ENUM("User", "Admin") }) - role!: Role; -} - -// } postgresql \ No newline at end of file +// } mongodb \ No newline at end of file diff --git a/backend/typescript/package.json b/backend/typescript/package.json index dd275a2..6f40d79 100644 --- a/backend/typescript/package.json +++ b/backend/typescript/package.json @@ -40,8 +40,6 @@ "pg": "^8.5.1", "prisma": "5.14.0", "reflect-metadata": "^0.1.13", - "sequelize": "^6.5.0", - "sequelize-typescript": "^2.1.0", "swagger-ui-express": "^4.1.6", "ts-node": "^10.0.0", "umzug": "^3.0.0-beta.16", diff --git a/backend/typescript/services/implementations/__tests__/userService.test.ts b/backend/typescript/services/implementations/__tests__/userService.test.ts index f8beec9..dd410d8 100644 --- a/backend/typescript/services/implementations/__tests__/userService.test.ts +++ b/backend/typescript/services/implementations/__tests__/userService.test.ts @@ -1,5 +1,7 @@ // postgresql { import { snakeCase } from "lodash"; +import { PrismaClient } from "@prisma/client"; +const prisma = new PrismaClient(); // } postgresql import UserModel from "../../../models/user.model"; @@ -10,9 +12,6 @@ import { UserDTO } from "../../../types"; // mongodb { import db from "../../../testUtils/testDb"; // } mongodb -// postgresql { -import { testSql } from "../../../testUtils/testDb"; -// } postgresql const testUsers = [ { @@ -70,18 +69,18 @@ describe("mongo userService", (): void => { }); // } mongodb -// postgresql { +// postgresql { describe("pg userService", () => { let userService: UserService; beforeEach(async () => { - await testSql.sync({ force: true }); + await prisma.$connect(); + await prisma.user.deleteMany(); // Clear the User table before each test userService = new UserService(); }); afterAll(async () => { - await testSql.sync({ force: true }); - await testSql.close(); + await prisma.$disconnect(); }); it("getUsers", async () => { @@ -93,7 +92,7 @@ describe("pg userService", () => { return userSnakeCase; }); - await UserModel.bulkCreate(users); + await prisma.user.createMany({ data: users }); const res = await userService.getUsers(); diff --git a/backend/typescript/testUtils/testDb.ts b/backend/typescript/testUtils/testDb.ts index 247cb80..4453409 100644 --- a/backend/typescript/testUtils/testDb.ts +++ b/backend/typescript/testUtils/testDb.ts @@ -1,18 +1,12 @@ // mongodb { import mongoose from "mongoose"; // } mongodb -// postgresql { -import { resolve } from "path"; -// } postgresql + // mongodb { // eslint-disable-next-line import/no-extraneous-dependencies import { MongoMemoryServer } from "mongodb-memory-server"; // } mongodb -// postgresql { -import { Sequelize } from "sequelize-typescript"; -// } postgresql - // mongodb { const mongo = new MongoMemoryServer(); @@ -44,17 +38,3 @@ const mongoTest = { export default mongoTest; // } mongodb - -// postgresql { -const DATABASE_URL = - process.env.NODE_ENV === "production" - ? /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ - process.env.DATABASE_URL! - : `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_TEST}`; - -/* eslint-disable-next-line import/prefer-default-export */ -export const testSql = new Sequelize(DATABASE_URL, { - models: [resolve(__dirname, "../models/*.model.ts")], - logging: false, -}); -// } postgresql diff --git a/backend/typescript/umzug.ts b/backend/typescript/umzug.ts deleted file mode 100644 index 077322d..0000000 --- a/backend/typescript/umzug.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as path from "path"; - -import { Umzug, SequelizeStorage } from "umzug"; -import { Sequelize } from "sequelize-typescript"; - -const DATABASE_URL = - process.env.NODE_ENV === "production" - ? /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ - process.env.DATABASE_URL! - : `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.DB_HOST}:5432/${process.env.POSTGRES_DB_DEV}`; - -const sequelize = new Sequelize(DATABASE_URL, { - models: [path.join(__dirname, "/*.model.ts")], -}); - -export const migrator = new Umzug({ - migrations: { - glob: ["migrations/*.ts", { cwd: __dirname }], - }, - context: sequelize, - storage: new SequelizeStorage({ - sequelize, - }), - logger: console, -}); - -export type Migration = typeof migrator._types.migration;