Skip to content

Commit ad07619

Browse files
authored
Develop (#8)
2 parents 68e6dec + e1a0a85 commit ad07619

File tree

21 files changed

+330
-18
lines changed

21 files changed

+330
-18
lines changed

package-lock.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
"test:watch": "jest --watch",
1818
"test:cov": "jest --coverage",
1919
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20-
"test:e2e": "jest --config ./test/jest-e2e.json"
20+
"test:e2e": "jest --config ./test/jest-e2e.json",
21+
"typeorm": "typeorm-ts-node-commonjs -d ./src/shared/database/config/typeorm.config.ts",
22+
"typeorm:migration:generate": "npm run build && npm run typeorm migration:generate",
23+
"typeorm:migration:run": "npm run build && npm run typeorm migration:run",
24+
"typeorm:migration:revert": "npm run typeorm migration:revert"
2125
},
2226
"dependencies": {
2327
"@nestjs/common": "^10.0.0",
@@ -26,6 +30,8 @@
2630
"@nestjs/platform-express": "^10.0.0",
2731
"@nestjs/swagger": "^7.4.2",
2832
"@nestjs/typeorm": "^10.0.2",
33+
"class-transformer": "^0.5.1",
34+
"class-validator": "^0.14.1",
2935
"joi": "^17.13.3",
3036
"nest-winston": "^1.9.7",
3137
"pg": "^8.13.0",

src/application/user/services/user.service.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import {
44
USER_DATABASE_PROVIDER,
55
} from '@domain/user/interfaces/user-database.provider';
66
import { TryCatch } from '@common/decorators/try-catch.decorator';
7-
import { User } from '@domain/user/entities/user.entity';
7+
import { User } from '@domain/user/entities/user.model';
88
import { Result } from '@common/result/result';
9+
import { ErrorCode } from '@common/result/error';
910

1011
@Injectable()
1112
export class UserService {
@@ -18,8 +19,23 @@ export class UserService {
1819

1920
@TryCatch
2021
async createUser(user: User): Promise<Result<User>> {
21-
this.logger.log(`Creating a user with email: ${user.email}`);
22+
this.logger.log('Checking if user exists before creating one.');
23+
const userExistsRes = await this.userDatabaseProvider.userExists({
24+
email: user.email,
25+
});
26+
if (userExistsRes.isError()) {
27+
this.logger.error(
28+
`Error when checking existence of user: ${userExistsRes.error.message}`,
29+
);
30+
return Result.error(userExistsRes.error);
31+
}
2232

33+
if (userExistsRes.value === true) {
34+
this.logger.log('User already exists; returning error');
35+
return Result.error('You info is Duplicate', ErrorCode.DUPLICATE);
36+
}
37+
38+
this.logger.log(`Creating a user with email: ${user.email}`);
2339
const res = await this.userDatabaseProvider.createUser(user);
2440
if (res.isError()) {
2541
this.logger.error(

src/common/http/base-http-controller.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { StdStatus } from '@common/std-response/std-status';
55
import { HttpStatus } from '@nestjs/common/enums/http-status.enum';
66

77
export abstract class BaseHttpController {
8-
protected respond<T>(response: Response, result: Result<T>) {
9-
const stdResponse = StdResponse.fromResult<T>(result);
8+
protected respond(response: Response, result: Result<any>) {
9+
const stdResponse = StdResponse.fromResult<any>(result);
1010

1111
response.status(this.fromStdStatus(stdResponse.status)).send(stdResponse);
1212
}
@@ -15,14 +15,16 @@ export abstract class BaseHttpController {
1515
switch (status) {
1616
case StdStatus.SUCCESS:
1717
return HttpStatus.OK;
18-
case StdStatus.BAD_REQUEST:
18+
case StdStatus.INVALID:
1919
return HttpStatus.BAD_REQUEST;
2020
case StdStatus.UNAUTHORIZED:
2121
return HttpStatus.UNAUTHORIZED;
2222
case StdStatus.FORBIDDEN:
2323
return HttpStatus.FORBIDDEN;
2424
case StdStatus.NOT_FOUND:
2525
return HttpStatus.NOT_FOUND;
26+
case StdStatus.DUPLICATE:
27+
return HttpStatus.CONFLICT;
2628
case StdStatus.INTERNAL_ERROR:
2729
return HttpStatus.INTERNAL_SERVER_ERROR;
2830
default:

src/common/result/error.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { StdStatus } from '@common/std-response/std-status';
2-
31
export enum ErrorCode {
42
INVALID_ARGUMENT,
53
UNAUTHORIZED,
64
NOT_FOUND,
75
FORBIDDEN,
86
VALIDATION_FAILURE,
7+
DUPLICATE,
98
INTERNAL,
109
}
1110

src/common/std-response/std-response.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class StdResponse<T> {
1212
static fromResult<T>(result: Result<T>): StdResponse<T> {
1313
if (result.isError()) {
1414
return new StdResponse<T>(
15-
result.error.data,
15+
result.error.data as T,
1616
result.error.message,
1717
StdResponse.toStdStatus(result.error.code),
1818
);
@@ -26,11 +26,13 @@ export class StdResponse<T> {
2626
case ErrorCode.NOT_FOUND:
2727
return StdStatus.NOT_FOUND;
2828
case ErrorCode.INVALID_ARGUMENT:
29-
return StdStatus.BAD_REQUEST;
29+
return StdStatus.INVALID;
3030
case ErrorCode.FORBIDDEN:
3131
return StdStatus.FORBIDDEN;
3232
case ErrorCode.UNAUTHORIZED:
3333
return StdStatus.UNAUTHORIZED;
34+
case ErrorCode.DUPLICATE:
35+
return StdStatus.DUPLICATE;
3436
default:
3537
return StdStatus.INTERNAL_ERROR;
3638
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
export enum StdStatus {
22
SUCCESS,
3-
BAD_REQUEST,
3+
INVALID,
44
UNAUTHORIZED,
55
FORBIDDEN,
66
NOT_FOUND,
7+
DUPLICATE,
78
INTERNAL_ERROR,
89
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export class ValidationFailure {
2+
constructor(
3+
public property: string,
4+
public message: string,
5+
) {}
6+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {
2+
ArgumentMetadata,
3+
Injectable,
4+
Paramtype,
5+
PipeTransform,
6+
Type,
7+
UnprocessableEntityException,
8+
} from '@nestjs/common';
9+
import { plainToInstance } from 'class-transformer';
10+
import { validate, ValidationError } from 'class-validator';
11+
import { StdResponse } from '@common/std-response/std-response';
12+
import { Result } from '@common/result/result';
13+
import { ValidationFailure } from './validation-failure';
14+
import { ErrorCode } from '@common/result/error';
15+
16+
@Injectable()
17+
export class ValidationPipe implements PipeTransform {
18+
constructor(
19+
private readonly types: Paramtype[],
20+
private readonly transport: 'http' | 'ws',
21+
) {}
22+
23+
async transform(value: any, metadata: ArgumentMetadata) {
24+
if (
25+
!this.types.includes(metadata.type) ||
26+
!metadata.metatype ||
27+
!this.toValidate(metadata.metatype)
28+
) {
29+
return value;
30+
}
31+
32+
let oldValue = value;
33+
let ack = null;
34+
if (this.transport === 'ws') {
35+
oldValue = value.data;
36+
ack = value.ack;
37+
} else if (this.transport === 'http' && metadata.type === 'param') {
38+
// For params, I wrap the single param value in an object
39+
oldValue = { [metadata.data]: value };
40+
}
41+
42+
const newValue = plainToInstance(metadata.metatype, oldValue);
43+
const errors = await validate(newValue);
44+
45+
if (errors.length > 0) {
46+
if (this.transport === 'ws' && ack) {
47+
ack(
48+
StdResponse.fromResult<ValidationFailure[]>(
49+
this.formatErrors(errors),
50+
),
51+
);
52+
}
53+
54+
if (this.transport === 'http') {
55+
throw new UnprocessableEntityException(
56+
StdResponse.fromResult(this.formatErrors(errors)),
57+
);
58+
}
59+
}
60+
61+
return value;
62+
}
63+
64+
private toValidate(metatype: Type<any>): boolean {
65+
const types = [String, Boolean, Number, Array, Object];
66+
return !types.includes(metatype as any);
67+
}
68+
69+
private formatErrors(validationErrors: ValidationError[]): Result<any, any> {
70+
const errors: ValidationFailure[] = [];
71+
72+
// Transform validation errors into ValidationFailure instances and push them to the array.
73+
validationErrors.forEach((error) => {
74+
if (error.constraints) {
75+
for (const failure of Object.values(error.constraints)) {
76+
errors.push(new ValidationFailure(error.property, failure));
77+
}
78+
}
79+
80+
if (error.children) {
81+
for (const subError of error.children) {
82+
errors.push(
83+
new ValidationFailure(subError.property, error.property + '.'),
84+
);
85+
}
86+
}
87+
88+
return errors;
89+
});
90+
91+
return Result.error(
92+
'Validation Error',
93+
ErrorCode.VALIDATION_FAILURE,
94+
errors,
95+
);
96+
}
97+
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { IDatatableEntity } from '@common/interfaces/datable-entity.interface';
33
import { IDeletableEntity } from '@common/interfaces/deletable-entity.interface';
44

55
export class IUser {
6+
email: string;
7+
password: string;
68
firstName: string;
79
lastName: string;
8-
email: string;
910
}
1011

1112
export class User
@@ -23,4 +24,8 @@ export class User
2324
constructor(init?: Partial<User>) {
2425
Object.assign(this, init);
2526
}
27+
28+
static create(iUser: IUser): User {
29+
return new User(iUser);
30+
}
2631
}

0 commit comments

Comments
 (0)