Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/backend/src/prisma/context.ts
Original file line number Diff line number Diff line change
@@ -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';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a topic for later but if this file does get big enough, it may be worth considering splitting them by process.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree this is worth watching as more processes are added. I do think having a shared seed context is useful because later seed processes depend on records created by earlier ones, but if the file grows too much, we could reduce duplication by having each process own its output type and using context.ts mainly as an aggregate/re-export point.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Exactly.

// Organization Context
export type OrganizationContext = {
organization: Organization;
bootstrapUserId: string;
};

// User Context
export type FullUser = Prisma.UserGetPayload<ReturnType<typeof getUserQueryArgs>>;

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<RoleEnum, Role[]>;
};

// Main Seed Context
export type SeedContext = OrganizationContext & UsersContext & RoleContext;

// Car Context
export type DateRange = {
Expand Down
20 changes: 14 additions & 6 deletions src/backend/src/prisma/factories/organization.factory.ts
Original file line number Diff line number Diff line change
@@ -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 }
}
});
1 change: 0 additions & 1 deletion src/backend/src/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
9 changes: 6 additions & 3 deletions src/backend/src/prisma/seed/organization.process.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,9 +17,8 @@ export class OrganizationProcess extends SeedProcess<{}, OrganizationOutput> {
}

async run(_deps: {}): Promise<OrganizationOutput> {
// 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()
});
Expand Down
52 changes: 35 additions & 17 deletions src/backend/src/prisma/seed/user.process.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
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,
Expand All @@ -15,17 +14,23 @@ import { SeedProcess } from '../processes/seed-process.js';
type FullUser = Prisma.UserGetPayload<ReturnType<typeof getUserQueryArgs>>;
Comment thread
Steph375 marked this conversation as resolved.
Outdated

const TOTAL_USERS = 350;
const BOOTSTRAP_APP_ADMINS = 1;

const GUEST_COUNT = Math.round(TOTAL_USERS * 0.5);
Comment thread
Steph375 marked this conversation as resolved.
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,
Comment thread
Steph375 marked this conversation as resolved.
[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);
Comment thread
Steph375 marked this conversation as resolved.

export type UsersOutput = {
appAdmins: FullUser[];
Expand All @@ -35,14 +40,16 @@ export type UsersOutput = {
members: FullUser[];
guests: FullUser[];
all: FullUser[];
roles: Role[];
rolesByType: Record<RoleEnum, Role[]>;
};

export class UsersProcess extends SeedProcess<OrganizationOutput, UsersOutput> {
dependencies() {
return [OrganizationProcess];
}

async run({ organization }: OrganizationOutput): Promise<UsersOutput> {
async run({ organization, bootstrapUserId }: OrganizationOutput): Promise<UsersOutput> {
const { organizationId } = organization;

const createUser = (input: Prisma.UserCreateInput): Promise<FullUser> =>
Expand All @@ -51,12 +58,12 @@ export class UsersProcess extends SeedProcess<OrganizationOutput, UsersOutput> {
...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)))
),
Expand All @@ -76,8 +83,19 @@ export class UsersProcess extends SeedProcess<OrganizationOutput, UsersOutput> {
)
]);

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 };
}
}
Loading