Skip to content

Commit 88018c0

Browse files
committed
feat(DEV-297): Integrated UnifiedField
1 parent 04f0873 commit 88018c0

File tree

12 files changed

+148
-68
lines changed

12 files changed

+148
-68
lines changed

src/commands/server/add-property.ts

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GluegunCommand } from 'gluegun';
22
import { join } from 'path';
33
import {
4-
ClassPropertyTypes,
4+
ClassPropertyTypes, IndentationText,
55
OptionalKind,
66
Project,
77
PropertyDeclarationStructure,
@@ -138,58 +138,124 @@ const NewCommand: GluegunCommand = {
138138
}
139139

140140
const type = ['any', 'bigint', 'boolean', 'never', 'null', 'number', 'string', 'symbol', 'undefined', 'unknown', 'void'].includes(propObj.type) ? propObj.type : pascalCase(propObj.type);
141+
142+
const description = `'${pascalCase(propObj.name)} of ${pascalCase(elementToEdit)}'`;
143+
// const typeString = `${type === 'Json'
144+
// ? 'JSON'
145+
// : `${propObj.enumRef
146+
// || (propObj.schema
147+
// ? propObj.schema
148+
// : (propObj.type === 'ObjectId'
149+
// ? propObj.reference
150+
// : pascalCase(type)))}`}`;
151+
152+
const typeString = () => {
153+
switch (true) {
154+
case type === 'Json':
155+
return 'JSON';
156+
157+
case !!propObj.enumRef:
158+
return propObj.enumRef;
159+
160+
case !!propObj.schema:
161+
return propObj.schema;
162+
163+
case propObj.type === 'ObjectId':
164+
return propObj.reference;
165+
166+
default:
167+
return pascalCase(type);
168+
}
169+
};
170+
171+
172+
173+
// Build @UnifiedField options; types vary and can't go in standardDeclaration
174+
function constructUnifiedFieldOptions(type: 'create' | 'input' | 'model'): string {
175+
switch (type) {
176+
case 'create':
177+
return `{
178+
description: ${description},
179+
${propObj.nullable ? 'isOptional: true,' : ''}
180+
roles: RoleEnum.ADMIN,
181+
${propObj.enumRef ? '' : `type: () => ${typeString()}${propObj.type === 'ObjectId' || propObj.schema ? 'CreateInput' : ''}`}
182+
}`;
183+
case 'input':
184+
return `{
185+
description: ${description},
186+
isOptional: true,
187+
roles: RoleEnum.ADMIN,
188+
${propObj.enumRef ? `enum: { enum: ${propObj.enumRef} }` : `type: () => ${typeString()}${propObj.type === 'ObjectId' || propObj.schema ? 'Input' : ''}`}
189+
}`;
190+
case 'model':
191+
return `{
192+
description: ${description},
193+
${propObj.nullable ? 'isOptional: true,' : ''}
194+
roles: RoleEnum.ADMIN,
195+
${propObj.enumRef ? '' : `type: () => ${typeString()}`}
196+
}`;
197+
}
198+
}
199+
141200
const standardDeclaration: OptionalKind<PropertyDeclarationStructure> = {
142-
decorators: [
143-
{ arguments: [`() => ${propObj.isArray ? `[${propObj.type
144-
=== 'ObjectId' ? propObj.reference : pascalCase(propObj.type)}]` : propObj.type
145-
=== 'ObjectId' ? propObj.reference : pascalCase(propObj.type)}`,
146-
`{ description: '${pascalCase(propObj.name)} of ${pascalCase(elementToEdit)}', nullable: ${propObj.nullable} }`],
147-
name: 'Field',
148-
},
149-
{ arguments: ['RoleEnum.ADMIN'], name: 'Restricted' },
150-
],
201+
decorators: [],
151202
hasQuestionToken: propObj.nullable,
152203
initializer: declare ? undefined : 'undefined',
153204
name: propObj.name,
154-
type: `${propObj.type === 'ObjectId' ? propObj.reference : type}${propObj.isArray ? '[]' : ''}`,
155205
};
156206

157207
// Patch model
158208
const lastModelProperty = modelProperties[modelProperties.length - 1];
159209
const newModelProperty: OptionalKind<PropertyDeclarationStructure> = structuredClone(standardDeclaration);
160-
newModelProperty.decorators.push({ arguments: [`${propObj.type === 'ObjectId' ? `{ ref: ${propObj.reference}, type: Schema.Types.ObjectId }` : ''}`], name: 'Prop' });
210+
newModelProperty.decorators.push({ arguments: [`${propObj.type === 'ObjectId' || propObj.schema ? `{ ref: () => ${propObj.reference}, type: Schema.Types.ObjectId }` : ''}`], name: 'Prop' });
211+
newModelProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('model')], name: 'UnifiedField' });
212+
newModelProperty.type = `${typeString()}${propObj.isArray ? '[]' : ''}`;
161213
const insertedModelProp = modelDeclaration.insertProperty(lastModelProperty.getChildIndex() + 1, newModelProperty);
162214
insertedModelProp.prependWhitespace('\n');
163215
insertedModelProp.appendWhitespace('\n');
164216

165217
// Patch input
166218
const lastInputProperty = inputProperties[inputProperties.length - 1];
167219
const newInputProperty: OptionalKind<PropertyDeclarationStructure> = structuredClone(standardDeclaration);
168-
if (propObj.nullable) {
169-
newInputProperty.decorators.push({ arguments: [], name: 'IsOptional' });
170-
}
220+
newInputProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('input')], name: 'UnifiedField' });
221+
const inputSuffix = propObj.type === 'ObjectId' || propObj.schema ? 'Input' : '';
222+
newInputProperty.type = `${typeString()}${inputSuffix}${propObj.isArray ? '[]' : ''}`;
171223
const insertedInputProp = inputDeclaration.insertProperty(lastInputProperty.getChildIndex() + 1, newInputProperty);
172224
insertedInputProp.prependWhitespace('\n');
173225
insertedInputProp.appendWhitespace('\n');
174226

175227
// Patch create input
176228
const lastCreateInputProperty = createInputProperties[createInputProperties.length - 1];
177-
const newCreateInputProperty: OptionalKind<PropertyDeclarationStructure> = structuredClone(newInputProperty);
229+
const newCreateInputProperty: OptionalKind<PropertyDeclarationStructure> = structuredClone(standardDeclaration);
178230
if (declare) {
179231
newCreateInputProperty.hasDeclareKeyword = true;
180232
} else {
181233
newCreateInputProperty.hasOverrideKeyword = true;
182234
}
235+
newCreateInputProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('create')], name: 'UnifiedField' });
236+
const createSuffix = propObj.type === 'ObjectId' || propObj.schema ? 'CreateInput' : '';
237+
newCreateInputProperty.type = `${typeString()}${createSuffix}${propObj.isArray ? '[]' : ''}`;
183238
const insertedCreateInputProp = createInputDeclaration.insertProperty(lastCreateInputProperty.getChildIndex() + 1, newCreateInputProperty);
184239
insertedCreateInputProp.prependWhitespace('\n');
185240
insertedCreateInputProp.appendWhitespace('\n');
186241
}
187242

243+
project.manipulationSettings.set({
244+
indentationText: IndentationText.TwoSpaces,
245+
});
246+
247+
// Format files
248+
moduleFile.formatText();
249+
inputFile.formatText();
250+
createInputFile.formatText();
251+
188252
// Save files
189253
await moduleFile.save();
190254
await inputFile.save();
191255
await createInputFile.save();
192256

257+
258+
193259
updateSpinner.succeed('All files updated successfully.');
194260

195261
// Add additional references

src/commands/server/module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ const NewCommand: ExtendedGluegunCommand = {
116116

117117
// nest-server-module/inputs/xxx-create.input.ts
118118
await template.generate({
119-
props: { imports: createTemplate.imports, nameCamel, nameKebab, namePascal, props: createTemplate.props },
119+
props: { imports: createTemplate.imports, isGql: controller === 'GraphQL' || controller === 'Both', nameCamel, nameKebab, namePascal, props: createTemplate.props },
120120
target: join(directory, 'inputs', `${nameKebab}-create.input.ts`),
121121
template: 'nest-server-module/inputs/template-create.input.ts.ejs',
122122
});
123123

124124
// nest-server-module/output/find-and-count-xxxs-result.output.ts
125125
await template.generate({
126-
props: { nameCamel, nameKebab, namePascal },
126+
props: { isGql: controller === 'GraphQL' || controller === 'Both', nameCamel, nameKebab, namePascal },
127127
target: join(directory, 'outputs', `find-and-count-${nameKebab}s-result.output.ts`),
128128
template: 'nest-server-module/outputs/template-fac-result.output.ts.ejs',
129129
});
@@ -132,6 +132,7 @@ const NewCommand: ExtendedGluegunCommand = {
132132
await template.generate({
133133
props: {
134134
imports: modelTemplate.imports,
135+
isGql: controller === 'GraphQL' || controller === 'Both',
135136
mappings: modelTemplate.mappings,
136137
nameCamel,
137138
nameKebab,

src/extensions/server.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,10 @@ export class Server {
304304
/**
305305
* User who has tested the ${this.pascalCase(modelName)}
306306
*/
307-
@Field(() => User, {
307+
@UnifiedField({
308308
description: 'User who has tested the ${this.pascalCase(modelName)}',
309-
nullable: true,
309+
isOptional: true,
310+
type: () => User,
310311
})
311312
@Prop({ type: Schema.Types.ObjectId, ref: 'User' })
312313
testedBy: User${undefinedString}
@@ -344,11 +345,6 @@ export class Server {
344345
/**
345346
* ${this.pascalCase(propName) + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}
346347
*/
347-
@Restricted(RoleEnum.S_EVERYONE)
348-
@Field(() => ${(isArray ? '[' : '') + (reference ? reference : modelFieldType) + (isArray ? ']' : '')}, {
349-
description: '${this.pascalCase(propName) + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}',
350-
nullable: ${item.nullable},
351-
})
352348
@Prop(${
353349
reference
354350
? `${isArray ? '[' : ''}{ ref: '${reference}', type: Schema.Types.ObjectId }${isArray ? ']' : ''}`
@@ -360,10 +356,13 @@ export class Server {
360356
? `${isArray ? '[' : ''}{ type: Object }${isArray ? ']' : ''}`
361357
: ''
362358
})
363-
${propName}: ${
364-
(reference ? reference : enumRef || modelClassType) + (isArray ? '[]' : '')
365-
// (reference ? ' | ' + reference + (isArray ? '[]' : '') : '')
366-
}${undefinedString}
359+
@UnifiedField({
360+
description: '${this.pascalCase(propName) + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}',
361+
isOptional: ${item.nullable},
362+
roles: RoleEnum.S_EVERYONE,
363+
type: () => ${reference ? reference : modelFieldType},
364+
})
365+
${propName}: ${(reference ? reference : enumRef || modelClassType) + (isArray ? '[]' : '')}${undefinedString}
367366
`;
368367
}
369368

@@ -422,11 +421,11 @@ export class Server {
422421
properties: string[]${undefinedString}
423422
424423
/**
425-
* User how has tested the ${this.pascalCase(modelName)}
424+
* User who has tested the ${this.pascalCase(modelName)}
426425
*/
427-
@Field(() => User, {
426+
@UnifiedField({
428427
description: 'User who has tested the ${this.pascalCase(modelName)}',
429-
nullable: ${config.nullable},
428+
isOptional: ${config.nullable},
430429
})
431430
testedBy: User${undefinedString}
432431
`,
@@ -464,14 +463,13 @@ export class Server {
464463
/**
465464
* ${this.pascalCase(name) + propertySuffix + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}
466465
*/
467-
@Restricted(RoleEnum.S_EVERYONE)
468-
@Field(() => ${(item.isArray ? '[' : '') + inputFieldType + (item.isArray ? ']' : '')}, {
466+
@UnifiedField({
469467
description: '${this.pascalCase(name) + propertySuffix + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}',
470-
nullable: ${nullable || item.nullable},
471-
})${nullable || item.nullable ? '\n @IsOptional()' : ''}
472-
${overrideFlag + this.camelCase(name)}${nullable || item.nullable ? '?' : ''}: ${
473-
inputClassType + (item.isArray ? '[]' : '')
474-
}${undefinedString}
468+
isOptional: ${nullable},
469+
roles: RoleEnum.S_EVERYONE,
470+
type: () => ${inputFieldType}
471+
})
472+
${overrideFlag + this.camelCase(name)}${nullable || item.nullable ? '?' : ''}: ${inputClassType}${item.isArray ? '[]' : ''}${undefinedString}
475473
`;
476474
}
477475

src/templates/nest-server-module/inputs/template-create.input.ts.ejs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Restricted, RoleEnum } from '@lenne.tech/nest-server';
2-
import { Field, InputType } from '@nestjs/graphql';
3-
import { IsOptional } from 'class-validator';<%- props.imports %>
1+
import { Restricted, RoleEnum, UnifiedField } from '@lenne.tech/nest-server';
2+
import { InputType } from '@nestjs/graphql';
43

54
import { <%= props.namePascal %>Input } from './<%= props.nameKebab %>.input';
65

src/templates/nest-server-module/inputs/template.input.ts.ejs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { CoreInput, Restricted, RoleEnum } from '@lenne.tech/nest-server';
2-
import { Field, InputType } from '@nestjs/graphql';
3-
import { IsOptional } from 'class-validator';<%- props.imports %>
1+
import { CoreInput, Restricted, RoleEnum, UnifiedField } from '@lenne.tech/nest-server';
2+
import { InputType } from '@nestjs/graphql';
43

54
/**
65
* <%= props.namePascal %> input
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
import { Field, ObjectType } from '@nestjs/graphql';
1+
import { UnifiedField } from '@lenne.tech/nest-server';
2+
<% if (props.isGql) { %>
3+
import { ObjectType } from '@nestjs/graphql';
4+
<% } %>
25

36
import { <%= props.namePascal %> } from '../<%= props.nameKebab %>.model';
4-
7+
<% if (props.isGql) { %>
58
@ObjectType({ description: 'Result of find and count <%= props.namePascal %>s' })
9+
<% } %>
610
export class FindAndCount<%= props.namePascal %>sResult {
7-
@Field(() => [<%= props.namePascal %>], { description: 'Found <%= props.namePascal %>s' })
11+
12+
@UnifiedField({
13+
type: () => <%= props.namePascal %>,
14+
description: 'Found <%= props.namePascal %>s',
15+
})
816
items: <%= props.namePascal %>[];
917

10-
@Field({ description: 'Total count (skip/offset and limit/take are ignored in the count)' })
18+
@UnifiedField({
19+
description: 'Total count (skip/offset and limit/take are ignored in the count)',
20+
})
1121
totalCount: number;
1222
}

src/templates/nest-server-module/template.controller.ts.ejs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { ApiCommonErrorResponses, FilterArgs, RoleEnum, Roles } from '@lenne.tech/nest-server';
22
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
3+
import { ApiOkResponse } from '@nestjs/swagger';
34

45
import { <%= props.namePascal %>Service } from './<%= props.nameKebab %>.service';
56
import { <%= props.namePascal %>Input } from './inputs/<%= props.nameKebab %>.input';
67
import { <%= props.namePascal %>CreateInput } from './inputs/<%= props.nameKebab %>-create.input';
8+
import { <%= props.namePascal %> } from './<%= props.nameKebab %>.model';
79

810
@ApiCommonErrorResponses()
911
@Controller('<%= props.lowercase %>')
@@ -14,31 +16,36 @@ constructor(protected readonly <%= props.nameCamel %>Service: <%= props.namePasc
1416

1517
@Post()
1618
@Roles(RoleEnum.ADMIN)
17-
async create(@Body() input: <%= props.namePascal %>CreateInput): Promise<any> {
19+
@ApiOkResponse({ type: <%= props.namePascal %> })
20+
async create(@Body() input: <%= props.namePascal %>CreateInput): Promise<<%= props.namePascal %>> {
1821
return await this.<%= props.nameCamel %>Service.create(input);
1922
}
2023

2124
@Get()
2225
@Roles(RoleEnum.ADMIN)
23-
async get(@Body() filterArgs: FilterArgs): Promise<any> {
26+
@ApiOkResponse({ isArray: true, type: <%= props.namePascal %> })
27+
async get(@Body() filterArgs: FilterArgs): Promise<<%= props.namePascal %>[]> {
2428
return await this.<%= props.nameCamel %>Service.find(filterArgs);
2529
}
2630

2731
@Get(':id')
2832
@Roles(RoleEnum.ADMIN)
29-
async getById(@Param('id') id: string): Promise<any> {
33+
@ApiOkResponse({ type: <%= props.namePascal %> })
34+
async getById(@Param('id') id: string): Promise<<%= props.namePascal %>> {
3035
return await this.<%= props.nameCamel %>Service.findOne({filterQuery: { _id: id }})
3136
}
3237

3338
@Put(':id')
3439
@Roles(RoleEnum.ADMIN)
35-
async update(@Param('id') id: string, @Body() input: <%= props.namePascal %>Input): Promise<any> {
40+
@ApiOkResponse({ type: <%= props.namePascal %> })
41+
async update(@Param('id') id: string, @Body() input: <%= props.namePascal %>Input): Promise<<%= props.namePascal %>> {
3642
return await this.<%= props.nameCamel %>Service.update(id, input);
3743
}
3844

3945
@Delete(':id')
4046
@Roles(RoleEnum.ADMIN)
41-
async delete(@Param('id') id: string): Promise<any> {
47+
@ApiOkResponse({ type: <%= props.namePascal %> })
48+
async delete(@Param('id') id: string): Promise<<%= props.namePascal %>> {
4249
return await this.<%= props.nameCamel %>Service.delete(id);
4350
}
4451

src/templates/nest-server-module/template.model.ts.ejs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { Restricted, RoleEnum, equalIds, mapClasses } from '@lenne.tech/nest-server';
2-
import { Field, ObjectType } from '@nestjs/graphql';
1+
import { Restricted, RoleEnum, equalIds, mapClasses , UnifiedField} from '@lenne.tech/nest-server';
2+
<% if (props.isGql) { %>
3+
import { ObjectType } from '@nestjs/graphql';
4+
<% } %>
35
import { Schema as MongooseSchema, Prop, SchemaFactory } from '@nestjs/mongoose';
46
import { Document, Schema } from 'mongoose';<%- props.imports %>
57

@@ -12,7 +14,7 @@ export type <%= props.namePascal %>Document = <%= props.namePascal %> & Document
1214
* <%= props.namePascal %> model
1315
*/
1416
@Restricted(RoleEnum.ADMIN)
15-
@ObjectType({ description: '<%= props.namePascal %>' })
17+
<% if (props.isGql) { %> @ObjectType({ description: '<%= props.namePascal %>' }) <% } %>
1618
@MongooseSchema({ timestamps: true })
1719
export class <%= props.namePascal %> extends PersistenceModel {
1820

src/templates/nest-server-module/template.module.ts.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { <%= props.namePascal %>Resolver } from './<%= props.nameKebab %>.resolv
1010
<% } -%>
1111
import { <%= props.namePascal %>Service } from './<%= props.nameKebab %>.service';
1212
<% if ((props.controller === 'Rest') || (props.controller === 'Both')) { -%>
13-
import { <%= props.namePascal %>Controller } from './<%= props.nameKebab %>.controller';
13+
import { <%= props.namePascal %>Controller } from './<%= props.nameKebab %>.controller';
1414
<% } -%>
1515

1616
/**

src/templates/nest-server-object/template-create.input.ts.ejs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Restricted, RoleEnum } from '@lenne.tech/nest-server';
2-
import { Field, InputType } from '@nestjs/graphql';
3-
import { IsOptional } from 'class-validator';
1+
import { Restricted, RoleEnum, UnifiedField } from '@lenne.tech/nest-server';
2+
import { InputType } from '@nestjs/graphql';
43
import { <%= props.namePascal %>Input } from './<%= props.nameKebab %>.input';<%- props.imports %>
54

6-
75
/**
86
* <%= props.namePascal %> create input
97
*/

0 commit comments

Comments
 (0)