Skip to content
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lenne.tech/cli",
"version": "0.0.120",
"version": "0.0.121",
"description": "lenne.Tech CLI: lt",
"keywords": [
"lenne.Tech",
Expand Down
82 changes: 66 additions & 16 deletions src/commands/server/add-property.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GluegunCommand } from 'gluegun';
import { join } from 'path';
import {
ClassPropertyTypes,
ClassPropertyTypes, IndentationText,
OptionalKind,
Project,
PropertyDeclarationStructure,
Expand Down Expand Up @@ -138,53 +138,103 @@ const NewCommand: GluegunCommand = {
}

const type = ['any', 'bigint', 'boolean', 'never', 'null', 'number', 'string', 'symbol', 'undefined', 'unknown', 'void'].includes(propObj.type) ? propObj.type : pascalCase(propObj.type);

const description = `'${pascalCase(propObj.name)} of ${pascalCase(elementToEdit)}'`;

const typeString = () => {
switch (true) {
case type === 'Json':
return 'JSON';

case !!propObj.enumRef:
return propObj.enumRef;

case !!propObj.schema:
return propObj.schema;

case propObj.type === 'ObjectId':
return propObj.reference;

default:
return pascalCase(type);
}
};

// Build @UnifiedField options; types vary and can't go in standardDeclaration
function constructUnifiedFieldOptions(type: 'create' | 'input' | 'model'): string {
switch (type) {
case 'create':
return `{
description: ${description},${propObj.nullable ? '\nisOptional: true,' : ''}
roles: RoleEnum.ADMIN,${propObj.enumRef ? '' : `\ntype: () => ${typeString()}${propObj.type === 'ObjectId' || propObj.schema ? 'CreateInput' : ''}`}
}`;
case 'input':
return `{
description: ${description},
isOptional: true,
roles: RoleEnum.ADMIN,
${propObj.enumRef ? `enum: { enum: ${propObj.enumRef} }` : `type: () => ${typeString()}${propObj.type === 'ObjectId' || propObj.schema ? 'Input' : ''}`}
}`;
case 'model':
return `{
description: ${description},${propObj.nullable ? '\nisOptional: true,' : ''}
roles: RoleEnum.ADMIN,${propObj.enumRef ? '' : `\ntype: () => ${typeString()}`}
}`;
}
}

const standardDeclaration: OptionalKind<PropertyDeclarationStructure> = {
decorators: [
{ arguments: [`() => ${propObj.isArray ? `[${propObj.type
=== 'ObjectId' ? propObj.reference : pascalCase(propObj.type)}]` : propObj.type
=== 'ObjectId' ? propObj.reference : pascalCase(propObj.type)}`,
`{ description: '${pascalCase(propObj.name)} of ${pascalCase(elementToEdit)}', nullable: ${propObj.nullable} }`],
name: 'Field',
},
{ arguments: ['RoleEnum.ADMIN'], name: 'Restricted' },
],
decorators: [],
hasQuestionToken: propObj.nullable,
initializer: declare ? undefined : 'undefined',
name: propObj.name,
type: `${propObj.type === 'ObjectId' ? propObj.reference : type}${propObj.isArray ? '[]' : ''}`,
};

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

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

// Patch create input
const lastCreateInputProperty = createInputProperties[createInputProperties.length - 1];
const newCreateInputProperty: OptionalKind<PropertyDeclarationStructure> = structuredClone(newInputProperty);
const newCreateInputProperty: OptionalKind<PropertyDeclarationStructure> = structuredClone(standardDeclaration);
if (declare) {
newCreateInputProperty.hasDeclareKeyword = true;
} else {
newCreateInputProperty.hasOverrideKeyword = true;
}
newCreateInputProperty.decorators.push({ arguments: [constructUnifiedFieldOptions('create')], name: 'UnifiedField' });
const createSuffix = propObj.type === 'ObjectId' || propObj.schema ? 'CreateInput' : '';
newCreateInputProperty.type = `${typeString()}${createSuffix}${propObj.isArray ? '[]' : ''}`;
const insertedCreateInputProp = createInputDeclaration.insertProperty(lastCreateInputProperty.getChildIndex() + 1, newCreateInputProperty);
insertedCreateInputProp.prependWhitespace('\n');
insertedCreateInputProp.appendWhitespace('\n');
}

project.manipulationSettings.set({
indentationText: IndentationText.TwoSpaces,
});

// Format files
moduleFile.formatText();
inputFile.formatText();
createInputFile.formatText();

// Save files
await moduleFile.save();
await inputFile.save();
Expand Down
5 changes: 3 additions & 2 deletions src/commands/server/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ const NewCommand: ExtendedGluegunCommand = {

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

// nest-server-module/output/find-and-count-xxxs-result.output.ts
await template.generate({
props: { nameCamel, nameKebab, namePascal },
props: { isGql: controller === 'GraphQL' || controller === 'Both', nameCamel, nameKebab, namePascal },
target: join(directory, 'outputs', `find-and-count-${nameKebab}s-result.output.ts`),
template: 'nest-server-module/outputs/template-fac-result.output.ts.ejs',
});
Expand All @@ -132,6 +132,7 @@ const NewCommand: ExtendedGluegunCommand = {
await template.generate({
props: {
imports: modelTemplate.imports,
isGql: controller === 'GraphQL' || controller === 'Both',
mappings: modelTemplate.mappings,
nameCamel,
nameKebab,
Expand Down
40 changes: 19 additions & 21 deletions src/extensions/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,10 @@ export class Server {
/**
* User who has tested the ${this.pascalCase(modelName)}
*/
@Field(() => User, {
@UnifiedField({
description: 'User who has tested the ${this.pascalCase(modelName)}',
nullable: true,
isOptional: true,
type: () => User,
})
@Prop({ type: Schema.Types.ObjectId, ref: 'User' })
testedBy: User${undefinedString}
Expand Down Expand Up @@ -344,11 +345,6 @@ export class Server {
/**
* ${this.pascalCase(propName) + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}
*/
@Restricted(RoleEnum.S_EVERYONE)
@Field(() => ${(isArray ? '[' : '') + (reference ? reference : modelFieldType) + (isArray ? ']' : '')}, {
description: '${this.pascalCase(propName) + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}',
nullable: ${item.nullable},
})
@Prop(${
reference
? `${isArray ? '[' : ''}{ ref: '${reference}', type: Schema.Types.ObjectId }${isArray ? ']' : ''}`
Expand All @@ -360,10 +356,13 @@ export class Server {
? `${isArray ? '[' : ''}{ type: Object }${isArray ? ']' : ''}`
: ''
})
${propName}: ${
(reference ? reference : enumRef || modelClassType) + (isArray ? '[]' : '')
// (reference ? ' | ' + reference + (isArray ? '[]' : '') : '')
}${undefinedString}
@UnifiedField({
description: '${this.pascalCase(propName) + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}',
isOptional: ${item.nullable},
roles: RoleEnum.S_EVERYONE,
type: () => ${reference ? reference : modelFieldType},
})
${propName}: ${(reference ? reference : enumRef || modelClassType) + (isArray ? '[]' : '')}${undefinedString}
`;
}

Expand Down Expand Up @@ -422,11 +421,11 @@ export class Server {
properties: string[]${undefinedString}

/**
* User how has tested the ${this.pascalCase(modelName)}
* User who has tested the ${this.pascalCase(modelName)}
*/
@Field(() => User, {
@UnifiedField({
description: 'User who has tested the ${this.pascalCase(modelName)}',
nullable: ${config.nullable},
isOptional: ${config.nullable},
})
testedBy: User${undefinedString}
`,
Expand Down Expand Up @@ -464,14 +463,13 @@ export class Server {
/**
* ${this.pascalCase(name) + propertySuffix + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}
*/
@Restricted(RoleEnum.S_EVERYONE)
@Field(() => ${(item.isArray ? '[' : '') + inputFieldType + (item.isArray ? ']' : '')}, {
@UnifiedField({
description: '${this.pascalCase(name) + propertySuffix + (modelName ? ` of ${this.pascalCase(modelName)}` : '')}',
nullable: ${nullable || item.nullable},
})${nullable || item.nullable ? '\n @IsOptional()' : ''}
${overrideFlag + this.camelCase(name)}${nullable || item.nullable ? '?' : ''}: ${
inputClassType + (item.isArray ? '[]' : '')
}${undefinedString}
isOptional: ${nullable},
roles: RoleEnum.S_EVERYONE,
type: () => ${inputFieldType}
})
${overrideFlag + this.camelCase(name)}${nullable || item.nullable ? '?' : ''}: ${inputClassType}${item.isArray ? '[]' : ''}${undefinedString}
`;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Restricted, RoleEnum } from '@lenne.tech/nest-server';
import { Field, InputType } from '@nestjs/graphql';
import { IsOptional } from 'class-validator';<%- props.imports %>
import { Restricted, RoleEnum, UnifiedField } from '@lenne.tech/nest-server';
import { InputType } from '@nestjs/graphql';

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

Expand Down
5 changes: 2 additions & 3 deletions src/templates/nest-server-module/inputs/template.input.ts.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { CoreInput, Restricted, RoleEnum } from '@lenne.tech/nest-server';
import { Field, InputType } from '@nestjs/graphql';
import { IsOptional } from 'class-validator';<%- props.imports %>
import { CoreInput, Restricted, RoleEnum, UnifiedField } from '@lenne.tech/nest-server';
import { InputType } from '@nestjs/graphql';

/**
* <%= props.namePascal %> input
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UnifiedField } from '@lenne.tech/nest-server';
<% if (props.isGql) { %>
import { ObjectType } from '@nestjs/graphql';
<% } %>

import { <%= props.namePascal %> } from '../<%= props.nameKebab %>.model';

<% if (props.isGql) { %>
@ObjectType({ description: 'Result of find and count <%= props.namePascal %>s' })
<% } %>
export class FindAndCount<%= props.namePascal %>sResult {
@Field(() => [<%= props.namePascal %>], { description: 'Found <%= props.namePascal %>s' })

@UnifiedField({
type: () => <%= props.namePascal %>,
description: 'Found <%= props.namePascal %>s',
})
items: <%= props.namePascal %>[];

@Field({ description: 'Total count (skip/offset and limit/take are ignored in the count)' })
@UnifiedField({
description: 'Total count (skip/offset and limit/take are ignored in the count)',
})
totalCount: number;
}
17 changes: 12 additions & 5 deletions src/templates/nest-server-module/template.controller.ts.ejs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ApiCommonErrorResponses, FilterArgs, RoleEnum, Roles } from '@lenne.tech/nest-server';
import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common';
import { ApiOkResponse } from '@nestjs/swagger';

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

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

@Post()
@Roles(RoleEnum.ADMIN)
async create(@Body() input: <%= props.namePascal %>CreateInput): Promise<any> {
@ApiOkResponse({ type: <%= props.namePascal %> })
async create(@Body() input: <%= props.namePascal %>CreateInput): Promise<<%= props.namePascal %>> {
return await this.<%= props.nameCamel %>Service.create(input);
}

@Get()
@Roles(RoleEnum.ADMIN)
async get(@Body() filterArgs: FilterArgs): Promise<any> {
@ApiOkResponse({ isArray: true, type: <%= props.namePascal %> })
async get(@Body() filterArgs: FilterArgs): Promise<<%= props.namePascal %>[]> {
return await this.<%= props.nameCamel %>Service.find(filterArgs);
}

@Get(':id')
@Roles(RoleEnum.ADMIN)
async getById(@Param('id') id: string): Promise<any> {
@ApiOkResponse({ type: <%= props.namePascal %> })
async getById(@Param('id') id: string): Promise<<%= props.namePascal %>> {
return await this.<%= props.nameCamel %>Service.findOne({filterQuery: { _id: id }})
}

@Put(':id')
@Roles(RoleEnum.ADMIN)
async update(@Param('id') id: string, @Body() input: <%= props.namePascal %>Input): Promise<any> {
@ApiOkResponse({ type: <%= props.namePascal %> })
async update(@Param('id') id: string, @Body() input: <%= props.namePascal %>Input): Promise<<%= props.namePascal %>> {
return await this.<%= props.nameCamel %>Service.update(id, input);
}

@Delete(':id')
@Roles(RoleEnum.ADMIN)
async delete(@Param('id') id: string): Promise<any> {
@ApiOkResponse({ type: <%= props.namePascal %> })
async delete(@Param('id') id: string): Promise<<%= props.namePascal %>> {
return await this.<%= props.nameCamel %>Service.delete(id);
}

Expand Down
8 changes: 5 additions & 3 deletions src/templates/nest-server-module/template.model.ts.ejs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Restricted, RoleEnum, equalIds, mapClasses } from '@lenne.tech/nest-server';
import { Field, ObjectType } from '@nestjs/graphql';
import { Restricted, RoleEnum, equalIds, mapClasses , UnifiedField} from '@lenne.tech/nest-server';
<% if (props.isGql) { %>
import { ObjectType } from '@nestjs/graphql';
<% } %>
import { Schema as MongooseSchema, Prop, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema } from 'mongoose';<%- props.imports %>

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

Expand Down
2 changes: 1 addition & 1 deletion src/templates/nest-server-module/template.module.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { <%= props.namePascal %>Resolver } from './<%= props.nameKebab %>.resolv
<% } -%>
import { <%= props.namePascal %>Service } from './<%= props.nameKebab %>.service';
<% if ((props.controller === 'Rest') || (props.controller === 'Both')) { -%>
import { <%= props.namePascal %>Controller } from './<%= props.nameKebab %>.controller';
import { <%= props.namePascal %>Controller } from './<%= props.nameKebab %>.controller';
<% } -%>

/**
Expand Down
6 changes: 2 additions & 4 deletions src/templates/nest-server-object/template-create.input.ts.ejs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Restricted, RoleEnum } from '@lenne.tech/nest-server';
import { Field, InputType } from '@nestjs/graphql';
import { IsOptional } from 'class-validator';
import { Restricted, RoleEnum, UnifiedField } from '@lenne.tech/nest-server';
import { InputType } from '@nestjs/graphql';
import { <%= props.namePascal %>Input } from './<%= props.nameKebab %>.input';<%- props.imports %>


/**
* <%= props.namePascal %> create input
*/
Expand Down
Loading