diff --git a/packages/components/src/admin/AdminDashboard.tsx b/packages/components/src/admin/AdminDashboard.tsx index 254e9fe..9331a39 100644 --- a/packages/components/src/admin/AdminDashboard.tsx +++ b/packages/components/src/admin/AdminDashboard.tsx @@ -20,7 +20,6 @@ export default function AdminDashboard() { const userData = data.users; const groupData = data.groups; - const groupInvitesData = data.groupInvites; return (
@@ -65,19 +64,6 @@ export default function AdminDashboard() { - - - - Invites - - Manage pending invitations. - - - - - - -
diff --git a/packages/components/src/admin/DataTable.tsx b/packages/components/src/admin/DataTable.tsx index 1cb6a52..27529e2 100644 --- a/packages/components/src/admin/DataTable.tsx +++ b/packages/components/src/admin/DataTable.tsx @@ -42,17 +42,6 @@ const columns: { [T in keyof AdminDataTypes]: DataColumn[] } = { { accessorKey: "createdAt", header: "Date of Creation" }, { accessorKey: "updatedAt", header: "Date Last Updated" }, ], - groupInvites: [ - { accessorKey: "email", header: "Email" }, - { accessorKey: "firstName", header: "First Name" }, - { accessorKey: "lastName", header: "Last Name" }, - { accessorKey: "stageName", header: "Stage Name" }, - { accessorKey: "role", header: "Role" }, - { accessorKey: "isSongWriter", header: "Songwriter?" }, - { accessorKey: "isAscapAffiliated", header: "ASCAP Affiliated?" }, - { accessorKey: "isBmiAffiliated", header: "BMI Affiliated?" }, - { accessorKey: "createdAt", header: "Date of Creation" }, - ], }; interface DataTableProps { diff --git a/packages/db/prisma/migrations/20250125001713_refactor_groups/migration.sql b/packages/db/prisma/migrations/20250125001713_refactor_groups/migration.sql new file mode 100644 index 0000000..49b0b99 --- /dev/null +++ b/packages/db/prisma/migrations/20250125001713_refactor_groups/migration.sql @@ -0,0 +1,62 @@ +/* + Warnings: + + - You are about to drop the `Group` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `GroupInvite` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `_GroupToUser` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "GroupInvite" DROP CONSTRAINT "GroupInvite_groupId_fkey"; + +-- DropForeignKey +ALTER TABLE "GroupInvite" DROP CONSTRAINT "GroupInvite_initiatorId_fkey"; + +-- DropForeignKey +ALTER TABLE "_GroupToUser" DROP CONSTRAINT "_GroupToUser_A_fkey"; + +-- DropForeignKey +ALTER TABLE "_GroupToUser" DROP CONSTRAINT "_GroupToUser_B_fkey"; + +-- DropTable +DROP TABLE "Group"; + +-- DropTable +DROP TABLE "GroupInvite"; + +-- DropTable +DROP TABLE "_GroupToUser"; + +-- CreateTable +CREATE TABLE "MusicianGroup" ( + "groupId" TEXT NOT NULL, + "organizerId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "MusicianGroup_pkey" PRIMARY KEY ("groupId") +); + +-- CreateTable +CREATE TABLE "MusicianGroupMember" ( + "groupId" TEXT NOT NULL, + "firstName" TEXT NOT NULL, + "lastName" TEXT NOT NULL, + "stageName" TEXT, + "email" TEXT NOT NULL, + "isSongWriter" BOOLEAN NOT NULL DEFAULT false, + "isAscapAffiliated" BOOLEAN NOT NULL DEFAULT false, + "isBmiAffiliated" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "MusicianGroupMember_groupId_email_key" ON "MusicianGroupMember"("groupId", "email"); + +-- AddForeignKey +ALTER TABLE "MusicianGroup" ADD CONSTRAINT "MusicianGroup_organizerId_fkey" FOREIGN KEY ("organizerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MusicianGroupMember" ADD CONSTRAINT "MusicianGroupMember_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "MusicianGroup"("groupId") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 1d39213..05652d1 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -33,10 +33,9 @@ model User { isSongWriter Boolean @default(false) isAscapAffiliated Boolean @default(false) isBmiAffiliated Boolean @default(false) - groups Group[] + musicianGroups MusicianGroup[] passwordResetReq PasswordResetReq? sessions Session[] - sentInvites GroupInvite[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -49,34 +48,6 @@ model Session { user User @relation(fields: [userId], references: [userId], onDelete: Cascade) } -model Group { - groupId String @id @default(uuid()) - name String - users User[] - invites GroupInvite[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -model GroupInvite { - inviteId String @default(cuid()) @map("id") - groupId String - initiatorId String - email String - firstName String - lastName String - stageName String? - role Role - isSongWriter Boolean @default(false) - isAscapAffiliated Boolean @default(false) - isBmiAffiliated Boolean @default(false) - group Group @relation(fields: [groupId], references: [groupId], onDelete: Cascade) - intitiator User @relation(fields: [initiatorId], references: [userId], onDelete: Cascade) - createdAt DateTime @default(now()) - - @@unique([groupId, email]) -} - model EmailVerificationCode { email String @id code String @@ -92,3 +63,29 @@ model PasswordResetReq { createdAt DateTime @default(now()) expiresAt DateTime } + +model MusicianGroup { + groupId String @id @default(uuid()) + organizerId String // The user that created the group + organizer User @relation(fields: [organizerId], references: [userId], onDelete: Cascade) + name String + groupMembers MusicianGroupMember[] // Does not contain the user that created the group + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model MusicianGroupMember { + groupId String + group MusicianGroup @relation(fields: [groupId], references: [groupId], onDelete: Cascade) + firstName String + lastName String + stageName String? + email String + isSongWriter Boolean @default(false) + isAscapAffiliated Boolean @default(false) + isBmiAffiliated Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([groupId, email]) +} diff --git a/packages/trpc/src/procedures/admin-view.ts b/packages/trpc/src/procedures/admin-view.ts index 1f85851..beae395 100644 --- a/packages/trpc/src/procedures/admin-view.ts +++ b/packages/trpc/src/procedures/admin-view.ts @@ -2,11 +2,15 @@ import { adminAuthenticatedProcedureBuilder } from "../internal/init"; export const getAdminViewProcedure = adminAuthenticatedProcedureBuilder.query( async ({ ctx }) => { - const [users, groups, groupInvites] = await Promise.all([ + const [users, groups] = await Promise.all([ ctx.prisma.user.findMany({ omit: { hashedPassword: true } }), - ctx.prisma.group.findMany(), - ctx.prisma.groupInvite.findMany(), + ctx.prisma.musicianGroup.findMany({ + include: { + organizer: true, + groupMembers: true, + }, + }), ]); - return { users, groups, groupInvites }; + return { users, groups }; }, ); diff --git a/packages/trpc/src/procedures/onboarding.ts b/packages/trpc/src/procedures/onboarding.ts index bbd3bdd..640a46a 100644 --- a/packages/trpc/src/procedures/onboarding.ts +++ b/packages/trpc/src/procedures/onboarding.ts @@ -60,7 +60,6 @@ export const onboardingProcedure = authenticatedProcedureBuilder }, }); } else { - // TODO: Actually send the group invites await ctx.prisma.user.update({ data: { role: "MUSICIAN", @@ -70,14 +69,13 @@ export const onboardingProcedure = authenticatedProcedureBuilder isSongWriter: input.isSongWriter, isAscapAffiliated: input.isAscapAffiliated, isBmiAffiliated: input.isBmiAffiliated, - groups: { + musicianGroups: { create: { name: input.groupName, - invites: { + groupMembers: { createMany: { data: input.groupMembers?.map((member) => ({ - initiatorId: ctx.session.userId, email: member.email, firstName: member.firstName, lastName: member.lastName, @@ -85,7 +83,6 @@ export const onboardingProcedure = authenticatedProcedureBuilder isSongWriter: member.isSongWriter, isAscapAffiliated: member.isAscapAffiliated, isBmiAffiliated: member.isBmiAffiliated, - role: "MUSICIAN", })) ?? [], }, }, diff --git a/tests/api/onboarding.test.ts b/tests/api/onboarding.test.ts index 86b695f..84bf2de 100644 --- a/tests/api/onboarding.test.ts +++ b/tests/api/onboarding.test.ts @@ -90,7 +90,7 @@ describe("get user", () => { }, }, }), - prisma.group.deleteMany({ + prisma.musicianGroup.deleteMany({ where: { name: "Owen's Group", }, @@ -136,18 +136,18 @@ describe("get user", () => { expect(response.message).toEqual("Successfully onboarded"); expect(mockCache.revalidatePath).toHaveBeenCalledWith("/onboarding"); - const group = await prisma.group.findFirst({ + const group = await prisma.musicianGroup.findFirst({ where: { name: "Owen's Group", }, include: { - invites: true, + groupMembers: true, }, }); expect(group).not.toBeNull(); - expect(group?.invites).toHaveLength(1); - expect(group?.invites[0]?.email).toEqual("damian@test.org"); + expect(group?.groupMembers).toHaveLength(1); + expect(group?.groupMembers[0]?.email).toEqual("damian@test.org"); }); test("Onboards media maker", async () => {