Add space member model (#1780)

This commit is contained in:
Luke Vella 2025-06-16 23:34:27 +02:00 committed by GitHub
parent dd9bdbcfc4
commit 424f39ae6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 200 additions and 68 deletions

View file

@ -9,6 +9,7 @@
* *
* See: https://github.com/lukevella/rallly/issues/949 * See: https://github.com/lukevella/rallly/issues/949
*/ */
import { createUser } from "@/features/user/mutations";
import { PrismaAdapter } from "@auth/prisma-adapter"; import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@rallly/database"; import { prisma } from "@rallly/database";
import type { Adapter } from "next-auth/adapters"; import type { Adapter } from "next-auth/adapters";
@ -38,26 +39,15 @@ export function CustomPrismaAdapter(options: {
}); });
}, },
createUser: async (user) => { createUser: async (user) => {
const newUser = await prisma.user.create({ return await createUser({
data: { name: user.name ?? "Unknown",
name: user.name ?? "Unknown", email: user.email,
email: user.email, emailVerified: user.emailVerified ?? undefined,
emailVerified: user.emailVerified, image: user.image ?? undefined,
image: user.image, timeZone: user.timeZone ?? undefined,
timeZone: user.timeZone, timeFormat: user.timeFormat ?? undefined,
weekStart: user.weekStart, locale: user.locale ?? undefined,
timeFormat: user.timeFormat,
locale: user.locale,
role: "user",
spaces: {
create: {
name: "Personal",
},
},
},
}); });
return newUser;
}, },
} as Adapter; } as Adapter;
} }

View file

@ -0,0 +1,22 @@
import { prisma } from "@rallly/database";
export async function createSpace({
ownerId,
name,
}: {
ownerId: string;
name: string;
}) {
return await prisma.space.create({
data: {
ownerId,
name,
members: {
create: {
userId: ownerId,
role: "OWNER",
},
},
},
});
}

View file

@ -0,0 +1,54 @@
import { type TimeFormat, prisma } from "@rallly/database";
export async function createUser({
name,
email,
emailVerified,
image,
timeZone,
timeFormat,
locale,
weekStart,
}: {
name: string;
email: string;
emailVerified?: Date;
image?: string;
timeZone?: string;
timeFormat?: TimeFormat;
locale?: string;
weekStart?: number;
}) {
return await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: {
name,
email,
emailVerified,
image,
timeZone,
timeFormat,
locale,
weekStart,
role: "user",
},
});
const space = await tx.space.create({
data: {
ownerId: user.id,
name: "Personal",
},
});
await tx.spaceMember.create({
data: {
spaceId: space.id,
userId: user.id,
role: "OWNER",
},
});
return user;
});
}

View file

@ -12,6 +12,7 @@ import { isValidName } from "@/utils/is-valid-name";
import { createToken, decryptToken } from "@/utils/session"; import { createToken, decryptToken } from "@/utils/session";
import { getInstanceSettings } from "@/features/instance-settings/queries"; import { getInstanceSettings } from "@/features/instance-settings/queries";
import { createUser } from "@/features/user/mutations";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { createRateLimitMiddleware, publicProcedure, router } from "../trpc"; import { createRateLimitMiddleware, publicProcedure, router } from "../trpc";
import type { RegistrationTokenPayload } from "../types"; import type { RegistrationTokenPayload } from "../types";
@ -124,21 +125,14 @@ export const auth = router({
return { ok: false }; return { ok: false };
} }
const user = await prisma.user.create({ const user = await createUser({
select: { id: true, name: true, email: true }, name,
data: { email,
name, emailVerified: new Date(),
email, timeZone: input.timeZone,
timeZone: input.timeZone, timeFormat: input.timeFormat,
timeFormat: input.timeFormat, weekStart: input.weekStart,
weekStart: input.weekStart, locale: input.locale,
locale: input.locale,
spaces: {
create: {
name: "Personal",
},
},
},
}); });
if (ctx.user?.isGuest) { if (ctx.user?.isGuest) {
@ -166,7 +160,14 @@ export const auth = router({
}, },
}); });
return { ok: true, user }; return {
ok: true,
user: {
id: user.id,
name: user.name,
email: user.email,
},
};
}), }),
getUserPermission: publicProcedure getUserPermission: publicProcedure
.input(z.object({ token: z.string() })) .input(z.object({ token: z.string() }))

View file

@ -0,0 +1,26 @@
-- CreateEnum
CREATE TYPE "SpaceMemberRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
-- CreateTable
CREATE TABLE "space_members" (
"id" TEXT NOT NULL,
"space_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"role" "SpaceMemberRole" NOT NULL DEFAULT 'MEMBER',
CONSTRAINT "space_members_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "space_members_space_id_idx" ON "space_members"("space_id");
-- CreateIndex
CREATE UNIQUE INDEX "space_members_space_id_user_id_key" ON "space_members"("space_id", "user_id");
-- AddForeignKey
ALTER TABLE "space_members" ADD CONSTRAINT "space_members_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "space_members" ADD CONSTRAINT "space_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -0,0 +1,15 @@
-- Create space members with OWNER role for existing spaces
INSERT INTO "space_members" ("id", "space_id", "user_id", "created_at", "updated_at", "role")
SELECT
gen_random_uuid(),
id as space_id,
owner_id as user_id,
NOW() as created_at,
NOW() as updated_at,
'OWNER' as role
FROM "spaces"
WHERE NOT EXISTS (
SELECT 1 FROM "space_members"
WHERE "space_members"."space_id" = "spaces"."id"
AND "space_members"."user_id" = "spaces"."owner_id"
);

View file

@ -0,0 +1,39 @@
model Space {
id String @id @default(uuid())
name String
ownerId String @map("owner_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
owner User @relation("UserSpaces", fields: [ownerId], references: [id], onDelete: Cascade)
polls Poll[]
scheduledEvents ScheduledEvent[]
subscription Subscription? @relation("SpaceToSubscription")
members SpaceMember[]
@@index([ownerId], type: Hash)
@@map("spaces")
}
enum SpaceMemberRole {
OWNER
ADMIN
MEMBER
}
model SpaceMember {
id String @id @default(uuid())
spaceId String @map("space_id")
userId String @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
role SpaceMemberRole @default(MEMBER)
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([spaceId, userId])
@@index([spaceId])
@@map("space_members")
}

View file

@ -33,22 +33,22 @@ enum UserRole {
} }
model User { model User {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
email String @unique() @db.Citext email String @unique() @db.Citext
emailVerified DateTime? @map("email_verified") emailVerified DateTime? @map("email_verified")
image String? image String?
timeZone String? @map("time_zone") timeZone String? @map("time_zone")
weekStart Int? @map("week_start") weekStart Int? @map("week_start")
timeFormat TimeFormat? @map("time_format") timeFormat TimeFormat? @map("time_format")
locale String? locale String?
createdAt DateTime @default(now()) @map("created_at") createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at") updatedAt DateTime? @updatedAt @map("updated_at")
customerId String? @map("customer_id") customerId String? @map("customer_id")
banned Boolean @default(false) banned Boolean @default(false)
bannedAt DateTime? @map("banned_at") bannedAt DateTime? @map("banned_at")
banReason String? @map("ban_reason") banReason String? @map("ban_reason")
role UserRole @default(user) role UserRole @default(user)
comments Comment[] comments Comment[]
polls Poll[] polls Poll[]
@ -58,7 +58,8 @@ model User {
paymentMethods PaymentMethod[] paymentMethods PaymentMethod[]
subscription Subscription? @relation("UserToSubscription") subscription Subscription? @relation("UserToSubscription")
spaces Space[] @relation("UserSpaces") spaces Space[] @relation("UserSpaces")
memberOf SpaceMember[]
pollViews PollView[] pollViews PollView[]
scheduledEvents ScheduledEvent[] scheduledEvents ScheduledEvent[]
@ -67,22 +68,6 @@ model User {
@@map("users") @@map("users")
} }
model Space {
id String @id @default(uuid())
name String
ownerId String @map("owner_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
owner User @relation("UserSpaces", fields: [ownerId], references: [id], onDelete: Cascade)
polls Poll[]
scheduledEvents ScheduledEvent[]
subscription Subscription? @relation("SpaceToSubscription")
@@index([ownerId], type: Hash)
@@map("spaces")
}
model VerificationToken { model VerificationToken {
identifier String @db.Citext identifier String @db.Citext
token String @unique token String @unique