19 files changed +125
-63
lines changed Original file line number Diff line number Diff line change @@ -39,6 +39,7 @@ module.exports = {
39
39
'import/no-cycle' : 'off' ,
40
40
'consistent-return' : 'off' ,
41
41
'no-underscore-dangle' : 'off' ,
42
+ 'max-classes-per-file' : 'off' ,
42
43
43
44
// Eslint errors
44
45
'no-restricted-syntax' : [
Original file line number Diff line number Diff line change 1
- const usersRoot = '/user ' ;
1
+ const usersRoot = '/users ' ;
2
2
3
3
export const routes = {
4
4
user : {
Original file line number Diff line number Diff line change @@ -8,16 +8,16 @@ export abstract class TypeormEntityBase {
8
8
}
9
9
10
10
@PrimaryColumn ( { update : false } )
11
- id ! : string ;
11
+ id : string ;
12
12
13
13
@CreateDateColumn ( {
14
14
type : 'timestamptz' ,
15
15
update : false ,
16
16
} )
17
- createdAt ! : Date ;
17
+ createdAt : Date ;
18
18
19
19
@UpdateDateColumn ( {
20
20
type : 'timestamptz' ,
21
21
} )
22
- updatedAt ! : Date ;
22
+ updatedAt : Date ;
23
23
}
Original file line number Diff line number Diff line change
1
+ /* Creating interfaces like this may be useful if you have a front end
2
+ web/mobile application or other microservices that need to talk to
3
+ your API since you can share them as a git submodule, a npm package/
4
+ library or in a monorepo.
5
+ */
1
6
export interface CreateUser {
2
7
email : string ;
3
8
country : string ;
Load Diff This file was deleted.
Original file line number Diff line number Diff line change
1
+ export interface FindUsers {
2
+ readonly country : string ;
3
+ readonly postalCode : string ;
4
+ readonly street : string ;
5
+ }
Original file line number Diff line number Diff line change @@ -5,7 +5,7 @@ import { createUserSymbol } from '@modules/user/user.providers';
5
5
import { ApiOperation , ApiResponse } from '@nestjs/swagger' ;
6
6
import { CreateUserCommand } from './create-user.command' ;
7
7
import { CreateUserService } from './create-user.service' ;
8
- import { CreateUserRequest } from './create-user.request.dto' ;
8
+ import { CreateUserHttpRequest } from './create-user.request.dto' ;
9
9
10
10
@Controller ( )
11
11
export class CreateUserHttpController {
@@ -27,7 +27,7 @@ export class CreateUserHttpController {
27
27
@ApiResponse ( {
28
28
status : HttpStatus . BAD_REQUEST ,
29
29
} )
30
- async create ( @Body ( ) body : CreateUserRequest ) : Promise < IdResponse > {
30
+ async create ( @Body ( ) body : CreateUserHttpRequest ) : Promise < IdResponse > {
31
31
const command = new CreateUserCommand ( body ) ;
32
32
33
33
const id = await this . createUser . createUser ( command ) ;
Original file line number Diff line number Diff line change @@ -3,18 +3,18 @@ import { Inject } from '@nestjs/common';
3
3
import { MessagePattern } from '@nestjs/microservices' ;
4
4
import { IdResponse } from 'src/interface-adapters/dtos/id.response.dto' ;
5
5
import { CreateUserCommand } from './create-user.command' ;
6
- import { CreateUserRequest } from './create-user.request.dto' ;
6
+ import { CreateUserMessageRequest } from './create-user.request.dto' ;
7
7
import { CreateUserService } from './create-user.service' ;
8
8
9
- export class CreateUserEventController {
9
+ export class CreateUserMessageController {
10
10
constructor (
11
11
@Inject ( createUserSymbol )
12
12
private readonly createUser : CreateUserService ,
13
13
) { }
14
14
15
- @MessagePattern ( 'user.create' ) // <- Subscribe to microservice event
16
- async create ( payload : CreateUserRequest ) : Promise < IdResponse > {
17
- const command = new CreateUserCommand ( payload ) ;
15
+ @MessagePattern ( 'user.create' ) // <- Subscribe to a microservice message
16
+ async create ( message : CreateUserMessageRequest ) : Promise < IdResponse > {
17
+ const command = new CreateUserCommand ( message ) ;
18
18
19
19
const id = await this . createUser . createUser ( command ) ;
20
20
Original file line number Diff line number Diff line change @@ -15,21 +15,27 @@ export class CreateUserRequest implements CreateUser {
15
15
} )
16
16
@MaxLength ( 320 )
17
17
@IsEmail ( )
18
- email ! : string ;
18
+ readonly email : string ;
19
19
20
20
@ApiProperty ( { example : 'France' , description : 'Country of residence' } )
21
21
@MaxLength ( 50 )
22
22
@IsString ( )
23
23
@IsAlpha ( )
24
- country ! : string ;
24
+ readonly country : string ;
25
25
26
26
@ApiProperty ( { example : '28566' , description : 'Postal code' } )
27
27
@MaxLength ( 10 )
28
28
@IsAlphanumeric ( )
29
- postalCode ! : string ;
29
+ readonly postalCode : string ;
30
30
31
31
@ApiProperty ( { example : 'Grande Rue' , description : 'Street' } )
32
32
@MaxLength ( 50 )
33
33
@IsAlphanumeric ( )
34
- street ! : string ;
34
+ readonly street : string ;
35
35
}
36
+
37
+ export class CreateUserHttpRequest extends CreateUserRequest
38
+ implements CreateUser { }
39
+
40
+ export class CreateUserMessageRequest extends CreateUserRequest
41
+ implements CreateUser { }
Original file line number Diff line number Diff line change @@ -8,14 +8,14 @@ export class UserOrmEntity extends TypeormEntityBase {
8
8
}
9
9
10
10
@Column ( { unique : true } )
11
- email ! : string ;
11
+ email : string ;
12
12
13
13
@Column ( )
14
- country ! : string ;
14
+ country : string ;
15
15
16
16
@Column ( )
17
- postalCode ! : string ;
17
+ postalCode : string ;
18
18
19
19
@Column ( )
20
- street ! : string ;
20
+ street : string ;
21
21
}
Original file line number Diff line number Diff line change @@ -14,6 +14,7 @@ import { QueryParams } from 'src/core/ports/repository.ports';
14
14
import { UserOrmEntity } from './user.orm-entity' ;
15
15
import { UserRepositoryPort } from './user.repository.interface' ;
16
16
import { UserOrmMapper } from './user.orm-mapper' ;
17
+ import { FindUsersQuery } from '../queries/find-users/find-users.query' ;
17
18
18
19
@Injectable ( )
19
20
export class UserRepository
@@ -58,6 +59,12 @@ export class UserRepository
58
59
return false ;
59
60
}
60
61
62
+ async findUsers ( query : FindUsersQuery ) : Promise < UserEntity [ ] > {
63
+ const users = await this . repository . find ( { where : query } ) ;
64
+
65
+ return users . map ( this . mapper . toDomainEntity ) ;
66
+ }
67
+
61
68
// Used to construct a query
62
69
protected prepareQuery (
63
70
params : QueryParams < UserProps > ,
@@ -66,6 +73,18 @@ export class UserRepository
66
73
if ( params . id ) {
67
74
where . id = params . id . value ;
68
75
}
76
+ if ( params . createdAt ) {
77
+ where . createdAt = params . createdAt . value ;
78
+ }
79
+ if ( params . address ?. country ) {
80
+ where . country = params . address . country ;
81
+ }
82
+ if ( params . address ?. street ) {
83
+ where . street = params . address . street ;
84
+ }
85
+ if ( params . address ?. postalCode ) {
86
+ where . postalCode = params . address . postalCode ;
87
+ }
69
88
return where ;
70
89
}
71
90
}
Original file line number Diff line number Diff line change @@ -43,3 +43,5 @@ export class UserResponse extends ResponseBase implements User {
43
43
} )
44
44
street : string ;
45
45
}
46
+
47
+ export class UserHttpResponse extends UserResponse implements User { }
Load Diff This file was deleted.
Load Diff This file was deleted.
Original file line number Diff line number Diff line change
1
+ import { Body , Controller , Get } from '@nestjs/common' ;
2
+ import { routes } from '@config/app.routes' ;
3
+ import { UserHttpResponse } from '@modules/user/dtos/user.response.dto' ;
4
+ import { UserRepository } from '@modules/user/database/user.repository' ;
5
+ import { FindUsersQuery } from './find-users.query' ;
6
+ import { FindUsersHttpRequest } from './find-users.request.dto' ;
7
+
8
+ @Controller ( )
9
+ export class FindUsersHttpController {
10
+ constructor ( private readonly userRepo : UserRepository ) { }
11
+
12
+ /* Since this is a simple query with no additional business
13
+ logic involved, it bypasses application's core completely
14
+ and retrieves user directly from repository.
15
+ */
16
+ @Get ( routes . user . root )
17
+ async findUsers (
18
+ @Body ( ) request : FindUsersHttpRequest ,
19
+ ) : Promise < UserHttpResponse [ ] > {
20
+ const query = new FindUsersQuery ( request ) ;
21
+ const users = await this . userRepo . findUsers ( query ) ;
22
+
23
+ /* Returning Response classes which are responsible
24
+ for whitelisting data that is sent to the user */
25
+ return users . map ( user => new UserHttpResponse ( user ) ) ;
26
+ }
27
+ }
Original file line number Diff line number Diff line change
1
+ // Query is a plain object with properties
2
+ export class FindUsersQuery {
3
+ constructor ( props : FindUsersQuery ) {
4
+ this . country = props . country ;
5
+ this . postalCode = props . postalCode ;
6
+ this . street = props . street ;
7
+ }
8
+
9
+ readonly country : string ;
10
+
11
+ readonly postalCode : string ;
12
+
13
+ readonly street : string ;
14
+ }
Original file line number Diff line number Diff line change
1
+ import { ApiProperty } from '@nestjs/swagger' ;
2
+ import { MaxLength , IsString , IsAlpha , IsAlphanumeric } from 'class-validator' ;
3
+ import { FindUsers } from 'src/interface-adapters/interfaces/user/find-users.interface' ;
4
+
5
+ export class FindUsersRequest implements FindUsers {
6
+ @ApiProperty ( { example : 'France' , description : 'Country of residence' } )
7
+ @MaxLength ( 50 )
8
+ @IsString ( )
9
+ @IsAlpha ( )
10
+ readonly country : string ;
11
+
12
+ @ApiProperty ( { example : '28566' , description : 'Postal code' } )
13
+ @MaxLength ( 10 )
14
+ @IsAlphanumeric ( )
15
+ readonly postalCode : string ;
16
+
17
+ @ApiProperty ( { example : 'Grande Rue' , description : 'Street' } )
18
+ @MaxLength ( 50 )
19
+ @IsAlphanumeric ( )
20
+ readonly street : string ;
21
+ }
22
+
23
+ export class FindUsersHttpRequest extends FindUsersRequest
24
+ implements FindUsers { }
Original file line number Diff line number Diff line change @@ -3,21 +3,21 @@ import { TypeOrmModule } from '@nestjs/typeorm';
3
3
import { UserOrmEntity } from './database/user.orm-entity' ;
4
4
import { UserRepository } from './database/user.repository' ;
5
5
import { CreateUserHttpController } from './commands/create-user/create-user.http.controller' ;
6
- import { FindUserByEmailHttpController } from './queries/find-user-by-email/find-user-by-email.http.controller' ;
7
6
import { DeleteUserHttpController } from './commands/delete-user/delete-user.controller' ;
8
7
import {
9
8
createUserCliLoggerProvider ,
10
9
createUserProvider ,
11
10
removeUserProvider ,
12
11
} from './user.providers' ;
13
12
import { CreateUserCliController } from './commands/create-user/create-user.cli.controller' ;
13
+ import { FindUsersHttpController } from './queries/find-users/find-users.http.controller' ;
14
14
15
15
@Module ( {
16
16
imports : [ TypeOrmModule . forFeature ( [ UserOrmEntity ] ) ] ,
17
17
controllers : [
18
18
CreateUserHttpController ,
19
19
DeleteUserHttpController ,
20
- FindUserByEmailHttpController ,
20
+ FindUsersHttpController ,
21
21
] ,
22
22
providers : [
23
23
UserRepository ,
Original file line number Diff line number Diff line change 7
7
"emitDecoratorMetadata" : true ,
8
8
"experimentalDecorators" : true ,
9
9
"allowSyntheticDefaultImports" : true ,
10
+ "strictPropertyInitialization" : false ,
10
11
"target" : " es2019" ,
11
12
"sourceMap" : true ,
12
13
"outDir" : " ./dist" ,
0 commit comments