Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Fixes CI pipeline failures caused by missing Prisma Client generation and TypeScript type safety issues. Added Prisma generation step to CI pipeline, installed missing type dependencies, and resolved 40+ exactOptionalPropertyTypes violations across service layer. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
243 lines
6.2 KiB
TypeScript
243 lines
6.2 KiB
TypeScript
import { Injectable, NotFoundException } from "@nestjs/common";
|
|
import { Prisma } from "@prisma/client";
|
|
import { PrismaService } from "../prisma/prisma.service";
|
|
import { ActivityService } from "../activity/activity.service";
|
|
import { ProjectStatus } from "@prisma/client";
|
|
import type { CreateProjectDto, UpdateProjectDto, QueryProjectsDto } from "./dto";
|
|
|
|
/**
|
|
* Service for managing projects
|
|
*/
|
|
@Injectable()
|
|
export class ProjectsService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly activityService: ActivityService
|
|
) {}
|
|
|
|
/**
|
|
* Create a new project
|
|
*/
|
|
async create(workspaceId: string, userId: string, createProjectDto: CreateProjectDto) {
|
|
const data: Prisma.ProjectCreateInput = {
|
|
name: createProjectDto.name,
|
|
description: createProjectDto.description ?? null,
|
|
color: createProjectDto.color ?? null,
|
|
startDate: createProjectDto.startDate ?? null,
|
|
endDate: createProjectDto.endDate ?? null,
|
|
workspace: { connect: { id: workspaceId } },
|
|
creator: { connect: { id: userId } },
|
|
status: createProjectDto.status ?? ProjectStatus.PLANNING,
|
|
metadata: createProjectDto.metadata
|
|
? (createProjectDto.metadata as unknown as Prisma.InputJsonValue)
|
|
: {},
|
|
};
|
|
|
|
const project = await this.prisma.project.create({
|
|
data,
|
|
include: {
|
|
creator: {
|
|
select: { id: true, name: true, email: true },
|
|
},
|
|
_count: {
|
|
select: { tasks: true, events: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
// Log activity
|
|
await this.activityService.logProjectCreated(workspaceId, userId, project.id, {
|
|
name: project.name,
|
|
});
|
|
|
|
return project;
|
|
}
|
|
|
|
/**
|
|
* Get paginated projects with filters
|
|
*/
|
|
async findAll(query: QueryProjectsDto) {
|
|
const page = query.page ?? 1;
|
|
const limit = query.limit ?? 50;
|
|
const skip = (page - 1) * limit;
|
|
|
|
// Build where clause
|
|
const where: Prisma.ProjectWhereInput = query.workspaceId
|
|
? {
|
|
workspaceId: query.workspaceId,
|
|
}
|
|
: {};
|
|
|
|
if (query.status) {
|
|
where.status = query.status;
|
|
}
|
|
|
|
if (query.startDateFrom || query.startDateTo) {
|
|
where.startDate = {};
|
|
if (query.startDateFrom) {
|
|
where.startDate.gte = query.startDateFrom;
|
|
}
|
|
if (query.startDateTo) {
|
|
where.startDate.lte = query.startDateTo;
|
|
}
|
|
}
|
|
|
|
// Execute queries in parallel
|
|
const [data, total] = await Promise.all([
|
|
this.prisma.project.findMany({
|
|
where,
|
|
include: {
|
|
creator: {
|
|
select: { id: true, name: true, email: true },
|
|
},
|
|
_count: {
|
|
select: { tasks: true, events: true },
|
|
},
|
|
},
|
|
orderBy: {
|
|
createdAt: "desc",
|
|
},
|
|
skip,
|
|
take: limit,
|
|
}),
|
|
this.prisma.project.count({ where }),
|
|
]);
|
|
|
|
return {
|
|
data,
|
|
meta: {
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(total / limit),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get a single project by ID
|
|
*/
|
|
async findOne(id: string, workspaceId: string) {
|
|
const project = await this.prisma.project.findUnique({
|
|
where: {
|
|
id,
|
|
workspaceId,
|
|
},
|
|
include: {
|
|
creator: {
|
|
select: { id: true, name: true, email: true },
|
|
},
|
|
tasks: {
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
status: true,
|
|
priority: true,
|
|
dueDate: true,
|
|
},
|
|
orderBy: { sortOrder: "asc" },
|
|
},
|
|
events: {
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
startTime: true,
|
|
endTime: true,
|
|
},
|
|
orderBy: { startTime: "asc" },
|
|
},
|
|
_count: {
|
|
select: { tasks: true, events: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
throw new NotFoundException(`Project with ID ${id} not found`);
|
|
}
|
|
|
|
return project;
|
|
}
|
|
|
|
/**
|
|
* Update a project
|
|
*/
|
|
async update(
|
|
id: string,
|
|
workspaceId: string,
|
|
userId: string,
|
|
updateProjectDto: UpdateProjectDto
|
|
) {
|
|
// Verify project exists
|
|
const existingProject = await this.prisma.project.findUnique({
|
|
where: { id, workspaceId },
|
|
});
|
|
|
|
if (!existingProject) {
|
|
throw new NotFoundException(`Project with ID ${id} not found`);
|
|
}
|
|
|
|
// Build update data, only including defined fields
|
|
const updateData: Prisma.ProjectUpdateInput = {};
|
|
if (updateProjectDto.name !== undefined) updateData.name = updateProjectDto.name;
|
|
if (updateProjectDto.description !== undefined)
|
|
updateData.description = updateProjectDto.description;
|
|
if (updateProjectDto.status !== undefined) updateData.status = updateProjectDto.status;
|
|
if (updateProjectDto.startDate !== undefined) updateData.startDate = updateProjectDto.startDate;
|
|
if (updateProjectDto.endDate !== undefined) updateData.endDate = updateProjectDto.endDate;
|
|
if (updateProjectDto.color !== undefined) updateData.color = updateProjectDto.color;
|
|
if (updateProjectDto.metadata !== undefined) {
|
|
updateData.metadata = updateProjectDto.metadata as unknown as Prisma.InputJsonValue;
|
|
}
|
|
|
|
const project = await this.prisma.project.update({
|
|
where: {
|
|
id,
|
|
workspaceId,
|
|
},
|
|
data: updateData,
|
|
include: {
|
|
creator: {
|
|
select: { id: true, name: true, email: true },
|
|
},
|
|
_count: {
|
|
select: { tasks: true, events: true },
|
|
},
|
|
},
|
|
});
|
|
|
|
// Log activity
|
|
await this.activityService.logProjectUpdated(workspaceId, userId, id, {
|
|
changes: updateProjectDto as Prisma.JsonValue,
|
|
});
|
|
|
|
return project;
|
|
}
|
|
|
|
/**
|
|
* Delete a project
|
|
*/
|
|
async remove(id: string, workspaceId: string, userId: string) {
|
|
// Verify project exists
|
|
const project = await this.prisma.project.findUnique({
|
|
where: { id, workspaceId },
|
|
});
|
|
|
|
if (!project) {
|
|
throw new NotFoundException(`Project with ID ${id} not found`);
|
|
}
|
|
|
|
await this.prisma.project.delete({
|
|
where: {
|
|
id,
|
|
workspaceId,
|
|
},
|
|
});
|
|
|
|
// Log activity
|
|
await this.activityService.logProjectDeleted(workspaceId, userId, id, {
|
|
name: project.name,
|
|
});
|
|
}
|
|
}
|