Files
stack/apps/api/src/teams/teams.service.ts
Jason Woltje 0e6734bdae
All checks were successful
ci/woodpecker/push/api Pipeline was successful
feat(api): add team management module with CRUD endpoints (#564)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-02-28 18:24:09 +00:00

131 lines
3.2 KiB
TypeScript

import {
BadRequestException,
ConflictException,
Injectable,
NotFoundException,
} from "@nestjs/common";
import { TeamMemberRole } from "@prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import { CreateTeamDto } from "./dto/create-team.dto";
import { ManageTeamMemberDto } from "./dto/manage-team-member.dto";
@Injectable()
export class TeamsService {
constructor(private readonly prisma: PrismaService) {}
async create(workspaceId: string, createTeamDto: CreateTeamDto) {
return this.prisma.team.create({
data: {
workspaceId,
name: createTeamDto.name,
description: createTeamDto.description ?? null,
},
});
}
async findAll(workspaceId: string) {
return this.prisma.team.findMany({
where: { workspaceId },
include: {
_count: {
select: { members: true },
},
},
orderBy: { createdAt: "asc" },
});
}
async addMember(workspaceId: string, teamId: string, dto: ManageTeamMemberDto) {
await this.ensureTeamInWorkspace(workspaceId, teamId);
const workspaceMember = await this.prisma.workspaceMember.findUnique({
where: {
workspaceId_userId: {
workspaceId,
userId: dto.userId,
},
},
select: { userId: true },
});
if (!workspaceMember) {
throw new BadRequestException(
`User ${dto.userId} must be a workspace member before being added to a team`
);
}
const existingTeamMember = await this.prisma.teamMember.findUnique({
where: {
teamId_userId: {
teamId,
userId: dto.userId,
},
},
select: { userId: true },
});
if (existingTeamMember) {
throw new ConflictException(`User ${dto.userId} is already a member of team ${teamId}`);
}
return this.prisma.teamMember.create({
data: {
teamId,
userId: dto.userId,
role: dto.role ?? TeamMemberRole.MEMBER,
},
include: {
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
}
async removeMember(workspaceId: string, teamId: string, userId: string): Promise<void> {
await this.ensureTeamInWorkspace(workspaceId, teamId);
const result = await this.prisma.teamMember.deleteMany({
where: {
teamId,
userId,
},
});
if (result.count === 0) {
throw new NotFoundException(`User ${userId} is not a member of team ${teamId}`);
}
}
async remove(workspaceId: string, teamId: string): Promise<void> {
const result = await this.prisma.team.deleteMany({
where: {
id: teamId,
workspaceId,
},
});
if (result.count === 0) {
throw new NotFoundException(`Team with ID ${teamId} not found`);
}
}
private async ensureTeamInWorkspace(workspaceId: string, teamId: string): Promise<void> {
const team = await this.prisma.team.findFirst({
where: {
id: teamId,
workspaceId,
},
select: { id: true },
});
if (!team) {
throw new NotFoundException(`Team with ID ${teamId} not found`);
}
}
}