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/seed.ts b/src/backend/src/prisma/seed.ts index 0707f509a5..b897a7e318 100644 --- a/src/backend/src/prisma/seed.ts +++ b/src/backend/src/prisma/seed.ts @@ -58,7 +58,6 @@ 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 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..e198c839f1 100644 --- a/src/backend/src/prisma/seed/user.process.ts +++ b/src/backend/src/prisma/seed/user.process.ts @@ -1,31 +1,35 @@ -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'; import { adminCreateInput, - appAdminCreateInput, guestCreateInput, headCreateInput, leadCreateInput, 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; + +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 +39,8 @@ export type UsersOutput = { members: FullUser[]; guests: FullUser[]; all: FullUser[]; + roles: Role[]; + rolesByType: Record; }; export class UsersProcess extends SeedProcess { @@ -42,7 +48,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 +57,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 +82,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 }; } }