Skip to content
Open
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: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ module.exports = {
["@providers", "./src/providers"],
["@utils", "./src/utils"],
["@validations", "./src/validations"],
["@controllers", "./src/api/controllers"],
["@api", "./src/api/modules"],
],
extensions: ['.ts', '.js', '.json'],
},
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
"lodash": "^4.17.21",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.0"
"swagger-ui-express": "^5.0.0",
"typescript": "5.1.3",
"wrap-ansi": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand All @@ -49,16 +51,15 @@
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"jest": "^29.7.0",
"prettier": "^3.0.0",
"prisma": "^5.10.2",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
"tsconfig-paths": "^4.2.0"
},
"jest": {
"moduleFileExtensions": [
Expand Down Expand Up @@ -87,7 +88,7 @@
"^@providers(.*)$": "<rootDir>/providers$1",
"^@utils(.*)$": "<rootDir>/utils$1",
"^@validations(.*)$": "<rootDir>/validations$1",
"^@controllers(.*)$": "<rootDir>/api/controller/$1"
"^@api(.*)$": "<rootDir>/api/modules/$1"
}
}
}
12 changes: 12 additions & 0 deletions src/api/api-routers.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UsersApiModule } from '@api/users/users.api.module';
import { NestModuleImport } from '@common/types';

export const ApiModules: NestModuleImport[] = [UsersApiModule];
// path: '/api',
// module: ApiModule,
export const ApiRouters = [
{
path: 'users',
module: UsersApiModule,
},
];
4 changes: 2 additions & 2 deletions src/api/api.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ describe('ApiController', () => {
});

describe('root', () => {
it('should return "Hello World!"', () => {
expect(apiController.getHello()).toBe('Hello World!');
it('should return "example-nestjs Api"', () => {
expect(apiController.getApi()).toBe('example-nestjs Api');
});
});
});
4 changes: 2 additions & 2 deletions src/api/api.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class ApiController {
constructor(private readonly service: ApiService) {}

@Get()
getAPI(): string {
return this.service.getAPI();
getApi(): string {
return this.service.getApi();
}
}
7 changes: 1 addition & 6 deletions src/api/api.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { Module } from '@nestjs/common';

import { NestModuleImport } from '@common/types';

import { ApiController } from './api.controller';
import { ApiService } from './api.service';

// API modules
const ApiModules: NestModuleImport[] = [];

const controllers: any[] = [ApiController];

export { ApiModules } from './api-routers.module';
@Module({
imports: [...ApiModules],
controllers: [...controllers],
providers: [ApiService],
})
Expand Down
4 changes: 2 additions & 2 deletions src/api/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';

@Injectable()
export class ApiService {
getAPI(): string {
return 'example-nestjs API';
getApi(): string {
return 'example-nestjs Api';
}
}
1 change: 1 addition & 0 deletions src/api/modules/users/dto/register.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class RegisterDTo extends CreateDTo {}
11 changes: 11 additions & 0 deletions src/api/modules/users/serializer/user.serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Exclude } from 'class-transformer';

import { UserEntity } from '../../../../modules/users/user.entity';

export class UserSerializer extends UserEntity {
@Exclude()
createdAt: Date;

@Exclude()
updatedAt: Date;
}
22 changes: 22 additions & 0 deletions src/api/modules/users/users.api.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';

import { UsersModule } from '@modules/users/users.module';

import { UsersApiController } from './users.api.controller';

describe('UsersApiController', () => {
let controller: UsersApiController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [UsersModule],
controllers: [UsersApiController],
}).compile();

controller = module.get<UsersApiController>(UsersApiController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
26 changes: 26 additions & 0 deletions src/api/modules/users/users.api.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Body, Controller, Get, Post } from '@nestjs/common';
import { ApiBody, ApiCreatedResponse, ApiTags } from '@nestjs/swagger';

import { CreateDTo } from '@modules/users/dto/create.dto';
import { UserDto } from '@modules/users/serializer/user.dto';
import { UsersService } from '@modules/users/users.service';

// api/users
@Controller()
@ApiTags('users')
export class UsersApiController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiCreatedResponse({ type: CreateDTo })
@ApiBody({ type: CreateDTo })
async create(@Body() dto: CreateDTo): Promise<UserDto> {
const user = await this.usersService.create(dto);
return new UserDto(user);
}

@Get()
async Hello() {
return 'this is a test route for users module';
}
}
11 changes: 11 additions & 0 deletions src/api/modules/users/users.api.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';

import { UsersModule } from '@modules/users/users.module';

import { UsersApiController } from './users.api.controller';

@Module({
imports: [UsersModule],
controllers: [UsersApiController],
})
export class UsersApiModule {}
18 changes: 15 additions & 3 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,35 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RouterModule } from '@nestjs/core';

import { NestModuleImport } from '@common/types';
import { DatabaseModule } from '@config/database/database.module';

import { ApiRouters, ApiModules } from './api/api-routers.module';
import { ApiModule } from './api/api.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';

// config modules
const configModules = () => [ConfigModule.forRoot(), DatabaseModule];

// // API modules
const appModules: NestModuleImport[] = [ApiModule];
// // Api modules
const appModules: NestModuleImport[] = [ApiModule, ...ApiModules];

const controllers: any[] = [AppController];

@Module({
imports: [...configModules(), ...appModules],
imports: [
...configModules(),
...appModules,
RouterModule.register([
{
path: '/api',
module: ApiModule,
children: ApiRouters,
},
]),
],
controllers: [...controllers],
providers: [AppService],
})
Expand Down
33 changes: 7 additions & 26 deletions src/common/abstract.entity.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
import { DateField, NumberField, StringFieldOptional } from '@decorators/field.decorators';
import { attributeManagement } from '@decorators/field.decorators';

import { AbstractDto } from './dto/abstract.dto';
import { Constructor } from './types';
@attributeManagement()
export abstract class AbstractEntity<T> {
protected obj: T | Partial<T>;

export abstract class AbstractEntity<DTO extends AbstractDto = AbstractDto, O = never> {
@NumberField({ int: true, isPositive: true })
id!: number;

@StringFieldOptional({ maxLength: 255 })
name: string;

@DateField()
createdAt: Date;

@DateField()
updatedAt: Date;

private dtoClass?: Constructor<DTO, [AbstractEntity, O?]>;

toDto(options?: O): DTO {
const dtoClass = this.dtoClass;

if (!dtoClass) {
throw new Error(`You need to use @UseDto on class (${this.constructor.name}) be able to call toDto function`);
}

return new dtoClass(this, options);
constructor(obj: T | Partial<T>) {
this.obj = obj;
Object.assign(this, obj);
}
}
9 changes: 0 additions & 9 deletions src/common/dto/abstract.dto.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/common/dto/index.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/config/swagger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { APP_INFO } from '@config/environments/public';

const swaggerConfig = (): Omit<OpenAPIObject, 'paths'> =>
new DocumentBuilder()
.setTitle('Example NestJS API Swagger')
.setDescription('The Example NestJS API description')
.setTitle('Example NestJS Api Swagger')
.setDescription('The Example NestJS Api description')
.setVersion(APP_INFO.VERSION)
.addTag('API')
.build();

const setupSwagger = (app: INestApplication<any>) =>
Expand Down
9 changes: 6 additions & 3 deletions src/decorators/field.decorators/boolean-field.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { IsBoolean, NotEquals } from 'class-validator';
import { ToBoolean } from '@decorators/transform.decorators';
import { IsNullable, IsUndefinable } from '@decorators/validator.decorators';

import { IFieldOptions } from './field-options.type';
import { IFieldOptions, fieldDecorator } from './field-options.decorator';

export type IBooleanFieldOptions = IFieldOptions;
export function BooleanField(options: Omit<ApiPropertyOptions, 'type'> & IBooleanFieldOptions = {}): PropertyDecorator {
export function BooleanField({
databaseFieldName,
...options
}: Omit<ApiPropertyOptions, 'type'> & IBooleanFieldOptions = {}): PropertyDecorator {
const decorators = [ToBoolean(), IsBoolean()];

if (options.nullable) {
Expand All @@ -21,7 +24,7 @@ export function BooleanField(options: Omit<ApiPropertyOptions, 'type'> & IBoolea
decorators.push(ApiProperty({ type: Boolean, ...options }));
}

return applyDecorators(...decorators);
return applyDecorators(fieldDecorator({ databaseFieldName }), ...decorators);
}

export function BooleanFieldOptional(
Expand Down
9 changes: 6 additions & 3 deletions src/decorators/field.decorators/date-field.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { IsDate, NotEquals } from 'class-validator';

import { IsNullable, IsUndefinable } from '@decorators/validator.decorators';

import { IFieldOptions } from './field-options.type';
import { IFieldOptions, fieldDecorator } from './field-options.decorator';

export function DateField(options: Omit<ApiPropertyOptions, 'type'> & IFieldOptions = {}): PropertyDecorator {
export function DateField({
databaseFieldName,
...options
}: Omit<ApiPropertyOptions, 'type'> & IFieldOptions = {}): PropertyDecorator {
const decorators = [Type(() => Date), IsDate()];

if (options.nullable) {
Expand All @@ -20,7 +23,7 @@ export function DateField(options: Omit<ApiPropertyOptions, 'type'> & IFieldOpti
decorators.push(ApiProperty({ type: Date, ...options }));
}

return applyDecorators(...decorators);
return applyDecorators(fieldDecorator({ databaseFieldName }), ...decorators);
}

export function DateFieldOptional(
Expand Down
37 changes: 37 additions & 0 deletions src/decorators/field.decorators/field-options.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'reflect-metadata';
import { applyDecorators } from '@nestjs/common';

export interface IFieldOptions {
each?: boolean;
swagger?: boolean;
nullable?: boolean;
groups?: string[];
databaseFieldName?: string;
}

export type FieldOptions = IFieldOptions;

export type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>;

export type EntityFieldOptions = Pick<FieldOptions, 'databaseFieldName'>;

export type IndexedAttributes = { [key: string]: string };

export const attributeManagement = () => (target: any) => {
const attributes: IndexedAttributes = {};
Reflect.defineProperty(target.constructor.prototype, 'attributes', {
get() {
return attributes;
},
set(value: IndexedAttributes) {
Object.assign(attributes, value);
},
enumerable: true,
configurable: true,
});
};

export const fieldDecorator = ({ databaseFieldName }: EntityFieldOptions = {}): PropertyDecorator =>
applyDecorators(attributeManagement(), (target, propertyKey) => {
target.attributes[propertyKey] = databaseFieldName || propertyKey;
});
10 changes: 0 additions & 10 deletions src/decorators/field.decorators/field-options.type.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/decorators/field.decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './field-options.type';
export * from './field-options.decorator';
export * from './number-field.decorator';
export * from './string-field.decorator';
export * from './boolean-field.decorator';
Expand Down
Loading