mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-11 05:27:51 +02:00
✨ Add space member model (#1780)
This commit is contained in:
parent
dd9bdbcfc4
commit
424f39ae6b
8 changed files with 200 additions and 68 deletions
|
@ -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,
|
emailVerified: user.emailVerified ?? undefined,
|
||||||
image: user.image,
|
image: user.image ?? undefined,
|
||||||
timeZone: user.timeZone,
|
timeZone: user.timeZone ?? undefined,
|
||||||
weekStart: user.weekStart,
|
timeFormat: user.timeFormat ?? undefined,
|
||||||
timeFormat: user.timeFormat,
|
locale: user.locale ?? undefined,
|
||||||
locale: user.locale,
|
|
||||||
role: "user",
|
|
||||||
spaces: {
|
|
||||||
create: {
|
|
||||||
name: "Personal",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return newUser;
|
|
||||||
},
|
},
|
||||||
} as Adapter;
|
} as Adapter;
|
||||||
}
|
}
|
||||||
|
|
22
apps/web/src/features/spaces/mutations.ts
Normal file
22
apps/web/src/features/spaces/mutations.ts
Normal 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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
54
apps/web/src/features/user/mutations.ts
Normal file
54
apps/web/src/features/user/mutations.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
|
@ -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 },
|
|
||||||
data: {
|
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
|
emailVerified: new Date(),
|
||||||
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() }))
|
||||||
|
|
|
@ -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;
|
|
@ -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"
|
||||||
|
);
|
39
packages/database/prisma/models/space.prisma
Normal file
39
packages/database/prisma/models/space.prisma
Normal 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")
|
||||||
|
}
|
|
@ -59,6 +59,7 @@ model User {
|
||||||
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue