import { Injectable, NotFoundException } from "@nestjs/common"; import { Prisma, UserLayout } from "@prisma/client"; import { PrismaService } from "../prisma/prisma.service"; import type { CreateLayoutDto, UpdateLayoutDto } from "./dto"; /** * Service for managing user layouts */ @Injectable() export class LayoutsService { constructor(private readonly prisma: PrismaService) {} /** * Get all layouts for a user */ async findAll(workspaceId: string, userId: string): Promise { return this.prisma.userLayout.findMany({ where: { workspaceId, userId, }, orderBy: { isDefault: "desc", createdAt: "desc", }, }); } /** * Get the default layout for a user */ async findDefault(workspaceId: string, userId: string): Promise { const layout = await this.prisma.userLayout.findFirst({ where: { workspaceId, userId, isDefault: true, }, }); // If no default layout exists, return the most recently created one if (!layout) { const recentLayout = await this.prisma.userLayout.findFirst({ where: { workspaceId, userId, }, orderBy: { createdAt: "desc", }, }); if (!recentLayout) { throw new NotFoundException(`No layouts found for this user`); } return recentLayout; } return layout; } /** * Get a single layout by ID */ async findOne(id: string, workspaceId: string, userId: string): Promise { const layout = await this.prisma.userLayout.findUnique({ where: { id, workspaceId, userId, }, }); if (!layout) { throw new NotFoundException(`Layout with ID ${id} not found`); } return layout; } /** * Create a new layout */ async create( workspaceId: string, userId: string, createLayoutDto: CreateLayoutDto ): Promise { // Use transaction to ensure atomicity when setting default return this.prisma.$transaction(async (tx) => { // If setting as default, unset other defaults first if (createLayoutDto.isDefault) { await tx.userLayout.updateMany({ where: { workspaceId, userId, isDefault: true, }, data: { isDefault: false, }, }); } return tx.userLayout.create({ data: { name: createLayoutDto.name, workspaceId, userId, isDefault: createLayoutDto.isDefault ?? false, layout: createLayoutDto.layout as unknown as Prisma.InputJsonValue, }, }); }); } /** * Update a layout */ async update( id: string, workspaceId: string, userId: string, updateLayoutDto: UpdateLayoutDto ): Promise { // Use transaction to ensure atomicity when setting default return this.prisma.$transaction(async (tx) => { // Verify layout exists const existingLayout = await tx.userLayout.findUnique({ where: { id, workspaceId, userId }, }); if (!existingLayout) { throw new NotFoundException(`Layout with ID ${id} not found`); } // If setting as default, unset other defaults first if (updateLayoutDto.isDefault === true) { await tx.userLayout.updateMany({ where: { workspaceId, userId, id: { not: id }, isDefault: true, }, data: { isDefault: false, }, }); } // Build update data, handling layout field separately const updateData: Prisma.UserLayoutUpdateInput = {}; if (updateLayoutDto.name !== undefined) updateData.name = updateLayoutDto.name; if (updateLayoutDto.isDefault !== undefined) updateData.isDefault = updateLayoutDto.isDefault; if (updateLayoutDto.layout !== undefined) { updateData.layout = updateLayoutDto.layout as unknown as Prisma.InputJsonValue; } return tx.userLayout.update({ where: { id, workspaceId, userId, }, data: updateData, }); }); } /** * Delete a layout */ async remove(id: string, workspaceId: string, userId: string): Promise { // Verify layout exists const layout = await this.prisma.userLayout.findUnique({ where: { id, workspaceId, userId }, }); if (!layout) { throw new NotFoundException(`Layout with ID ${id} not found`); } await this.prisma.userLayout.delete({ where: { id, workspaceId, userId, }, }); } }