import { Injectable, NotFoundException } from "@nestjs/common"; import { Prisma } 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) { 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) { 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) { 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 ) { // 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: { ...createLayoutDto, workspaceId, userId, isDefault: createLayoutDto.isDefault || false, layout: (createLayoutDto.layout || []) as unknown as Prisma.JsonValue, } as any, }); }); } /** * Update a layout */ async update( id: string, workspaceId: string, userId: string, updateLayoutDto: UpdateLayoutDto ) { // 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, }, }); } return tx.userLayout.update({ where: { id, workspaceId, userId, }, data: updateLayoutDto as any, }); }); } /** * Delete a layout */ async remove(id: string, workspaceId: string, userId: string) { // 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, }, }); } }