Files
stack/apps/api/src/layouts/layouts.service.ts
Jason Woltje c221b63d14
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix: Resolve CI typecheck failures and improve type safety
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>
2026-01-30 20:39:03 -06:00

185 lines
4.5 KiB
TypeScript

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: {
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) {
// 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) {
// 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,
},
});
}
}