feat: private forum groups - Group/GroupMember models, group CRUD API, invite codes, private threads, groups UI pages

This commit is contained in:
root
2026-06-15 12:25:52 +02:00
parent 947fe3b66d
commit 64ae34e9c9
10 changed files with 1545 additions and 6 deletions
+46 -3
View File
@@ -14,8 +14,28 @@ export async function GET(request: NextRequest) {
where.categoryId = categoryId;
}
// For group-scoped visibility: if user is authenticated, find which group IDs
// they are a member of, so we can include public threads (groupId: null) plus
// private threads where they are a member.
const auth = await requireAuth(request);
let userGroupIds: string[] = [];
if (auth) {
const memberships = await prisma.groupMember.findMany({
where: { userId: auth.userId },
select: { groupId: true },
});
userGroupIds = memberships.map((m) => m.groupId);
}
const threads = await prisma.forumThread.findMany({
where,
where: {
...where,
OR: [
{ groupId: null },
{ groupId: { in: userGroupIds } },
],
},
include: {
author: {
select: { id: true, name: true, isPremium: true, isPro: true },
@@ -23,6 +43,9 @@ export async function GET(request: NextRequest) {
category: {
select: { id: true, name: true, icon: true },
},
group: {
select: { id: true, name: true },
},
_count: {
select: { posts: true },
},
@@ -65,7 +88,7 @@ export async function POST(request: NextRequest) {
);
}
const { title, content, categoryId } = await request.json();
const { title, content, categoryId, groupId } = await request.json();
if (!title || !content || !categoryId) {
return NextResponse.json(
@@ -86,13 +109,30 @@ export async function POST(request: NextRequest) {
);
}
// If groupId is provided, verify user is a member of that group
if (groupId) {
const membership = await prisma.groupMember.findUnique({
where: {
groupId_userId: { groupId, userId: user.id },
},
});
if (!membership) {
return NextResponse.json(
{ error: "You are not a member of this group" },
{ status: 403 }
);
}
}
const thread = await prisma.forumThread.create({
data: {
title,
content,
categoryId,
authorId: user.id,
shariahStatus: "pending",
groupId: groupId || null,
shariahStatus: groupId ? "private" : "pending",
},
include: {
author: {
@@ -101,6 +141,9 @@ export async function POST(request: NextRequest) {
category: {
select: { id: true, name: true, icon: true },
},
group: {
select: { id: true, name: true },
},
_count: {
select: { posts: true },
},