Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DtoRelationCanDisconnectOnUpdate annotation #25

Merged
merged 6 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ model Product {

/// @DtoRelationIncludeId
photoId String?
/// @DtoRelationCanDisconnectOnUpdate
photo Photo? @relation(fields: [photoId], references: [id])

/// @DtoEntityHidden
Expand Down Expand Up @@ -106,6 +107,7 @@ model Category {

model Company {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
/// @DtoRelationCanDisconnectOnUpdate
product Product[]

companyUserRoles CompanyUserRole[]
Expand Down
3 changes: 3 additions & 0 deletions src/generator/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const DTO_RELATION_CAN_CREATE_ON_UPDATE =
/@DtoRelationCanCreateOnUpdate/;
export const DTO_RELATION_CAN_CONNECT_ON_UPDATE =
/@DtoRelationCanConnectOnUpdate/;
export const DTO_RELATION_CAN_DISCONNECT_ON_UPDATE =
/@DtoRelationCanDisconnectOnUpdate/;
export const DTO_RELATION_MODIFIERS = [
DTO_RELATION_CAN_CREATE_ON_CREATE,
DTO_RELATION_CAN_CONNECT_ON_CREATE,
Expand All @@ -26,6 +28,7 @@ export const DTO_RELATION_MODIFIERS_ON_CREATE = [
export const DTO_RELATION_MODIFIERS_ON_UPDATE = [
DTO_RELATION_CAN_CREATE_ON_UPDATE,
DTO_RELATION_CAN_CONNECT_ON_UPDATE,
DTO_RELATION_CAN_DISCONNECT_ON_UPDATE,
];
export const DTO_TYPE_FULL_UPDATE = /@DtoTypeFullUpdate/;
export const DTO_CAST_TYPE = /@DtoCastType/;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DTO_CREATE_OPTIONAL,
DTO_RELATION_CAN_CONNECT_ON_CREATE,
DTO_RELATION_CAN_CREATE_ON_CREATE,
DTO_RELATION_CAN_DISCONNECT_ON_UPDATE,
DTO_RELATION_INCLUDE_ID,
DTO_RELATION_MODIFIERS_ON_CREATE,
DTO_RELATION_REQUIRED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'node:path';
import {
DTO_RELATION_CAN_CONNECT_ON_UPDATE,
DTO_RELATION_CAN_CREATE_ON_UPDATE,
DTO_RELATION_CAN_DISCONNECT_ON_UPDATE,
DTO_RELATION_INCLUDE_ID,
DTO_RELATION_MODIFIERS_ON_UPDATE,
DTO_TYPE_FULL_UPDATE,
Expand Down Expand Up @@ -91,6 +92,7 @@ export const computeUpdateDtoParams = ({
preAndSuffixClassName: templateHelpers.updateDtoName,
canCreateAnnotation: DTO_RELATION_CAN_CREATE_ON_UPDATE,
canConnectAnnotation: DTO_RELATION_CAN_CONNECT_ON_UPDATE,
canDisconnectAnnotation: DTO_RELATION_CAN_DISCONNECT_ON_UPDATE,
});

overrides.type = relationInputType.type;
Expand Down
108 changes: 107 additions & 1 deletion src/generator/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ interface GenerateRelationInputParam {
| TemplateHelpers['updateDtoName'];
canCreateAnnotation: RegExp;
canConnectAnnotation: RegExp;
canDisconnectAnnotation?: RegExp;
}
export const generateRelationInput = ({
field,
Expand All @@ -234,6 +235,7 @@ export const generateRelationInput = ({
preAndSuffixClassName,
canCreateAnnotation,
canConnectAnnotation,
canDisconnectAnnotation,
}: GenerateRelationInputParam) => {
const relationInputClassProps: Array<
Pick<ParsedField, 'name' | 'type' | 'apiProperties' | 'classValidators'>
Expand All @@ -246,7 +248,15 @@ export const generateRelationInput = ({

const createRelation = isAnnotatedWith(field, canCreateAnnotation);
const connectRelation = isAnnotatedWith(field, canConnectAnnotation);
const isRequired = !(createRelation && connectRelation);
const disconnectRelation = canDisconnectAnnotation
? isAnnotatedWith(field, canDisconnectAnnotation)
: undefined;
// should the validation require the relation field to exist
// this should only be true in cases where only one relation field is generated
// for multiple relaiton fields, e.g. create AND connect, each should be optional
const isRequired =
[createRelation, connectRelation, disconnectRelation].filter((v) => v)
.length === 1;

if (createRelation) {
const preAndPostfixedName = t.createDtoName(field.type);
Expand Down Expand Up @@ -355,6 +365,102 @@ export const generateRelationInput = ({
});
}

if (disconnectRelation) {
if (field.isRequired && !field.isList) {
throw new Error(
`The disconnect annotation is not supported for required field '${model.name}.${field.name}'`,
);
}

if (!field.isList) {
const decorators: {
apiProperties?: IApiProperty[];
classValidators?: IClassValidator[];
} = {};

if (t.config.classValidation) {
decorators.classValidators = parseClassValidators(
{ ...field, isRequired },
'Boolean',
);
concatUniqueIntoArray(
decorators.classValidators,
classValidators,
'name',
);
}

if (!t.config.noDependencies) {
decorators.apiProperties = parseApiProperty({ ...field, isRequired });
decorators.apiProperties.push({
name: 'type',
value: 'Boolean',
noEncapsulation: true,
});
}

relationInputClassProps.push({
name: 'disconnect',
type: 'boolean',
apiProperties: decorators.apiProperties,
classValidators: decorators.classValidators,
});
} else {
const preAndPostfixedName = t.connectDtoName(field.type);
apiExtraModels.push(preAndPostfixedName);
const modelToImportFrom = allModels.find(
({ name }) => name === field.type,
);

if (!modelToImportFrom)
throw new Error(
`related model '${field.type}' for '${model.name}.${field.name}' not found`,
);

imports.push({
from: slash(
`${getRelativePath(model.output.dto, modelToImportFrom.output.dto)}${
path.sep
}${t.connectDtoFilename(field.type)}`,
),
destruct: [preAndPostfixedName],
});

const decorators: {
apiProperties?: IApiProperty[];
classValidators?: IClassValidator[];
} = {};

if (t.config.classValidation) {
decorators.classValidators = parseClassValidators(
{ ...field, isRequired },
preAndPostfixedName,
);
concatUniqueIntoArray(
decorators.classValidators,
classValidators,
'name',
);
}

if (!t.config.noDependencies) {
decorators.apiProperties = parseApiProperty({ ...field, isRequired });
decorators.apiProperties.push({
name: 'type',
value: preAndPostfixedName,
noEncapsulation: true,
});
}

relationInputClassProps.push({
name: 'disconnect',
type: preAndPostfixedName,
apiProperties: decorators.apiProperties,
classValidators: decorators.classValidators,
});
}
}

if (!relationInputClassProps.length) {
throw new Error(
`Can not find relation input props for '${model.name}.${field.name}'`,
Expand Down