feat(team): add team settings with edit, leave, and kick functionality

This commit is contained in:
2026-07-31 01:48:31 +02:00
parent e664cf3b96
commit de0eaee521
10 changed files with 579 additions and 9 deletions
@@ -0,0 +1,57 @@
import { useValidatedBody, useValidatedParams, z, zh } from 'h3-zod';
import prisma from '~~/lib/prisma';
export default defineEventHandler(async (event) => {
const user = await requireUserSession(event);
const userId = user.user.id;
const { huntId, teamId } = await useValidatedParams(
event,
z.object({
huntId: zh.numAsString,
teamId: zh.numAsString
})
);
const body = await useValidatedBody(
event,
z.object({
name: z.string().min(3).max(256).trim(),
description: z.string().max(256).trim().default(''),
password: z.string().min(1).max(256).trim()
})
);
// Only the team owner may edit the team
const team = await prisma.huntTeam.findFirst({
where: {
id: teamId,
huntId,
ownerId: userId
},
select: {
id: true
}
});
if (!team) {
throw createError({ status: 403, statusText: 'Not allowed!' });
}
return await prisma.huntTeam.update({
where: {
id: teamId
},
data: {
name: body.name,
description: body.description,
password: body.password
},
select: {
id: true,
name: true,
description: true,
password: true
}
});
});
@@ -0,0 +1,60 @@
import { useValidatedBody, useValidatedParams, z, zh } from 'h3-zod';
import prisma from '~~/lib/prisma';
export default defineEventHandler(async (event) => {
const user = await requireUserSession(event);
const userId = user.user.id;
const { huntId, teamId } = await useValidatedParams(
event,
z.object({
huntId: zh.numAsString,
teamId: zh.numAsString
})
);
const { userId: memberId } = await useValidatedBody(
event,
z.object({
userId: z.int().gt(0)
})
);
// Only the team owner may remove members
const team = await prisma.huntTeam.findFirst({
where: {
id: teamId,
huntId,
ownerId: userId
},
select: {
id: true
}
});
if (!team) {
throw createError({ status: 403, statusText: 'Not allowed!' });
}
if (memberId === userId) {
throw createError({
status: 400,
statusText: 'Owner cannot kick themselves!'
});
}
try {
await prisma.teamMember.delete({
where: {
memberId_teamId: {
memberId,
teamId
}
}
});
} catch (e) {
throw createError({ status: 404, statusText: 'Not found!', cause: e });
}
return { success: true };
});
@@ -0,0 +1,65 @@
import { useValidatedParams, z, zh } from 'h3-zod';
import prisma from '~~/lib/prisma';
export default defineEventHandler(async (event) => {
const user = await requireUserSession(event);
const userId = user.user.id;
const { huntId } = await useValidatedParams(
event,
z.object({
huntId: zh.numAsString
})
);
const team = await prisma.huntTeam.findFirst({
where: {
huntId,
OR: [
{
ownerId: userId
},
{
members: {
some: {
memberId: userId
}
}
}
]
},
select: {
id: true,
name: true,
description: true,
password: true,
ownerId: true,
createdAt: true,
owner: {
select: {
id: true,
name: true
}
},
members: {
select: {
member: {
select: {
id: true,
name: true
}
}
},
orderBy: {
createdAt: 'asc'
}
}
}
});
if (!team) {
throw createError({ status: 404, statusText: 'Not found!' });
}
return team;
});
@@ -0,0 +1,58 @@
import { useValidatedParams, z, zh } from 'h3-zod';
import prisma from '~~/lib/prisma';
export default defineEventHandler(async (event) => {
const user = await requireUserSession(event);
const userId = user.user.id;
const { huntId } = await useValidatedParams(
event,
z.object({
huntId: zh.numAsString
})
);
// Find the team the user is a member of (but not the owner)
const membership = await prisma.teamMember.findFirst({
where: {
memberId: userId,
team: {
huntId,
ownerId: {
not: userId
}
}
},
select: {
memberId: true,
teamId: true
}
});
if (!membership) {
// Either the user has no team in this hunt, or they are the owner.
// Owners cannot leave their own team (they must delete it instead).
const isOwner = await prisma.huntTeam.findFirst({
where: {
huntId,
ownerId: userId
},
select: {
id: true
}
});
throw createError({
status: isOwner ? 403 : 404,
statusText: isOwner ? 'Owner cannot leave the team!' : 'Not found!'
});
}
await prisma.teamMember.delete({
where: {
memberId_teamId: membership
}
});
return { success: true };
});