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>
185 lines
4.5 KiB
TypeScript
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,
|
|
},
|
|
});
|
|
}
|
|
}
|