Refactor types service into modular services and repositories

This commit is contained in:
MatthieuTD
2025-09-25 16:15:37 +02:00
parent 2ce164784f
commit b10a6baf47
21 changed files with 1533 additions and 589 deletions

View File

@@ -0,0 +1,7 @@
export const CUSTOM_FIELD_SELECT = {
id: true,
name: true,
type: true,
required: true,
options: true,
} as const;

View File

@@ -0,0 +1,55 @@
import { ModelTypeMapper } from './model-type.mapper';
describe('ModelTypeMapper', () => {
it('should map component create input', () => {
const dto = {
name: 'Comp',
description: 'Desc',
customFields: [{ name: 'Field', type: 'string', required: false, options: [] }],
} as any;
const input = ModelTypeMapper.toComponentCreateInput(dto, 'code');
expect(input).toMatchObject({
name: 'Comp',
code: 'code',
description: 'Desc',
notes: 'Desc',
});
expect(input.customFields?.create?.[0]).toMatchObject({ name: 'Field' });
});
it('should map piece model type to DTO shape', () => {
const mapped = ModelTypeMapper.mapPieceModelType({
id: '1',
name: 'Piece',
pieceCustomFields: [{ id: 'cf' }],
pieceModels: [{ id: 'model' }],
pieceRequirements: [{ id: 'req' }],
pieces: [{ id: 'piece' }],
});
expect(mapped).toMatchObject({
id: '1',
customFields: [{ id: 'cf' }],
models: [{ id: 'model' }],
pieceRequirements: [{ id: 'req' }],
pieces: [{ id: 'piece' }],
});
});
it('should map piece update input', () => {
const input = ModelTypeMapper.toPieceUpdateInput({
name: 'New',
description: 'D',
customFields: [],
} as any);
expect(input).toMatchObject({
name: 'New',
description: 'D',
notes: 'D',
});
expect(input.pieceCustomFields).toBeUndefined();
});
});

View File

@@ -0,0 +1,170 @@
import { Prisma } from '@prisma/client';
import {
CreateTypeComposantDto,
CreateTypePieceDto,
UpdateTypeComposantDto,
UpdateTypePieceDto,
} from '../../shared/dto/type.dto';
import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant';
export const COMPONENT_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
customFields: { select: CUSTOM_FIELD_SELECT },
composants: true,
models: true,
};
export const PIECE_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
pieceCustomFields: { select: CUSTOM_FIELD_SELECT },
pieceModels: true,
pieceRequirements: true,
pieces: true,
};
type ModelTypeCreateWithoutCategory = Omit<
Prisma.ModelTypeCreateInput,
'category'
>;
export class ModelTypeMapper {
static toComponentCreateInput(
dto: CreateTypeComposantDto,
code: string,
): ModelTypeCreateWithoutCategory {
const { customFields, description, name } = dto;
return {
name,
code,
description: description ?? null,
notes: description ?? null,
customFields: customFields
? {
create: customFields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options,
})),
}
: undefined,
};
}
static toComponentUpdateInput(
dto: UpdateTypeComposantDto,
): Prisma.ModelTypeUpdateInput {
const { customFields, description, name } = dto;
const data: Prisma.ModelTypeUpdateInput = {};
if (name !== undefined) {
data.name = name;
}
if (description !== undefined) {
data.description = description;
data.notes = description;
}
if (customFields !== undefined) {
data.customFields = undefined;
}
return data;
}
static toPieceCreateInput(
dto: CreateTypePieceDto,
code: string,
): ModelTypeCreateWithoutCategory {
const { customFields, description, name } = dto;
return {
name,
code,
description: description ?? null,
notes: description ?? null,
pieceCustomFields: customFields
? {
create: customFields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options,
})),
}
: undefined,
};
}
static toPieceUpdateInput(dto: UpdateTypePieceDto): Prisma.ModelTypeUpdateInput {
const { customFields, description, name } = dto;
const data: Prisma.ModelTypeUpdateInput = {};
if (name !== undefined) {
data.name = name;
}
if (description !== undefined) {
data.description = description;
data.notes = description;
}
if (customFields !== undefined) {
data.pieceCustomFields = undefined;
}
return data;
}
static mapPieceModelType(modelType: any) {
if (!modelType) {
return null;
}
const {
pieceCustomFields,
pieceModels,
pieceRequirements,
pieces,
...rest
} = modelType;
return {
...rest,
customFields: pieceCustomFields ?? [],
models: pieceModels ?? [],
pieceRequirements: pieceRequirements ?? [],
pieces: pieces ?? [],
};
}
static mapComponentCustomFieldInputs(
fields?: CreateTypeComposantDto['customFields'],
) {
if (!fields || fields.length === 0) {
return [];
}
return fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options,
}));
}
static mapPieceCustomFieldInputs(
fields?: CreateTypePieceDto['customFields'],
) {
if (!fields || fields.length === 0) {
return [];
}
return fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options,
}));
}
}

View File

@@ -0,0 +1,83 @@
import { TypeMachineMapper } from './type-machine.mapper';
const baseDto = {
name: 'Machine A',
description: 'Desc',
customFields: [
{ name: 'Field', type: 'string', required: true, options: ['a'] },
],
componentRequirements: [
{
label: 'Comp',
minCount: 2,
maxCount: 4,
required: true,
allowNewModels: false,
typeComposantId: 'comp-id',
},
],
pieceRequirements: [
{
label: 'Piece',
minCount: 0,
maxCount: 2,
required: false,
allowNewModels: true,
typePieceId: 'piece-id',
},
],
};
describe('TypeMachineMapper', () => {
it('should map create input with nested relations', () => {
const input = TypeMachineMapper.toCreateInput(baseDto as any);
expect(input.customFields?.create).toHaveLength(1);
expect(input.componentRequirements?.create?.[0]).toMatchObject({
label: 'Comp',
minCount: 2,
maxCount: 4,
required: true,
allowNewModels: false,
});
expect(input.pieceRequirements?.create?.[0]).toMatchObject({
label: 'Piece',
minCount: 0,
maxCount: 2,
required: false,
allowNewModels: true,
});
});
it('should map custom field inputs for create many', () => {
const result = TypeMachineMapper.mapCustomFieldInputs(baseDto.customFields as any);
expect(result).toEqual([
{
name: 'Field',
type: 'string',
required: true,
options: ['a'],
},
]);
});
it('should map requirements for bulk insert', () => {
const component = TypeMachineMapper.mapComponentRequirementInputs(
baseDto.componentRequirements as any,
);
const piece = TypeMachineMapper.mapPieceRequirementInputs(
baseDto.pieceRequirements as any,
);
expect(component[0]).toMatchObject({
typeComposantId: 'comp-id',
minCount: 2,
maxCount: 4,
});
expect(piece[0]).toMatchObject({
typePieceId: 'piece-id',
minCount: 0,
maxCount: 2,
});
});
});

View File

@@ -0,0 +1,188 @@
import { Prisma } from '@prisma/client';
import {
CreateTypeMachineDto,
UpdateTypeMachineDto,
} from '../../shared/dto/type.dto';
import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant';
type RequirementDto = {
label?: string | null;
minCount?: number | null;
maxCount?: number | null;
required?: boolean | null;
allowNewModels?: boolean | null;
typeComposantId?: string;
typePieceId?: string;
};
export const TYPE_MACHINE_DEFAULT_INCLUDE: Prisma.TypeMachineInclude = {
customFields: { select: CUSTOM_FIELD_SELECT },
componentRequirements: {
include: { typeComposant: true },
},
pieceRequirements: {
include: { typePiece: true },
},
};
export const TYPE_MACHINE_WITH_MACHINES_INCLUDE: Prisma.TypeMachineInclude = {
...TYPE_MACHINE_DEFAULT_INCLUDE,
machines: true,
};
export class TypeMachineMapper {
static toCreateInput(
dto: CreateTypeMachineDto,
): Prisma.TypeMachineCreateInput {
const { customFields, componentRequirements, pieceRequirements, ...data } =
dto;
return {
...data,
customFields: this.mapCustomFields(customFields),
componentRequirements: this.mapComponentRequirements(componentRequirements),
pieceRequirements: this.mapPieceRequirements(pieceRequirements),
};
}
static toUpdateData(dto: UpdateTypeMachineDto): Prisma.TypeMachineUpdateInput {
const { customFields, componentRequirements, pieceRequirements, ...data } =
dto;
const payload: Prisma.TypeMachineUpdateInput = { ...data };
if (customFields !== undefined) {
payload.customFields = undefined;
}
if (componentRequirements !== undefined) {
payload.componentRequirements = undefined;
}
if (pieceRequirements !== undefined) {
payload.pieceRequirements = undefined;
}
return payload;
}
static mapCustomFields(
fields?: CreateTypeMachineDto['customFields'],
): Prisma.CustomFieldCreateNestedManyWithoutTypeMachineInput | undefined {
if (!fields || fields.length === 0) {
return undefined;
}
return {
create: fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options,
})),
};
}
static mapCustomFieldInputs(
fields?: CreateTypeMachineDto['customFields'],
) {
if (!fields || fields.length === 0) {
return [];
}
return fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options,
}));
}
static mapComponentRequirements(
requirements?: RequirementDto[] | null,
): Prisma.TypeMachineComponentRequirementCreateNestedManyWithoutTypeMachineInput | undefined {
if (!requirements || requirements.length === 0) {
return undefined;
}
return {
create: requirements.map((requirement) => ({
label: requirement.label ?? null,
minCount: requirement.minCount ?? 1,
maxCount: requirement.maxCount ?? null,
required: requirement.required ?? true,
allowNewModels: requirement.allowNewModels ?? true,
typeComposant: requirement.typeComposantId
? {
connect: { id: requirement.typeComposantId },
}
: (() => {
throw new Error(
'typeComposantId est requis pour créer une contrainte de composant.',
);
})(),
})),
};
}
static mapComponentRequirementInputs(
requirements?: RequirementDto[] | null,
) {
if (!requirements || requirements.length === 0) {
return [];
}
return requirements.map((requirement) => ({
label: requirement.label ?? null,
minCount: requirement.minCount ?? 1,
maxCount: requirement.maxCount ?? null,
required: requirement.required ?? true,
allowNewModels: requirement.allowNewModels ?? true,
typeComposantId: requirement.typeComposantId!,
}));
}
static mapPieceRequirements(
requirements?: RequirementDto[] | null,
): Prisma.TypeMachinePieceRequirementCreateNestedManyWithoutTypeMachineInput | undefined {
if (!requirements || requirements.length === 0) {
return undefined;
}
return {
create: requirements.map((requirement) => ({
label: requirement.label ?? null,
minCount: requirement.minCount ?? 0,
maxCount: requirement.maxCount ?? null,
required: requirement.required ?? false,
allowNewModels: requirement.allowNewModels ?? true,
typePiece: requirement.typePieceId
? {
connect: { id: requirement.typePieceId },
}
: (() => {
throw new Error(
'typePieceId est requis pour créer une contrainte de pièce.',
);
})(),
})),
};
}
static mapPieceRequirementInputs(
requirements?: RequirementDto[] | null,
) {
if (!requirements || requirements.length === 0) {
return [];
}
return requirements.map((requirement) => ({
label: requirement.label ?? null,
minCount: requirement.minCount ?? 0,
maxCount: requirement.maxCount ?? null,
required: requirement.required ?? false,
allowNewModels: requirement.allowNewModels ?? true,
typePieceId: requirement.typePieceId!,
}));
}
}

View File

@@ -0,0 +1,58 @@
import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class ComposantModelsRepository {
constructor(private readonly prisma: PrismaService) {}
private get client(): PrismaClient {
return this.prisma;
}
async create(
data: Prisma.ComposantModelCreateInput,
include?: Prisma.ComposantModelInclude,
) {
return this.client.composantModel.create({
data,
include,
});
}
async findAll(
typeComposantId?: string,
include?: Prisma.ComposantModelInclude,
) {
return this.client.composantModel.findMany({
where: typeComposantId ? { typeComposantId } : undefined,
include,
orderBy: { name: 'asc' },
});
}
async findOne(id: string, include?: Prisma.ComposantModelInclude) {
return this.client.composantModel.findUnique({
where: { id },
include,
});
}
async update(
id: string,
data: Prisma.ComposantModelUpdateInput,
include?: Prisma.ComposantModelInclude,
) {
return this.client.composantModel.update({
where: { id },
data,
include,
});
}
async delete(id: string) {
return this.client.composantModel.delete({
where: { id },
});
}
}

View File

@@ -0,0 +1,71 @@
import { ModelTypesRepository } from './model-types.repository';
describe('ModelTypesRepository', () => {
const prismaMock = {
modelType: {
findUnique: jest.fn(),
create: jest.fn(),
findMany: jest.fn(),
findFirst: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
customField: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
} as any;
const repository = new ModelTypesRepository(prismaMock);
beforeEach(() => {
jest.clearAllMocks();
});
it('should generate unique code when conflicts exist', async () => {
prismaMock.modelType.findUnique
.mockResolvedValueOnce({ id: 'existing' })
.mockResolvedValueOnce(null);
const code = await repository.generateUniqueCode('My Type');
expect(code).toBe('my-type-1');
expect(prismaMock.modelType.findUnique).toHaveBeenCalledTimes(2);
});
it('should create component type custom fields with relation id', async () => {
await repository.createComponentTypeCustomFields('comp-id', [
{ name: 'Field', type: 'string', required: true, options: [] },
]);
expect(prismaMock.customField.createMany).toHaveBeenCalledWith({
data: [
{
name: 'Field',
type: 'string',
required: true,
options: [],
typeComposantId: 'comp-id',
},
],
});
});
it('should create piece type custom fields with relation id', async () => {
await repository.createPieceTypeCustomFields('piece-id', [
{ name: 'Field', type: 'string', required: false, options: [] },
]);
expect(prismaMock.customField.createMany).toHaveBeenCalledWith({
data: [
{
name: 'Field',
type: 'string',
required: false,
options: [],
typePieceId: 'piece-id',
},
],
});
});
});

View File

@@ -0,0 +1,172 @@
import { Injectable } from '@nestjs/common';
import { ModelCategory, Prisma, PrismaClient } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { slugifyName } from '../utils/slug.util';
type CustomFieldInput = Omit<
Prisma.CustomFieldCreateManyInput,
'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId'
>;
type PieceCustomFieldInput = Omit<
Prisma.CustomFieldCreateManyInput,
'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId'
>;
type ModelTypeCreateData = Omit<Prisma.ModelTypeCreateInput, 'category'>;
@Injectable()
export class ModelTypesRepository {
constructor(private readonly prisma: PrismaService) {}
private get client(): PrismaClient {
return this.prisma;
}
async generateUniqueCode(name: string): Promise<string> {
const base = slugifyName(name) || 'type';
let candidate = base;
let suffix = 1;
while (await this.client.modelType.findUnique({ where: { code: candidate } })) {
candidate = `${base}-${suffix++}`;
}
return candidate;
}
async createComponentType(
data: ModelTypeCreateData,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.create({
data: {
...data,
category: ModelCategory.COMPONENT,
},
include,
});
}
async findComponentTypes(include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findMany({
where: { category: ModelCategory.COMPONENT },
include,
});
}
async findComponentType(id: string, include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findFirst({
where: { id, category: ModelCategory.COMPONENT },
include,
});
}
async updateComponentType(
id: string,
data: Prisma.ModelTypeUpdateInput,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.update({
where: { id },
data,
include,
});
}
async deleteComponentType(id: string) {
return this.client.modelType.delete({
where: { id },
});
}
async deleteComponentTypeCustomFields(typeComposantId: string) {
await this.client.customField.deleteMany({
where: { typeComposantId },
});
}
async createComponentTypeCustomFields(
typeComposantId: string,
customFields: CustomFieldInput[],
) {
if (!customFields.length) {
return;
}
await this.client.customField.createMany({
data: customFields.map((field) => ({
...field,
typeComposantId,
})),
});
}
async createPieceType(
data: ModelTypeCreateData,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.create({
data: {
...data,
category: ModelCategory.PIECE,
},
include,
});
}
async findPieceTypes(include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findMany({
where: { category: ModelCategory.PIECE },
include,
orderBy: { name: 'asc' },
});
}
async findPieceType(id: string, include?: Prisma.ModelTypeInclude) {
return this.client.modelType.findFirst({
where: { id, category: ModelCategory.PIECE },
include,
});
}
async updatePieceType(
id: string,
data: Prisma.ModelTypeUpdateInput,
include?: Prisma.ModelTypeInclude,
) {
return this.client.modelType.update({
where: { id },
data,
include,
});
}
async deletePieceType(id: string) {
return this.client.modelType.delete({
where: { id },
});
}
async deletePieceTypeCustomFields(typePieceId: string) {
await this.client.customField.deleteMany({
where: { typePieceId },
});
}
async createPieceTypeCustomFields(
typePieceId: string,
customFields: PieceCustomFieldInput[],
) {
if (!customFields.length) {
return;
}
await this.client.customField.createMany({
data: customFields.map((field) => ({
...field,
typePieceId,
})),
});
}
}

View File

@@ -0,0 +1,55 @@
import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class PieceModelsRepository {
constructor(private readonly prisma: PrismaService) {}
private get client(): PrismaClient {
return this.prisma;
}
async create(
data: Prisma.PieceModelCreateInput,
include?: Prisma.PieceModelInclude,
) {
return this.client.pieceModel.create({
data,
include,
});
}
async findAll(typePieceId?: string, include?: Prisma.PieceModelInclude) {
return this.client.pieceModel.findMany({
where: typePieceId ? { typePieceId } : undefined,
include,
orderBy: { name: 'asc' },
});
}
async findOne(id: string, include?: Prisma.PieceModelInclude) {
return this.client.pieceModel.findUnique({
where: { id },
include,
});
}
async update(
id: string,
data: Prisma.PieceModelUpdateInput,
include?: Prisma.PieceModelInclude,
) {
return this.client.pieceModel.update({
where: { id },
data,
include,
});
}
async delete(id: string) {
return this.client.pieceModel.delete({
where: { id },
});
}
}

View File

@@ -0,0 +1,117 @@
import { TypeMachinesRepository } from './type-machines.repository';
describe('TypeMachinesRepository', () => {
const prismaMock = {
customField: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
typeMachineComponentRequirement: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
typeMachinePieceRequirement: {
deleteMany: jest.fn(),
createMany: jest.fn(),
},
machine: {
findMany: jest.fn().mockResolvedValue([{ id: '1', name: 'Machine' }]),
},
typeMachine: {
create: jest.fn(),
findMany: jest.fn(),
findUnique: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
} as any;
const repository = new TypeMachinesRepository(prismaMock);
beforeEach(() => {
jest.clearAllMocks();
});
it('should append typeMachineId when creating custom fields', async () => {
await repository.createCustomFields('machine-id', [
{ name: 'Field', type: 'string', required: true, options: [] },
]);
expect(prismaMock.customField.createMany).toHaveBeenCalledWith({
data: [
{
name: 'Field',
type: 'string',
required: true,
options: [],
typeMachineId: 'machine-id',
},
],
});
});
it('should append typeMachineId when creating component requirements', async () => {
await repository.createComponentRequirements('machine-id', [
{
label: 'Comp',
minCount: 1,
maxCount: 2,
required: true,
allowNewModels: true,
typeComposantId: 'comp',
},
]);
expect(
prismaMock.typeMachineComponentRequirement.createMany,
).toHaveBeenCalledWith({
data: [
{
label: 'Comp',
minCount: 1,
maxCount: 2,
required: true,
allowNewModels: true,
typeComposantId: 'comp',
typeMachineId: 'machine-id',
},
],
});
});
it('should append typeMachineId when creating piece requirements', async () => {
await repository.createPieceRequirements('machine-id', [
{
label: 'Piece',
minCount: 0,
maxCount: 1,
required: false,
allowNewModels: true,
typePieceId: 'piece',
},
]);
expect(prismaMock.typeMachinePieceRequirement.createMany).toHaveBeenCalledWith({
data: [
{
label: 'Piece',
minCount: 0,
maxCount: 1,
required: false,
allowNewModels: true,
typePieceId: 'piece',
typeMachineId: 'machine-id',
},
],
});
});
it('should find machines using type', async () => {
const result = await repository.findMachinesUsingType('machine-id');
expect(result).toEqual([{ id: '1', name: 'Machine' }]);
expect(prismaMock.machine.findMany).toHaveBeenCalledWith({
where: { typeMachineId: 'machine-id' },
select: { id: true, name: true },
});
});
});

View File

@@ -0,0 +1,141 @@
import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
type CustomFieldInput = Omit<
Prisma.CustomFieldCreateManyInput,
'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId'
>;
type ComponentRequirementInput = Omit<
Prisma.TypeMachineComponentRequirementCreateManyInput,
'id' | 'typeMachineId'
>;
type PieceRequirementInput = Omit<
Prisma.TypeMachinePieceRequirementCreateManyInput,
'id' | 'typeMachineId'
>;
@Injectable()
export class TypeMachinesRepository {
constructor(private readonly prisma: PrismaService) {}
private get client(): PrismaClient {
return this.prisma;
}
async create(
data: Prisma.TypeMachineCreateInput,
include?: Prisma.TypeMachineInclude,
) {
return this.client.typeMachine.create({
data,
include,
});
}
async findAll(include?: Prisma.TypeMachineInclude) {
return this.client.typeMachine.findMany({
include,
});
}
async findOne(id: string, include?: Prisma.TypeMachineInclude) {
return this.client.typeMachine.findUnique({
where: { id },
include,
});
}
async update(
id: string,
data: Prisma.TypeMachineUpdateInput,
include?: Prisma.TypeMachineInclude,
) {
return this.client.typeMachine.update({
where: { id },
data,
include,
});
}
async delete(id: string) {
return this.client.typeMachine.delete({
where: { id },
});
}
async deleteCustomFields(typeMachineId: string) {
await this.client.customField.deleteMany({
where: { typeMachineId },
});
}
async createCustomFields(
typeMachineId: string,
customFields: CustomFieldInput[],
) {
if (!customFields.length) {
return;
}
await this.client.customField.createMany({
data: customFields.map((field) => ({
...field,
typeMachineId,
})),
});
}
async deleteComponentRequirements(typeMachineId: string) {
await this.client.typeMachineComponentRequirement.deleteMany({
where: { typeMachineId },
});
}
async createComponentRequirements(
typeMachineId: string,
requirements: ComponentRequirementInput[],
) {
if (!requirements.length) {
return;
}
await this.client.typeMachineComponentRequirement.createMany({
data: requirements.map((requirement) => ({
...requirement,
typeMachineId,
})),
});
}
async deletePieceRequirements(typeMachineId: string) {
await this.client.typeMachinePieceRequirement.deleteMany({
where: { typeMachineId },
});
}
async createPieceRequirements(
typeMachineId: string,
requirements: PieceRequirementInput[],
) {
if (!requirements.length) {
return;
}
await this.client.typeMachinePieceRequirement.createMany({
data: requirements.map((requirement) => ({
...requirement,
typeMachineId,
})),
});
}
async findMachinesUsingType(typeMachineId: string) {
return this.client.machine.findMany({
where: { typeMachineId },
select: { id: true, name: true },
});
}
}

View File

@@ -0,0 +1,9 @@
export function slugifyName(name: string): string {
return name
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-+/g, '-');
}