feat: add workspace management features including member invitations and role updates

- Implemented API endpoints for managing workspace members (GET, POST, PATCH, DELETE).
- Added workspace creation and retrieval functionalities.
- Enhanced project model to associate with workspaces.
- Updated project member roles and access control logic.
- Created sign-out page and updated authentication flow.
- Modified header to include navigation to workspaces.
- Updated Prisma schema to include workspace and member models.
- Seed script updated to create demo workspaces and associated members.
This commit is contained in:
Yusuf İpek
2026-02-07 07:41:12 +03:00
parent d90ce4e1f6
commit 6e95f667e3
28 changed files with 2891 additions and 138 deletions
+64 -9
View File
@@ -23,11 +23,13 @@ model User {
updatedAt DateTime @updatedAt
// Relations
accounts Account[]
sessions Session[]
projects Project[]
comments Comment[]
memberships ProjectMember[]
accounts Account[]
sessions Session[]
ownedWorkspaces Workspace[]
workspaceMemberships WorkspaceMember[]
projects Project[]
comments Comment[]
projectMemberships ProjectMember[]
@@map("users")
}
@@ -76,6 +78,55 @@ model VerificationToken {
// APPLICATION MODELS
// ============================================
// ---- Workspace ----
model Workspace {
id String @id @default(cuid())
name String
slug String @unique
description String? @db.Text
// Ownership
ownerId String
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
members WorkspaceMember[]
projects Project[]
@@index([ownerId])
@@index([slug])
@@map("workspaces")
}
model WorkspaceMember {
id String @id @default(cuid())
role WorkspaceMemberRole @default(COMMENTATOR)
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@unique([workspaceId, userId])
@@index([userId])
@@map("workspace_members")
}
enum WorkspaceMemberRole {
ADMIN // Full access: manage members, delete projects, etc.
COMMENTATOR // Can view all projects and comment only
}
// ---- Project ----
model Project {
id String @id @default(cuid())
name String
@@ -89,6 +140,10 @@ model Project {
ownerId String
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
// Workspace (required - every project belongs to a workspace)
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -100,6 +155,7 @@ model Project {
@@index([ownerId])
@@index([slug])
@@index([workspaceId])
@@map("projects")
}
@@ -111,7 +167,7 @@ enum ProjectVisibility {
model ProjectMember {
id String @id @default(cuid())
role ProjectMemberRole @default(VIEWER)
role ProjectMemberRole @default(COMMENTATOR)
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@ -127,9 +183,8 @@ model ProjectMember {
}
enum ProjectMemberRole {
VIEWER // Can view and comment
EDITOR // Can add/edit videos
ADMIN // Can manage members and settings
ADMIN // Can manage members, settings, delete videos
COMMENTATOR // Can view and comment only
}
model Video {
+44 -6
View File
@@ -1,4 +1,5 @@
import { PrismaClient, ProjectVisibility, ProjectMemberRole, SharePermission } from '@prisma/client';
import 'dotenv/config';
import { PrismaClient, ProjectVisibility, ProjectMemberRole, SharePermission, WorkspaceMemberRole } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import pg from 'pg';
@@ -21,6 +22,8 @@ async function main() {
await prisma.shareLink.deleteMany();
await prisma.projectMember.deleteMany();
await prisma.project.deleteMany();
await prisma.workspaceMember.deleteMany();
await prisma.workspace.deleteMany();
await prisma.session.deleteMany();
await prisma.account.deleteMany();
await prisma.verificationToken.deleteMany();
@@ -57,7 +60,39 @@ async function main() {
console.log('✓ Created 3 demo users');
// Create projects
// Create workspaces
const mainWorkspace = await prisma.workspace.create({
data: {
name: 'OpenFrame Studio',
slug: 'openframe-studio',
description: 'Main workspace for all video production projects',
ownerId: demoUser.id,
members: {
create: [
{ userId: collaborator.id, role: WorkspaceMemberRole.ADMIN },
{ userId: reviewer.id, role: WorkspaceMemberRole.COMMENTATOR },
],
},
},
});
const clientWorkspace = await prisma.workspace.create({
data: {
name: 'XYZ Corp',
slug: 'xyz-corp',
description: 'Client workspace for XYZ Corporation projects',
ownerId: collaborator.id,
members: {
create: [
{ userId: demoUser.id, role: WorkspaceMemberRole.ADMIN },
],
},
},
});
console.log('✓ Created 2 workspaces with members');
// Create projects (linked to workspace)
const techProject = await prisma.project.create({
data: {
name: 'Tech Review Series',
@@ -65,10 +100,11 @@ async function main() {
slug: 'tech-review-series',
visibility: ProjectVisibility.PRIVATE,
ownerId: demoUser.id,
workspaceId: mainWorkspace.id,
members: {
create: [
{ userId: collaborator.id, role: ProjectMemberRole.EDITOR },
{ userId: reviewer.id, role: ProjectMemberRole.VIEWER },
{ userId: collaborator.id, role: ProjectMemberRole.ADMIN },
{ userId: reviewer.id, role: ProjectMemberRole.COMMENTATOR },
],
},
},
@@ -81,6 +117,7 @@ async function main() {
slug: 'programming-tutorials',
visibility: ProjectVisibility.INVITE,
ownerId: demoUser.id,
workspaceId: mainWorkspace.id,
},
});
@@ -91,6 +128,7 @@ async function main() {
slug: 'xyz-corp-promo',
visibility: ProjectVisibility.PRIVATE,
ownerId: collaborator.id,
workspaceId: clientWorkspace.id,
members: {
create: [{ userId: demoUser.id, role: ProjectMemberRole.ADMIN }],
},
@@ -273,8 +311,8 @@ async function main() {
console.log('\n✅ Seeding complete!\n');
console.log('Demo accounts:');
console.log(' - [email protected] (Owner)');
console.log(' - [email protected] (Editor)');
console.log(' - [email protected] (Viewer)');
console.log(' - [email protected] (Admin)');
console.log(' - [email protected] (Commentator)');
}
main()