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
*/
import { createUser } from "@/features/user/mutations";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@rallly/database";
import type { Adapter } from "next-auth/adapters";
@ -38,26 +39,15 @@ export function CustomPrismaAdapter(options: {
});
},
createUser: async (user) => {
const newUser = await prisma.user.create({
data: {
name: user.name ?? "Unknown",
email: user.email,
emailVerified: user.emailVerified,
image: user.image,
timeZone: user.timeZone,
weekStart: user.weekStart,
timeFormat: user.timeFormat,
locale: user.locale,
role: "user",
spaces: {
create: {
name: "Personal",
},
},
},
return await createUser({
name: user.name ?? "Unknown",
email: user.email,
emailVerified: user.emailVerified ?? undefined,
image: user.image ?? undefined,
timeZone: user.timeZone ?? undefined,
timeFormat: user.timeFormat ?? undefined,
locale: user.locale ?? undefined,
});
return newUser;
},
} 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 { getInstanceSettings } from "@/features/instance-settings/queries";
import { createUser } from "@/features/user/mutations";
import { TRPCError } from "@trpc/server";
import { createRateLimitMiddleware, publicProcedure, router } from "../trpc";
import type { RegistrationTokenPayload } from "../types";
@ -124,21 +125,14 @@ export const auth = router({
return { ok: false };
}
const user = await prisma.user.create({
select: { id: true, name: true, email: true },
data: {
name,
email,
timeZone: input.timeZone,
timeFormat: input.timeFormat,
weekStart: input.weekStart,
locale: input.locale,
spaces: {
create: {
name: "Personal",
},
},
},
const user = await createUser({
name,
email,
emailVerified: new Date(),
timeZone: input.timeZone,
timeFormat: input.timeFormat,
weekStart: input.weekStart,
locale: input.locale,
});
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
.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 {
id String @id @default(cuid())
name String
email String @unique() @db.Citext
emailVerified DateTime? @map("email_verified")
image String?
timeZone String? @map("time_zone")
weekStart Int? @map("week_start")
timeFormat TimeFormat? @map("time_format")
locale String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
customerId String? @map("customer_id")
banned Boolean @default(false)
bannedAt DateTime? @map("banned_at")
banReason String? @map("ban_reason")
role UserRole @default(user)
id String @id @default(cuid())
name String
email String @unique() @db.Citext
emailVerified DateTime? @map("email_verified")
image String?
timeZone String? @map("time_zone")
weekStart Int? @map("week_start")
timeFormat TimeFormat? @map("time_format")
locale String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
customerId String? @map("customer_id")
banned Boolean @default(false)
bannedAt DateTime? @map("banned_at")
banReason String? @map("ban_reason")
role UserRole @default(user)
comments Comment[]
polls Poll[]
@ -58,7 +58,8 @@ model User {
paymentMethods PaymentMethod[]
subscription Subscription? @relation("UserToSubscription")
spaces Space[] @relation("UserSpaces")
spaces Space[] @relation("UserSpaces")
memberOf SpaceMember[]
pollViews PollView[]
scheduledEvents ScheduledEvent[]
@ -67,22 +68,6 @@ model User {
@@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 {
identifier String @db.Citext
token String @unique