diff --git a/scripts/seed.ts b/scripts/seed.ts index b100c4c0e5261e..7ed496c83be00b 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -110,390 +110,6 @@ async function createTeamAndAddUsers( return team; } -async function createOrganizationAndAddMembersAndTeams({ - org: { orgData, members: orgMembers }, - teams, - usersOutsideOrg, -}: { - org: { - orgData: Ensure, "name" | "slug"> & { - organizationSettings: Prisma.OrganizationSettingsCreateWithoutOrganizationInput; - }; - members: { - memberData: Ensure, "username" | "name" | "email" | "password">; - orgMembership: Partial; - orgProfile: { - username: string; - }; - inTeams: { slug: string; role: MembershipRole }[]; - }[]; - }; - teams: { - teamData: Omit, "name" | "slug">, "members">; - nonOrgMembers: Ensure, "username" | "name" | "email" | "password">[]; - }[]; - usersOutsideOrg: { - name: string; - username: string; - email: string; - }[]; -}) { - console.log(`\nšŸ¢ Creating organization "${orgData.name}"`); - - const existingTeam = await prisma.team.findFirst({ - where: { - slug: orgData.slug, - parentId: null, - }, - }); - - if (existingTeam) { - console.log(`Organization with slug '${orgData.slug}' already exists, ensuring settings are up to date.`); - await prisma.organizationSettings.upsert({ - where: { organizationId: existingTeam.id }, - update: { ...orgData.organizationSettings }, - create: { organizationId: existingTeam.id, ...orgData.organizationSettings }, - }); - return; - } - - const orgMembersInDb: (User & { - inTeams: { slug: string; role: MembershipRole }[]; - orgMembership: Partial; - orgProfile: { - username: string; - }; - })[] = []; - - const batchSize = 50; - for (let i = 0; i < orgMembers.length; i += batchSize) { - const batch = orgMembers.slice(i, i + batchSize); - - const batchResults = await Promise.all( - batch.map(async (member) => { - try { - const newUser = await createUserAndEventType({ - user: { - ...member.memberData, - theme: - member.memberData.theme === "dark" || member.memberData.theme === "light" - ? member.memberData.theme - : undefined, - password: member.memberData.password.create?.hash ?? "", - }, - eventTypes: [ - { - title: "30min", - slug: "30min", - length: 30, - _bookings: [ - { - uid: uuid(), - title: "30min", - startTime: dayjs().add(1, "day").toDate(), - endTime: dayjs().add(1, "day").add(30, "minutes").toDate(), - }, - ], - }, - ], - }); - - // Create temp org redirect with upsert to handle duplicates - await prisma.tempOrgRedirect.upsert({ - where: { - from_type_fromOrgId: { - from: member.memberData.username, - type: RedirectType.User, - fromOrgId: 0, - }, - }, - update: { - toUrl: `${WEBAPP_URL}/${member.orgProfile.username}`, - }, - create: { - fromOrgId: 0, - type: RedirectType.User, - from: member.memberData.username, - toUrl: `${WEBAPP_URL}/${member.orgProfile.username}`, - }, - }); - - return { - ...newUser, - inTeams: member.inTeams, - orgMembership: member.orgMembership, - orgProfile: member.orgProfile, - }; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { - console.log("Organization member already seeded, skipping"); - const existingUser = await prisma.user.findUnique({ - where: { - email_username: { - email: member.memberData.email, - username: member.memberData.username, - }, - }, - }); - if (!existingUser) throw e; - return { - ...existingUser, - inTeams: member.inTeams, - orgMembership: member.orgMembership, - orgProfile: member.orgProfile, - }; - } - throw e; - } - }) - ); - - orgMembersInDb.push(...batchResults.filter((r): r is NonNullable => r !== null)); - } - - await Promise.all( - usersOutsideOrg.map(async (user) => { - await prisma.user.upsert({ - where: { email_username: { email: user.email, username: user.username } }, - update: {}, - create: { - username: user.username, - name: user.name, - email: user.email, - emailVerified: new Date(), - password: { - create: { - hash: await hashPassword(user.username), - }, - }, - }, - }); - }) - ); - - const { organizationSettings, ...restOrgData } = orgData; - - // Step 1: Create organization (team) with just metadata and organizationSettings - const orgInDb = await prisma.team.create({ - data: { - ...restOrgData, - metadata: { - ...(orgData.metadata && typeof orgData.metadata === "object" ? orgData.metadata : {}), - isOrganization: true, - }, - organizationSettings: { - create: { - ...organizationSettings, - }, - }, - }, - select: { - id: true, - }, - }); - - // Step 2: Create org profiles in batches to avoid large transactions - const profileBatchSize = 50; - for (let i = 0; i < orgMembersInDb.length; i += profileBatchSize) { - const batch = orgMembersInDb.slice(i, i + profileBatchSize); - await Promise.all( - batch.map((member) => - prisma.profile.create({ - data: { - uid: uuid(), - username: member.orgProfile.username, - organizationId: orgInDb.id, - userId: member.id, - movedFromUser: { - connect: { - id: member.id, - }, - }, - }, - }) - ) - ); - } - - // Step 3: Create memberships using createMany for better performance - const membershipBatchSize = 100; - for (let i = 0; i < orgMembersInDb.length; i += membershipBatchSize) { - const batch = orgMembersInDb.slice(i, i + membershipBatchSize); - await prisma.membership.createMany({ - data: batch.map((member) => ({ - teamId: orgInDb.id, - userId: member.id, - role: member.orgMembership.role || "MEMBER", - accepted: member.orgMembership.accepted ?? false, - })), - }); - } - - // Step 4: Fetch created profiles to rebuild orgMembersInDBWithProfileId - const createdProfiles = await prisma.profile.findMany({ - where: { - organizationId: orgInDb.id, - userId: { in: orgMembersInDb.map((m) => m.id) }, - }, - select: { - id: true, - userId: true, - }, - }); - - const orgMembersInDBWithProfileId = orgMembersInDb.map((member) => ({ - ...member, - profile: { - ...member.orgProfile, - id: createdProfiles.find((p) => p.userId === member.id)?.id, - }, - })); - - // For each member create one event - for (const member of orgMembersInDBWithProfileId) { - await prisma.eventType.create({ - data: { - title: `${member.name} Event`, - slug: `${member.username}-event`, - length: 15, - owner: { - connect: { - id: member.id, - }, - }, - profile: { - connect: { - id: member.profile.id, - }, - }, - users: { - connect: { - id: member.id, - }, - }, - }, - }); - - // Create schedule for every member - await prisma.schedule.create({ - data: { - name: "Working Hours", - userId: member.id, - availability: { - create: { - days: [1, 2, 3, 4, 5], - startTime: "1970-01-01T09:00:00.000Z", - endTime: "1970-01-01T17:00:00.000Z", - }, - }, - }, - }); - } - - const organizationTeams: Team[] = []; - - // Create all the teams in the organization - for (let teamIndex = 0; teamIndex < teams.length; teamIndex++) { - const nonOrgMembers: User[] = []; - const team = teams[teamIndex]; - for (const nonOrgMember of team.nonOrgMembers) { - nonOrgMembers.push( - await prisma.user.create({ - data: { - ...nonOrgMember, - password: { - create: { - hash: await hashPassword(nonOrgMember.username), - }, - }, - emailVerified: new Date(), - }, - }) - ); - } - organizationTeams.push( - await prisma.team.create({ - data: { - ...team.teamData, - parent: { - connect: { - id: orgInDb.id, - }, - }, - metadata: team.teamData.metadata || {}, - members: { - create: nonOrgMembers.map((member) => ({ - user: { - connect: { - id: member.id, - }, - }, - role: "MEMBER", - accepted: true, - })), - }, - }, - }) - ); - - const ownerForEvent = orgMembersInDBWithProfileId[0]; - if (!ownerForEvent) { - console.log( - `Warning: No organization members with profiles found for creating team event, skipping event creation for team ${team.teamData.slug}` - ); - continue; - } - // Create event for each team - await prisma.eventType.create({ - data: { - title: `${team.teamData.name} Event 1`, - slug: `${team.teamData.slug}-event-1`, - schedulingType: SchedulingType.ROUND_ROBIN, - length: 15, - team: { - connect: { - id: organizationTeams[teamIndex].id, - }, - }, - owner: { - connect: { - id: ownerForEvent.id, - }, - }, - profile: { - connect: { - id: ownerForEvent.profile.id, - }, - }, - users: { - connect: { - id: ownerForEvent.id, - }, - }, - }, - }); - } - - // Create memberships for all the organization members with the respective teams - for (const member of orgMembersInDBWithProfileId) { - for (const { slug: teamSlug, role } of member.inTeams) { - const team = organizationTeams.find((t) => t.slug === teamSlug); - if (!team) { - throw Error(`Team with slug ${teamSlug} not found`); - } - await prisma.membership.create({ - data: { - createdAt: new Date(), - teamId: team.id, - userId: member.id, - role: role, - accepted: true, - }, - }); - } - } -} - async function seedApiKey(userId: number, apiKey: string) { const hashedKey = hashAPIKey(apiKey); @@ -519,118 +135,15 @@ async function seedApiKey(userId: number, apiKey: string) { console.log(`šŸ”‘ Created seeded API Key: ${apiKeyPrefix}${apiKey}`); } -async function ensureAcmeOwnerHasApiKeySeeded() { - const owner1AcmeUser = await prisma.user.findFirst({ - where: { email: "owner1-acme@example.com" }, +async function ensureProUserHasApiKeySeeded() { + const proUser = await prisma.user.findFirst({ + where: { email: "pro@example.com" }, }); - if (owner1AcmeUser) { - await seedApiKey(owner1AcmeUser.id, "0123456789abcdef0123456789abcdef"); + if (proUser) { + await seedApiKey(proUser.id, "0123456789abcdef0123456789abcdef"); } } -async function seedPerHostLocationsInAcmeOrg() { - const acmeOrg = await prisma.team.findFirst({ - where: { slug: "acme", parentId: null }, - select: { id: true }, - }); - if (!acmeOrg) { - console.log("Acme org not found, skipping per-host location seeding."); - return; - } - - const acmeTeam = await prisma.team.findFirst({ - where: { slug: "team1", parentId: acmeOrg.id }, - select: { id: true }, - }); - if (!acmeTeam) { - console.log("Acme Team 1 not found, skipping per-host location seeding."); - return; - } - - const existingEvent = await prisma.eventType.findFirst({ - where: { slug: "per-host-location-event", teamId: acmeTeam.id }, - }); - if (existingEvent) { - console.log("Per-host location event already seeded in Acme, skipping."); - return; - } - - const owner = await prisma.user.findFirst({ - where: { email: "owner1-acme@example.com" }, - select: { id: true }, - }); - const member0 = await prisma.user.findFirst({ - where: { email: "member0-acme@example.com" }, - select: { id: true }, - }); - const member2 = await prisma.user.findFirst({ - where: { email: "member2-acme@example.com" }, - select: { id: true }, - }); - if (!owner || !member0 || !member2) { - console.log("Required Acme members not found, skipping per-host location seeding."); - return; - } - - const hosts = [owner, member0, member2]; - - const ownerProfile = await prisma.profile.findFirst({ - where: { userId: owner.id, organizationId: acmeOrg.id }, - select: { id: true }, - }); - - const eventType = await prisma.eventType.create({ - data: { - title: "Per Host Location Event", - slug: "per-host-location-event", - length: 30, - schedulingType: SchedulingType.ROUND_ROBIN, - enablePerHostLocations: true, - team: { connect: { id: acmeTeam.id } }, - owner: { connect: { id: owner.id } }, - ...(ownerProfile ? { profile: { connect: { id: ownerProfile.id } } } : {}), - users: { connect: hosts.map((h) => ({ id: h.id })) }, - hosts: { - create: hosts.map((h) => ({ - userId: h.id, - isFixed: false, - })), - }, - }, - }); - - await prisma.hostLocation.create({ - data: { - userId: owner.id, - eventTypeId: eventType.id, - type: "integrations:daily", - }, - }); - - await prisma.hostLocation.create({ - data: { - userId: member0.id, - eventTypeId: eventType.id, - type: "link", - link: "https://example.com/meet", - }, - }); - - await prisma.hostLocation.create({ - data: { - userId: member2.id, - eventTypeId: eventType.id, - type: "userPhone", - phoneNumber: "+1234567890", - }, - }); - - console.log( - `Seeded per-host locations in Acme Org for event "${eventType.slug}" (id=${eventType.id}): ` + - `owner1->Cal Video, member0->Link, member2->Phone` - ); -} - async function main() { await createUserAndEventType({ user: { @@ -1201,177 +714,9 @@ async function main() { ] ); - await createOrganizationAndAddMembersAndTeams({ - org: { - orgData: { - name: "Acme Inc", - slug: "acme", - isOrganization: true, - organizationSettings: { - isOrganizationVerified: true, - orgAutoAcceptEmail: "acme.com", - isAdminAPIEnabled: true, - isAdminReviewed: true, - }, - }, - members: [ - { - memberData: { - email: "owner1-acme@example.com", - password: { - create: { - hash: "owner1-acme", - }, - }, - username: "owner1-acme", - name: "Owner 1", - }, - orgMembership: { - role: "OWNER", - accepted: true, - }, - orgProfile: { - username: "owner1", - }, - inTeams: [ - { - slug: "team1", - role: "ADMIN", - }, - ], - }, - ...Array.from({ length: 10 }, (_, i) => ({ - memberData: { - email: `member${i}-acme@example.com`, - password: { - create: { - hash: `member${i}-acme`, - }, - }, - username: `member${i}-acme`, - name: `Member ${i}`, - }, - orgMembership: { - role: MembershipRole.MEMBER, - accepted: true, - }, - orgProfile: { - username: `member${i}`, - }, - inTeams: - i % 2 === 0 - ? [ - { - slug: "team1", - role: MembershipRole.MEMBER, - }, - ] - : [], - })), - ], - }, - teams: [ - { - teamData: { - name: "Team 1", - slug: "team1", - }, - nonOrgMembers: [ - { - email: "non-acme-member-1@example.com", - password: { - create: { - hash: "non-acme-member-1", - }, - }, - username: "non-acme-member-1", - name: "NonAcme Member1", - }, - ], - }, - ], - usersOutsideOrg: [ - { - name: "Jane Doe", - email: "jane@acme.com", - username: "jane-outside-org", - }, - ], - }); - - await createOrganizationAndAddMembersAndTeams({ - org: { - orgData: { - name: "Dunder Mifflin", - slug: "dunder-mifflin", - isOrganization: true, - organizationSettings: { - isOrganizationVerified: true, - orgAutoAcceptEmail: "dunder-mifflin.com", - isAdminReviewed: true, - }, - }, - members: [ - { - memberData: { - email: "owner1-dunder@example.com", - password: { - create: { - hash: "owner1-dunder", - }, - }, - username: "owner1-dunder", - name: "Owner 1", - }, - orgMembership: { - role: "OWNER", - accepted: true, - }, - orgProfile: { - username: "owner1", - }, - inTeams: [ - { - slug: "team1", - role: "ADMIN", - }, - ], - }, - ], - }, - teams: [ - { - teamData: { - name: "Team 1", - slug: "team1", - }, - nonOrgMembers: [ - { - email: "non-dunder-member-1@example.com", - password: { - create: { - hash: "non-dunder-member-1", - }, - }, - username: "non-dunder-member-1", - name: "NonDunder Member1", - }, - ], - }, - ], - usersOutsideOrg: [ - { - name: "John Doe", - email: "john@dunder-mifflin.com", - username: "john-outside-org", - }, - ], - }); - // Routing forms feature removed - routing form seeding no longer needed - await ensureAcmeOwnerHasApiKeySeeded(); - await seedPerHostLocationsInAcmeOrg(); + await ensureProUserHasApiKeySeeded(); } async function runSeed() {