From 03d8f932f952ae8141d4337268138fe2cb25ecb9 Mon Sep 17 00:00:00 2001 From: Austin Turner Date: Sat, 19 Oct 2024 09:30:47 -0600 Subject: [PATCH] Add proper relationship from org to user In preparation for removing userId from user to avoid duplicate users, we are ensuring that the lookup from orgs to users is using a proper id --- apps/api/src/app/db/salesforce-org.db.ts | 10 ++++-- apps/api/src/app/db/user.db.ts | 2 ++ apps/api/src/main.ts | 31 ++++++++++++++++++- .../migration.sql | 13 ++++++++ prisma/schema.prisma | 28 ++++++++++------- 5 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 prisma/migrations/20241016225558_add_user_to_sfdc_org/migration.sql diff --git a/apps/api/src/app/db/salesforce-org.db.ts b/apps/api/src/app/db/salesforce-org.db.ts index 0f5279e5..bfe2a147 100644 --- a/apps/api/src/app/db/salesforce-org.db.ts +++ b/apps/api/src/app/db/salesforce-org.db.ts @@ -5,6 +5,7 @@ import { Maybe, SalesforceOrgUi } from '@jetstream/types'; import { Prisma, SalesforceOrg } from '@prisma/client'; import { parseISO } from 'date-fns/parseISO'; import isUndefined from 'lodash/isUndefined'; +import { findIdByUserId } from './user.db'; const SELECT = Prisma.validator()({ jetstreamOrganizationId: true, @@ -50,8 +51,9 @@ const findUniqueOrg = ({ jetstreamUserId, uniqueId }: { jetstreamUserId: string; }); }; -const findUsersOrgs = ({ jetstreamUserId }: { jetstreamUserId: string }) => { +const findUsersOrgs = ({ jetstreamUserId, actualUserId }: { jetstreamUserId: string; actualUserId: string }) => { return Prisma.validator()({ + jetstreamUserId2: actualUserId, jetstreamUserId, jetstreamUrl: ENV.JETSTREAM_SERVER_URL, }); @@ -103,13 +105,15 @@ export async function findByUniqueId(jetstreamUserId: string, uniqueId: string) } export async function findByUserId(jetstreamUserId: string) { + const actualUserId = await findIdByUserId({ userId: jetstreamUserId }); return await prisma.salesforceOrg.findMany({ select: SELECT, - where: findUsersOrgs({ jetstreamUserId }), + where: findUsersOrgs({ jetstreamUserId, actualUserId }), }); } export async function createOrUpdateSalesforceOrg(jetstreamUserId: string, salesforceOrgUi: Partial) { + const actualUserId = await findIdByUserId({ userId: jetstreamUserId }); const existingOrg = await prisma.salesforceOrg.findUnique({ where: findUniqueOrg({ jetstreamUserId, uniqueId: salesforceOrgUi.uniqueId! }), }); @@ -126,6 +130,7 @@ export async function createOrUpdateSalesforceOrg(jetstreamUserId: string, sales orgToDelete = await prisma.salesforceOrg.findFirst({ select: { id: true }, where: { + jetstreamUserId2: { equals: actualUserId }, jetstreamUserId: { equals: jetstreamUserId }, jetstreamUrl: { equals: ENV.JETSTREAM_SERVER_URL! }, username: { equals: salesforceOrgUi.username }, @@ -178,6 +183,7 @@ export async function createOrUpdateSalesforceOrg(jetstreamUserId: string, sales const org = await prisma.salesforceOrg.create({ select: SELECT, data: { + jetstreamUserId2: actualUserId, jetstreamUserId, jetstreamUrl: ENV.JETSTREAM_SERVER_URL, jetstreamOrganizationId: salesforceOrgUi.jetstreamOrganizationId, diff --git a/apps/api/src/app/db/user.db.ts b/apps/api/src/app/db/user.db.ts index 0552c1a8..ddbddbbd 100644 --- a/apps/api/src/app/db/user.db.ts +++ b/apps/api/src/app/db/user.db.ts @@ -77,6 +77,7 @@ export async function createOrUpdateUser(user: UserProfileServer): Promise<{ cre data: { appMetadata: JSON.stringify(user._json[ENV.AUTH_AUDIENCE!]), deletedAt: null, + lastLoggedIn: new Date(), preferences: { upsert: { create: { skipFrontdoorLogin: false }, @@ -98,6 +99,7 @@ export async function createOrUpdateUser(user: UserProfileServer): Promise<{ cre picture: user._json.picture, appMetadata: JSON.stringify(user._json[ENV.AUTH_AUDIENCE!]), deletedAt: null, + lastLoggedIn: new Date(), preferences: { create: { skipFrontdoorLogin: false } }, }, select: userSelect, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index dfe50548..0cd95271 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,5 +1,5 @@ import '@jetstream/api-config'; // this gets imported first to ensure as some items require early initialization -import { ENV, getExceptionLog, httpLogger, logger, pgPool } from '@jetstream/api-config'; +import { ENV, getExceptionLog, httpLogger, logger, pgPool, prisma } from '@jetstream/api-config'; import { HTTP, SESSION_EXP_DAYS } from '@jetstream/shared/constants'; import { Maybe } from '@jetstream/types'; import { json, raw, urlencoded } from 'body-parser'; @@ -364,3 +364,32 @@ if (ENV.NODE_ENV === 'production' && cluster.isPrimary) { logger.error(getExceptionLog(error), '[SERVER][ERROR]'); }); } + +if (ENV.EXAMPLE_USER_OVERRIDE && ENV.EXAMPLE_USER && (ENV.ENVIRONMENT !== 'production' || ENV.IS_CI)) { + const id = 'AAAAAAAA-0000-0000-0000-AAAAAAAAAAAA'; + logger.info('Upserting example user. id: %s', id); + prisma.user + .upsert({ + create: { + id, + userId: ENV.EXAMPLE_USER.user_id, + email: ENV.EXAMPLE_USER._json.email, + name: ENV.EXAMPLE_USER._json.name, + nickname: ENV.EXAMPLE_USER._json.nickname, + picture: ENV.EXAMPLE_USER._json.picture, + appMetadata: JSON.stringify(ENV.EXAMPLE_USER._json[ENV.AUTH_AUDIENCE!]), + deletedAt: null, + lastLoggedIn: new Date(), + preferences: { create: { skipFrontdoorLogin: false } }, + }, + update: {}, + where: { id }, + }) + .then(() => { + logger.info('Example user created'); + }) + .catch((ex) => { + logger.error(getExceptionLog(ex), '[EXAMPLE_USER][ERROR] Fatal error, could not create example user'); + process.exit(1); + }); +} diff --git a/prisma/migrations/20241016225558_add_user_to_sfdc_org/migration.sql b/prisma/migrations/20241016225558_add_user_to_sfdc_org/migration.sql new file mode 100644 index 00000000..2f800484 --- /dev/null +++ b/prisma/migrations/20241016225558_add_user_to_sfdc_org/migration.sql @@ -0,0 +1,13 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "lastLoggedIn" TIMESTAMP(3); + +-- AlterTable +ALTER TABLE "salesforce_org" ADD COLUMN "jetstreamUserId2" UUID; + +UPDATE salesforce_org +SET "jetstreamUserId2" = u.id +FROM "User" u +WHERE salesforce_org."jetstreamUserId" = u."userId"; + +-- AddForeignKey +ALTER TABLE "salesforce_org" ADD CONSTRAINT "salesforce_org_jetstreamUserId2_fkey" FOREIGN KEY ("jetstreamUserId2") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index efba494b..c212e1ac 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,18 +8,20 @@ datasource db { } model User { - id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid - userId String @unique @db.VarChar - email String @db.VarChar - name String? @db.VarChar - nickname String? @db.VarChar - picture String? @db.VarChar - appMetadata Json? @db.Json - preferences UserPreference? - organizations JetstreamOrganization[] - deletedAt DateTime? - createdAt DateTime @default(now()) @db.Timestamp(6) - updatedAt DateTime @updatedAt + id String @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid + userId String @unique @db.VarChar + email String @db.VarChar + name String? @db.VarChar + nickname String? @db.VarChar + picture String? @db.VarChar + appMetadata Json? @db.Json + preferences UserPreference? + salesforceOrgs SalesforceOrg[] + organizations JetstreamOrganization[] + lastLoggedIn DateTime? + deletedAt DateTime? + createdAt DateTime @default(now()) @db.Timestamp(6) + updatedAt DateTime @updatedAt } model UserPreference { @@ -63,6 +65,8 @@ model SalesforceApi { model SalesforceOrg { id Int @id @default(autoincrement()) jetstreamUserId String @db.VarChar + jetstreamUser User? @relation(fields: [jetstreamUserId2], references: [id], onDelete: Cascade) + jetstreamUserId2 String? @db.Uuid jetstreamOrganization JetstreamOrganization? @relation(fields: [jetstreamOrganizationId], references: [id]) jetstreamOrganizationId String? @db.Uuid uniqueId String @db.VarChar