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: any = { ...createProjectDto, workspaceId, creatorId: userId, status: createProjectDto.status || ProjectStatus.PLANNING, metadata: createProjectDto.metadata || {}, }; 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: any = { 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`); } const project = await this.prisma.project.update({ where: { id, workspaceId, }, data: updateProjectDto as any, 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, }); } }