From 3e706270f87ad315076679c5a7d34696c909d49d Mon Sep 17 00:00:00 2001 From: Steph375 Date: Tue, 2 Jun 2026 21:16:14 -0400 Subject: [PATCH 1/5] outline --- src/backend/src/prisma/context.ts | 0 src/backend/src/prisma/factories/organization.factory.ts | 0 src/backend/src/prisma/processes/organization.process.ts | 0 src/backend/src/prisma/processes/user.process.ts | 5 +++-- 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/backend/src/prisma/context.ts create mode 100644 src/backend/src/prisma/factories/organization.factory.ts create mode 100644 src/backend/src/prisma/processes/organization.process.ts diff --git a/src/backend/src/prisma/context.ts b/src/backend/src/prisma/context.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/backend/src/prisma/factories/organization.factory.ts b/src/backend/src/prisma/factories/organization.factory.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/backend/src/prisma/processes/organization.process.ts b/src/backend/src/prisma/processes/organization.process.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/backend/src/prisma/processes/user.process.ts b/src/backend/src/prisma/processes/user.process.ts index 809e9ff39a..8c3ffc294c 100644 --- a/src/backend/src/prisma/processes/user.process.ts +++ b/src/backend/src/prisma/processes/user.process.ts @@ -2,6 +2,7 @@ import { Prisma, Theme } from '@prisma/client'; import { RoleEnum } from 'shared'; import { getUserQueryArgs } from '../../prisma-query-args/user.query-args.js'; import { SeedProcess } from './seed-process.js'; +import { OrganizationProcess } from './organization.process.js'; type FullUser = Prisma.UserGetPayload>; @@ -34,8 +35,8 @@ export class UsersProcess extends SeedProcess<{}, UsersOutput> { this.organizationId = organizationId; } - dependencies() { - return []; + dependencies() { + return [OrganizationProcess]; } async run(_deps: {}): Promise { From 492325a23179af2d89ebc79606f411579e9500bc Mon Sep 17 00:00:00 2001 From: Steph375 Date: Sun, 7 Jun 2026 15:04:25 -0400 Subject: [PATCH 2/5] #4208-fix --- src/backend/src/prisma/context.ts | 32 +++++++++++- .../prisma/factories/organization.factory.ts | 20 +++++--- .../prisma/processes/organization.process.ts | 0 src/backend/src/prisma/seed.ts | 4 +- .../src/prisma/seed/organization.process.ts | 9 ++-- src/backend/src/prisma/seed/user.process.ts | 50 +++++++++++++------ 6 files changed, 86 insertions(+), 29 deletions(-) delete mode 100644 src/backend/src/prisma/processes/organization.process.ts diff --git a/src/backend/src/prisma/context.ts b/src/backend/src/prisma/context.ts index 246bcc9fce..68ac9bef87 100644 --- a/src/backend/src/prisma/context.ts +++ b/src/backend/src/prisma/context.ts @@ -1,4 +1,34 @@ -import { Prisma } from '@prisma/client'; +import { Organization, Prisma, Role } from '@prisma/client'; +import { RoleEnum } from 'shared'; +import { getUserQueryArgs } from '../prisma-query-args/user.query-args.js'; + +// Organization Context +export type OrganizationContext = { + organization: Organization; + bootstrapUserId: string; +}; + +// User Context +export type FullUser = Prisma.UserGetPayload>; + +export type UsersContext = { + appAdmins: FullUser[]; + admins: FullUser[]; + heads: FullUser[]; + leadership: FullUser[]; + members: FullUser[]; + guests: FullUser[]; + all: FullUser[]; +}; + +// Role Context +export type RoleContext = { + roles: Role[]; + rolesByType: Record; +}; + +// Main Seed Context +export type SeedContext = OrganizationContext & UsersContext & RoleContext; // Car Context export type DateRange = { diff --git a/src/backend/src/prisma/factories/organization.factory.ts b/src/backend/src/prisma/factories/organization.factory.ts index cada67bea9..639b49ec44 100644 --- a/src/backend/src/prisma/factories/organization.factory.ts +++ b/src/backend/src/prisma/factories/organization.factory.ts @@ -1,19 +1,27 @@ import { Prisma, Theme } from '@prisma/client'; +export const BOOTSTRAP_GOOGLE_AUTH_ID = 'thomas-emrax'; + export const bootstrapUserCreateInput = (): Prisma.UserCreateInput => ({ firstName: 'Thomas', lastName: 'Emrax', - googleAuthId: 'thomas-emrax', - email: 'emrax.t@husky.neu.edu', - emailId: 'emrax.t', + googleAuthId: BOOTSTRAP_GOOGLE_AUTH_ID, + email: 'admin@bootstrap.com', + emailId: 'admin', userSettings: { - create: { defaultTheme: Theme.DARK, slackId: 'emrax.t' } + create: { defaultTheme: Theme.DARK, slackId: 'admin' } } }); export const organizationCreateInput = (userCreatedId: string): Prisma.OrganizationCreateInput => ({ name: 'Northeastern Electric Racing', description: - 'Northeastern Electric Racing is a student-run organization at Northeastern University building all-electric formula-style race cars from scratch to compete in Forumla Hybrid + Electric Formula SAE (FSAE).', - userCreated: { connect: { userId: userCreatedId } } + 'Northeastern Electric Racing is a student-run organization at Northeastern University building all-electric formula-style race cars from scratch to compete in Formula Hybrid + Electric Formula SAE (FSAE).', + applicationLink: 'https://docs.google.com/forms/d/e/1FAIpQLSeCvG7GqmZm_gmSZiahbVTW9ZFpEWG0YfGQbkSB_whhHzxXpA/closedform', + platformDescription: + 'Finishline is a Project Management Dashboard developed by the Software Team at Northeastern Electric Racing.', + platformLogoImageId: '1auQO3GYydZOo1-vCn0D2iyCfaxaVFssx', + userCreated: { + connect: { userId: userCreatedId } + } }); diff --git a/src/backend/src/prisma/processes/organization.process.ts b/src/backend/src/prisma/processes/organization.process.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index 0707f509a5..15d035024a 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -54,12 +54,10 @@ import { Theme } from '@prisma/client'; import { SeedRunner } from './processes/seed-runner.js'; import { UsersProcess } from './seed/user.process.js'; import { OrganizationProcess } from './seed/organization.process.js'; -import { CarProcess } from './seed/car.process.js'; const prisma = new PrismaClient(); -// ORDER MATTERS AT THE MOMENT. I am still looking into topological sort so that order won't matter here. -await new SeedRunner().withPrisma(prisma).register(new OrganizationProcess(), new CarProcess(), new UsersProcess()).run(); +await new SeedRunner().withPrisma(prisma).register(new OrganizationProcess(), new UsersProcess()).run(); await prisma.$disconnect(); diff --git a/src/backend/src/prisma/seed/organization.process.ts b/src/backend/src/prisma/seed/organization.process.ts index 460f5a064a..298073613a 100644 --- a/src/backend/src/prisma/seed/organization.process.ts +++ b/src/backend/src/prisma/seed/organization.process.ts @@ -1,6 +1,10 @@ import { Organization } from '@prisma/client'; import { SeedProcess } from '../processes/seed-process.js'; -import { bootstrapUserCreateInput, organizationCreateInput } from '../factories/organization.factory.js'; +import { + BOOTSTRAP_GOOGLE_AUTH_ID, + bootstrapUserCreateInput, + organizationCreateInput +} from '../factories/organization.factory.js'; export type OrganizationOutput = { organization: Organization; @@ -13,9 +17,8 @@ export class OrganizationProcess extends SeedProcess<{}, OrganizationOutput> { } async run(_deps: {}): Promise { - // Kept the GOAT in for old times sake const bootstrap = await this.prisma.user.upsert({ - where: { googleAuthId: 'thomas-emrax' }, + where: { googleAuthId: BOOTSTRAP_GOOGLE_AUTH_ID }, update: {}, create: bootstrapUserCreateInput() }); diff --git a/src/backend/src/prisma/seed/user.process.ts b/src/backend/src/prisma/seed/user.process.ts index d2dfc73cfc..623b4f149e 100644 --- a/src/backend/src/prisma/seed/user.process.ts +++ b/src/backend/src/prisma/seed/user.process.ts @@ -4,7 +4,6 @@ import { getUserQueryArgs } from '../../prisma-query-args/user.query-args.js'; import { OrganizationOutput, OrganizationProcess } from './organization.process.js'; import { adminCreateInput, - appAdminCreateInput, guestCreateInput, headCreateInput, leadCreateInput, @@ -15,17 +14,23 @@ import { SeedProcess } from '../processes/seed-process.js'; type FullUser = Prisma.UserGetPayload>; const TOTAL_USERS = 350; +const BOOTSTRAP_APP_ADMINS = 1; + +const GUEST_COUNT = Math.round(TOTAL_USERS * 0.5); +const MEMBER_COUNT = Math.round(TOTAL_USERS * 0.35); +const LEADERSHIP_COUNT = Math.round(TOTAL_USERS * 0.1); +const HEAD_COUNT = Math.round(TOTAL_USERS * 0.04); const ROLE_COUNTS = { - [RoleEnum.GUEST]: Math.floor(TOTAL_USERS * 0.5), - [RoleEnum.MEMBER]: Math.floor(TOTAL_USERS * 0.5 * 0.35), - [RoleEnum.LEADERSHIP]: Math.floor(TOTAL_USERS * 0.5 * 0.1), - [RoleEnum.HEAD]: Math.floor(TOTAL_USERS * 0.5 * 0.04), - [RoleEnum.ADMIN]: Math.floor(TOTAL_USERS * 0.5 * 0.01), - [RoleEnum.APP_ADMIN]: 2 + [RoleEnum.GUEST]: GUEST_COUNT, + [RoleEnum.MEMBER]: MEMBER_COUNT, + [RoleEnum.LEADERSHIP]: LEADERSHIP_COUNT, + [RoleEnum.HEAD]: HEAD_COUNT, + [RoleEnum.ADMIN]: TOTAL_USERS - BOOTSTRAP_APP_ADMINS - GUEST_COUNT - MEMBER_COUNT - LEADERSHIP_COUNT - HEAD_COUNT, + [RoleEnum.APP_ADMIN]: 0 } as const; -export const USER_COUNT = Object.values(ROLE_COUNTS).reduce((a, b) => a + b, 0); +export const USER_COUNT = BOOTSTRAP_APP_ADMINS + Object.values(ROLE_COUNTS).reduce((a, b) => a + b, 0); export type UsersOutput = { appAdmins: FullUser[]; @@ -35,6 +40,8 @@ export type UsersOutput = { members: FullUser[]; guests: FullUser[]; all: FullUser[]; + roles: Role[]; + rolesByType: Record; }; export class UsersProcess extends SeedProcess { @@ -42,7 +49,7 @@ export class UsersProcess extends SeedProcess { return [OrganizationProcess]; } - async run({ organization }: OrganizationOutput): Promise { + async run({ organization, bootstrapUserId }: OrganizationOutput): Promise { const { organizationId } = organization; const createUser = (input: Prisma.UserCreateInput): Promise => @@ -51,12 +58,12 @@ export class UsersProcess extends SeedProcess { ...getUserQueryArgs(organizationId) }); - const [appAdmins, admins, heads, leadership, members, guests] = await Promise.all([ - Promise.all( - Array.from({ length: ROLE_COUNTS[RoleEnum.APP_ADMIN] }, () => - createUser(appAdminCreateInput(this.faker, organizationId)) - ) - ), + const bootstrapAppAdmin = await this.prisma.user.findUniqueOrThrow({ + where: { userId: bootstrapUserId }, + ...getUserQueryArgs(organizationId) + }); + + const [admins, heads, leadership, members, guests] = await Promise.all([ Promise.all( Array.from({ length: ROLE_COUNTS[RoleEnum.ADMIN] }, () => createUser(adminCreateInput(this.faker, organizationId))) ), @@ -76,8 +83,19 @@ export class UsersProcess extends SeedProcess { ) ]); + const appAdmins = [bootstrapAppAdmin]; const all = [...appAdmins, ...admins, ...heads, ...leadership, ...members, ...guests]; + const roles = all.flatMap((user) => user.roles); + + const rolesByType = { + [RoleEnum.APP_ADMIN]: roles.filter((role) => role.roleType === RoleEnum.APP_ADMIN), + [RoleEnum.ADMIN]: roles.filter((role) => role.roleType === RoleEnum.ADMIN), + [RoleEnum.HEAD]: roles.filter((role) => role.roleType === RoleEnum.HEAD), + [RoleEnum.LEADERSHIP]: roles.filter((role) => role.roleType === RoleEnum.LEADERSHIP), + [RoleEnum.MEMBER]: roles.filter((role) => role.roleType === RoleEnum.MEMBER), + [RoleEnum.GUEST]: roles.filter((role) => role.roleType === RoleEnum.GUEST) + }; - return { appAdmins, admins, heads, leadership, members, guests, all }; + return { appAdmins, admins, heads, leadership, members, guests, all, roles, rolesByType }; } } From d7c79b6bb55ca083cb28acdc74aa9185c8a085b5 Mon Sep 17 00:00:00 2001 From: Steph375 Date: Sun, 7 Jun 2026 15:20:43 -0400 Subject: [PATCH 3/5] #4208 tsc-check fix --- src/backend/src/prisma/seed/user.process.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/src/prisma/seed/user.process.ts b/src/backend/src/prisma/seed/user.process.ts index 623b4f149e..670413b988 100644 --- a/src/backend/src/prisma/seed/user.process.ts +++ b/src/backend/src/prisma/seed/user.process.ts @@ -1,4 +1,4 @@ -import { Prisma } from '@prisma/client'; +import { Prisma, Role } from '@prisma/client'; import { RoleEnum } from 'shared'; import { getUserQueryArgs } from '../../prisma-query-args/user.query-args.js'; import { OrganizationOutput, OrganizationProcess } from './organization.process.js'; From df5a0feaa21b712d901839882de8a64b61e009a9 Mon Sep 17 00:00:00 2001 From: Steph375 Date: Sun, 7 Jun 2026 21:19:45 -0400 Subject: [PATCH 4/5] add back car process --- src/backend/src/prisma/seed.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/src/prisma/seed.ts b/src/backend/src/prisma/seed.ts index 15d035024a..b897a7e318 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -54,10 +54,11 @@ import { Theme } from '@prisma/client'; import { SeedRunner } from './processes/seed-runner.js'; import { UsersProcess } from './seed/user.process.js'; import { OrganizationProcess } from './seed/organization.process.js'; +import { CarProcess } from './seed/car.process.js'; const prisma = new PrismaClient(); -await new SeedRunner().withPrisma(prisma).register(new OrganizationProcess(), new UsersProcess()).run(); +await new SeedRunner().withPrisma(prisma).register(new OrganizationProcess(), new CarProcess(), new UsersProcess()).run(); await prisma.$disconnect(); From 99cb18eb912daedc99d0ada0abab90ea5574196d Mon Sep 17 00:00:00 2001 From: Steph375 Date: Mon, 8 Jun 2026 17:19:28 -0400 Subject: [PATCH 5/5] #4208 fix --- src/backend/src/prisma/seed/user.process.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/src/prisma/seed/user.process.ts b/src/backend/src/prisma/seed/user.process.ts index 670413b988..e198c839f1 100644 --- a/src/backend/src/prisma/seed/user.process.ts +++ b/src/backend/src/prisma/seed/user.process.ts @@ -10,8 +10,7 @@ import { memberCreateInput } from '../factories/user.factory.js'; import { SeedProcess } from '../processes/seed-process.js'; - -type FullUser = Prisma.UserGetPayload>; +import type { FullUser } from '../context.js'; const TOTAL_USERS = 350; const BOOTSTRAP_APP_ADMINS = 1;