├── .dockerignore
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── apps
├── auth
│ ├── Dockerfile
│ ├── src
│ │ ├── auth.controller.spec.ts
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── guards
│ │ │ └── local-auth.guard.ts
│ │ ├── main.ts
│ │ ├── strategies
│ │ │ └── local.strategy.ts
│ │ └── user
│ │ │ ├── dto
│ │ │ ├── create-user.dto.ts
│ │ │ └── update-user.dto.ts
│ │ │ ├── graphql.ts
│ │ │ ├── models
│ │ │ └── user.schema.ts
│ │ │ ├── user.graphql
│ │ │ ├── user.module.ts
│ │ │ ├── user.repository.ts
│ │ │ ├── user.resolver.spec.ts
│ │ │ ├── user.resolver.ts
│ │ │ ├── user.service.spec.ts
│ │ │ └── user.service.ts
│ ├── test
│ │ ├── app.e2e-spec.ts
│ │ └── jest-e2e.json
│ └── tsconfig.app.json
└── reservations
│ ├── Dockerfile
│ ├── src
│ ├── dto
│ │ ├── create-reservation.dto.ts
│ │ └── update-reservation.dto.ts
│ ├── main.ts
│ ├── models
│ │ └── reservation.schema.ts
│ ├── reservations.controller.spec.ts
│ ├── reservations.controller.ts
│ ├── reservations.module.ts
│ ├── reservations.repository.ts
│ ├── reservations.service.spec.ts
│ └── reservations.service.ts
│ ├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
│ └── tsconfig.app.json
├── docker-compose.yaml
├── libs
└── common
│ ├── src
│ ├── database
│ │ ├── abstract.repository.ts
│ │ ├── abstract.schema.ts
│ │ ├── database.module.ts
│ │ └── index.ts
│ ├── decorators
│ │ ├── current-user.decorator.ts
│ │ ├── gql-current-user.decorator.ts
│ │ ├── index.ts
│ │ └── roles.decorator.ts
│ ├── enums
│ │ ├── index.ts
│ │ └── roles.enum.ts
│ ├── guards
│ │ ├── gql-jwt-auth.guard.ts
│ │ ├── index.ts
│ │ └── jwt-auth.guard.ts
│ ├── index.ts
│ ├── interfaces
│ │ ├── index.ts
│ │ └── token-payload.interface.ts
│ ├── logger
│ │ ├── index.ts
│ │ └── logger.module.ts
│ └── strategies
│ │ ├── index.ts
│ │ └── jwt.strategy.ts
│ └── tsconfig.lib.json
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | ### Node template
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 | .pnpm-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Snowpack dependency directory (https://snowpack.dev/)
47 | web_modules/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Optional stylelint cache
59 | .stylelintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variable files
77 |
78 | .env.development.local
79 | .env.test.local
80 | .env.production.local
81 | .env.local
82 |
83 | # parcel-bundler cache (https://parceljs.org/)
84 | .cache
85 | .parcel-cache
86 |
87 | # Next.js build output
88 | .next
89 | out
90 |
91 | # Nuxt.js build / generate output
92 | .nuxt
93 | dist
94 |
95 | # Gatsby files
96 | .cache/
97 | # Comment in the public line in if your project uses Gatsby and not Next.js
98 | # https://nextjs.org/blog/next-9-1#public-directory-support
99 | # public
100 |
101 | # vuepress build output
102 | .vuepress/dist
103 |
104 | # vuepress v2.x temp and cache directory
105 | .temp
106 | .cache
107 |
108 | # Docusaurus cache and generated files
109 | .docusaurus
110 |
111 | # Serverless directories
112 | .serverless/
113 |
114 | # FuseBox cache
115 | .fusebox/
116 |
117 | # DynamoDB Local files
118 | .dynamodb/
119 |
120 | # TernJS port file
121 | .tern-port
122 |
123 | # Stores VSCode versions used for testing VSCode extensions
124 | .vscode-test
125 |
126 | # yarn v2
127 | .yarn/cache
128 | .yarn/unplugged
129 | .yarn/build-state.yml
130 | .yarn/install-state.gz
131 | .pnp.*
132 |
133 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir: __dirname,
6 | sourceType: 'module',
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node template
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 | .pnpm-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Snowpack dependency directory (https://snowpack.dev/)
47 | web_modules/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Optional stylelint cache
59 | .stylelintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variable files
77 | .env
78 | .env.development.local
79 | .env.test.local
80 | .env.production.local
81 | .env.local
82 |
83 | # parcel-bundler cache (https://parceljs.org/)
84 | .cache
85 | .parcel-cache
86 |
87 | # Next.js build output
88 | .next
89 | out
90 |
91 | # Nuxt.js build / generate output
92 | .nuxt
93 | dist
94 |
95 | # Gatsby files
96 | .cache/
97 | # Comment in the public line in if your project uses Gatsby and not Next.js
98 | # https://nextjs.org/blog/next-9-1#public-directory-support
99 | # public
100 |
101 | # vuepress build output
102 | .vuepress/dist
103 |
104 | # vuepress v2.x temp and cache directory
105 | .temp
106 | .cache
107 |
108 | # Docusaurus cache and generated files
109 | .docusaurus
110 |
111 | # Serverless directories
112 | .serverless/
113 |
114 | # FuseBox cache
115 | .fusebox/
116 |
117 | # DynamoDB Local files
118 | .dynamodb/
119 |
120 | # TernJS port file
121 | .tern-port
122 |
123 | # Stores VSCode versions used for testing VSCode extensions
124 | .vscode-test
125 |
126 | # yarn v2
127 | .yarn/cache
128 | .yarn/unplugged
129 | .yarn/build-state.yml
130 | .yarn/install-state.gz
131 | .pnp.*
132 | .idea
133 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
6 | [circleci-url]: https://circleci.com/gh/nestjs/nest
7 |
8 | A progressive Node.js framework for building efficient and scalable server-side applications.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 | ## Description
26 |
27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
28 |
29 | ## Installation
30 |
31 | ```bash
32 | $ npm install
33 | ```
34 |
35 | ## Running the app
36 |
37 | ```bash
38 | # development
39 | $ npm run start
40 |
41 | # watch mode
42 | $ npm run start:dev
43 |
44 | # production mode
45 | $ npm run start:prod
46 | ```
47 |
48 | ## Test
49 |
50 | ```bash
51 | # unit tests
52 | $ npm run test
53 |
54 | # e2e tests
55 | $ npm run test:e2e
56 |
57 | # test coverage
58 | $ npm run test:cov
59 | ```
60 |
61 | ## Support
62 |
63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
64 |
65 | ## Stay in touch
66 |
67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
68 | - Website - [https://nestjs.com](https://nestjs.com/)
69 | - Twitter - [@nestframework](https://twitter.com/nestframework)
70 |
71 | ## License
72 |
73 | Nest is [MIT licensed](LICENSE).
74 |
--------------------------------------------------------------------------------
/apps/auth/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine as development
2 |
3 | WORKDIR usr/src/app
4 |
5 | COPY package.json ./
6 | COPY package-lock.json ./
7 |
8 | RUN npm install
9 | COPY . .
10 | RUN npm run build
11 |
12 | FROM node:alpine as production
13 | ARG NODE_ENV=production
14 |
15 | ENV NODE_ENV=${NODE_ENV}
16 | WORKDIR usr/src/app
17 |
18 |
19 | COPY package.json ./
20 | COPY package-lock.json ./
21 |
22 | RUN npm install --prod
23 | COPY --from=development /usr/src/app/dist ./dist
24 | CMD ["node","dist/apps/auth/main"]
--------------------------------------------------------------------------------
/apps/auth/src/auth.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { AuthController } from './auth.controller';
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthController', () => {
6 | let authController: AuthController;
7 |
8 | beforeEach(async () => {
9 | const app: TestingModule = await Test.createTestingModule({
10 | controllers: [AuthController],
11 | providers: [AuthService],
12 | }).compile();
13 |
14 | authController = app.get(AuthController);
15 | });
16 |
17 | describe('root', () => {
18 | it('should return "Hello World!"', () => {
19 | expect(authController.getHello()).toBe('Hello World!');
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/apps/auth/src/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Post, Res, UseGuards } from '@nestjs/common';
2 | import { AuthService } from './auth.service';
3 | import { LocalAuthGuard } from './guards/local-auth.guard';
4 |
5 | import { UserDocument } from './user/models/user.schema';
6 | import { Response } from 'express';
7 | import { CurrentUser } from '@app/common';
8 |
9 | @Controller()
10 | export class AuthController {
11 | constructor(private readonly authService: AuthService) {}
12 | @UseGuards(LocalAuthGuard)
13 | @Post('/login')
14 | async login(
15 | @CurrentUser() user: UserDocument,
16 | @Res({ passthrough: true }) response: Response,
17 | ) {
18 | await this.authService.login(user, response);
19 | response.send(user);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/apps/auth/src/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, ValidationPipe } from '@nestjs/common';
2 | import { AuthController } from './auth.controller';
3 | import { AuthService } from './auth.service';
4 | import { UserModule } from './user/user.module';
5 | import { APP_PIPE } from '@nestjs/core';
6 | import { JwtStrategy, LoggerModule } from '@app/common';
7 | import { ConfigModule, ConfigService } from '@nestjs/config';
8 | import * as Joi from 'joi';
9 | import { JwtModule } from '@nestjs/jwt';
10 | import { LocalStrategy } from './strategies/local.strategy';
11 |
12 | @Module({
13 | imports: [
14 | UserModule,
15 | LoggerModule,
16 | ConfigModule.forRoot({
17 | validationSchema: Joi.object({
18 | MONGODB_URI: Joi.string().required(),
19 | NODE_ENV: Joi.string()
20 | .valid('development', 'production', 'test')
21 | .required(),
22 | JWT_SECRET: Joi.string().required(),
23 | JWT_EXPIRATION: Joi.number().required(),
24 | PORT: Joi.number().port().required(),
25 | }),
26 | isGlobal: true,
27 | }),
28 | JwtModule.registerAsync({
29 | useFactory: (config: ConfigService) => ({
30 | secret: config.get('JWT_SECRET'),
31 | signOptions: {
32 | expiresIn: config.get('JWT_EXPIRATION'),
33 | },
34 | }),
35 | inject: [ConfigService],
36 | }),
37 | ],
38 | controllers: [AuthController],
39 | providers: [
40 | AuthService,
41 | LocalStrategy,
42 | JwtStrategy,
43 | { provide: APP_PIPE, useValue: new ValidationPipe({ whitelist: true }) },
44 | ],
45 | })
46 | export class AuthModule {}
47 |
--------------------------------------------------------------------------------
/apps/auth/src/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { UserDocument } from './user/models/user.schema';
3 | import { Response } from 'express';
4 | import { ConfigService } from '@nestjs/config';
5 | import { JwtService } from '@nestjs/jwt';
6 | import { TokenPayloadInterface } from '@app/common';
7 |
8 | @Injectable()
9 | export class AuthService {
10 | constructor(
11 | private readonly configService: ConfigService,
12 | private readonly jwtService: JwtService,
13 | ) {}
14 | async login(user: UserDocument, response: Response) {
15 | const tokenPayload: TokenPayloadInterface = {
16 | id: user._id.toHexString(),
17 | };
18 |
19 | const expires = new Date();
20 | expires.setSeconds(
21 | expires.getSeconds() + this.configService.get('JWT_EXPIRATION'),
22 | );
23 |
24 | const token: string = this.jwtService.sign(tokenPayload);
25 |
26 | //response.cookie('Authentication', token, { expires, httpOnly: true });
27 | response.setHeader('Authorization', 'Bearer ' + token);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/apps/auth/src/guards/local-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { AuthGuard } from '@nestjs/passport';
3 |
4 | @Injectable()
5 | export class LocalAuthGuard extends AuthGuard('local') {}
6 |
--------------------------------------------------------------------------------
/apps/auth/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { AuthModule } from './auth.module';
3 | import { Logger } from 'nestjs-pino';
4 | import { ConfigService } from '@nestjs/config';
5 |
6 | async function bootstrap() {
7 | const app = await NestFactory.create(AuthModule);
8 | const config = app.get(ConfigService);
9 | app.useLogger(app.get(Logger));
10 | await app.listen(config.get('PORT'));
11 | }
12 | bootstrap();
13 |
--------------------------------------------------------------------------------
/apps/auth/src/strategies/local.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, UnauthorizedException } from '@nestjs/common';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { Strategy } from 'passport-local';
4 | import { UserService } from '../user/user.service';
5 |
6 | @Injectable()
7 | export class LocalStrategy extends PassportStrategy(Strategy) {
8 | constructor(private readonly userService: UserService) {
9 | super({ usernameField: 'email' });
10 | }
11 |
12 | async validate(email: string, password: string) {
13 | try {
14 | return this.userService.verifyUser(email, password);
15 | } catch (e) {
16 | throw new UnauthorizedException(e);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apps/auth/src/user/dto/create-user.dto.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IsArray,
3 | IsEmail,
4 | IsEnum,
5 | IsOptional,
6 | IsString,
7 | IsStrongPassword,
8 | } from 'class-validator';
9 | import { CreateUserInput } from '../graphql';
10 | import { UserRoles } from '@app/common';
11 |
12 | export class CreateUserDto extends CreateUserInput {
13 | @IsEmail()
14 | email: string;
15 |
16 | @IsStrongPassword()
17 | password: string;
18 |
19 | @IsOptional()
20 | @IsArray({ each: true })
21 | @IsString({ each: true })
22 | @IsEnum(UserRoles)
23 | roles: UserRoles[];
24 | }
25 |
--------------------------------------------------------------------------------
/apps/auth/src/user/dto/update-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { CreateUserDto } from './create-user.dto';
2 | import { PartialType } from '@nestjs/mapped-types';
3 |
4 | export class UpdateUserDto extends PartialType(CreateUserDto) {
5 | id: string;
6 | }
7 |
--------------------------------------------------------------------------------
/apps/auth/src/user/graphql.ts:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * -------------------------------------------------------
4 | * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
5 | * -------------------------------------------------------
6 | */
7 |
8 | /* tslint:disable */
9 | /* eslint-disable */
10 |
11 | export class CreateUserInput {
12 | email: string;
13 | password: string;
14 | }
15 |
16 | export class UpdateUserInput {
17 | id: string;
18 | email?: Nullable;
19 | password?: Nullable;
20 | }
21 |
22 | export class User {
23 | id?: Nullable;
24 | email?: Nullable;
25 | }
26 |
27 | export abstract class IQuery {
28 | abstract users(): Nullable[] | Promise[]>;
29 |
30 | abstract user(id: string): Nullable | Promise>;
31 |
32 | abstract currentUser(): Nullable | Promise>;
33 | }
34 |
35 | export abstract class IMutation {
36 | abstract createUser(createUserInput: CreateUserInput): User | Promise;
37 |
38 | abstract updateUser(updateUserInput: UpdateUserInput): User | Promise;
39 |
40 | abstract removeUser(id: string): Nullable | Promise>;
41 | }
42 |
43 | type Nullable = T | null;
44 |
--------------------------------------------------------------------------------
/apps/auth/src/user/models/user.schema.ts:
--------------------------------------------------------------------------------
1 | import { AbstractDocument } from '@app/common';
2 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
3 | import { UserRoles } from '@app/common/enums';
4 | @Schema({ id: true, versionKey: false })
5 | export class UserDocument extends AbstractDocument {
6 | @Prop({ type: String, required: true, unique: true })
7 | email: string;
8 |
9 | @Prop({ type: String, required: true })
10 | password: string;
11 |
12 | @Prop({ type: [UserRoles], required: true, default: [UserRoles.user] })
13 | roles: UserRoles[];
14 | }
15 |
16 | export const UserSchema = SchemaFactory.createForClass(UserDocument);
17 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.graphql:
--------------------------------------------------------------------------------
1 | enum Roles {
2 | admin
3 | user
4 | }
5 |
6 | type User {
7 | id: String
8 | email: String
9 | roles: [Roles]
10 | }
11 |
12 | input CreateUserInput {
13 | email: String!
14 | password: String!
15 | roles: [Roles]
16 | }
17 |
18 | input UpdateUserInput {
19 | id: String!
20 | email: String
21 | password: String
22 | roles: [Roles]
23 | }
24 |
25 | type Query {
26 | users: [User]!
27 | user(id: String!): User
28 | currentUser: User
29 | }
30 |
31 | type Mutation {
32 | createUser(createUserInput: CreateUserInput!): User!
33 | updateUser(updateUserInput: UpdateUserInput!): User!
34 | removeUser(id: String!): User
35 | }
36 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { UserService } from './user.service';
3 | import { UserResolver } from './user.resolver';
4 | import { GraphQLModule } from '@nestjs/graphql';
5 | import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
6 | import { join } from 'path';
7 | import { DatabaseModule } from '@app/common';
8 | import { UserDocument, UserSchema } from './models/user.schema';
9 | import { UserRepository } from './user.repository';
10 |
11 | @Module({
12 | imports: [
13 | DatabaseModule,
14 | DatabaseModule.forFeature([
15 | { name: UserDocument.name, schema: UserSchema },
16 | ]),
17 | GraphQLModule.forRoot({
18 | driver: ApolloDriver,
19 | typePaths: ['./**/*.graphql'],
20 | definitions: {
21 | path: join(process.cwd(), 'apps/auth/src/user/graphql.ts'),
22 | outputAs: 'class',
23 | },
24 | }),
25 | ],
26 | providers: [UserResolver, UserService, UserRepository],
27 | exports: [UserService],
28 | })
29 | export class UserModule {}
30 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.repository.ts:
--------------------------------------------------------------------------------
1 | import { AbstractRepository } from '@app/common';
2 | import { UserDocument } from './models/user.schema';
3 | import { Model } from 'mongoose';
4 | import { InjectModel } from '@nestjs/mongoose';
5 | import { Injectable, Logger } from '@nestjs/common';
6 |
7 | @Injectable()
8 | export class UserRepository extends AbstractRepository {
9 | protected readonly logger: Logger = new Logger(UserRepository.name);
10 | constructor(
11 | @InjectModel(UserDocument.name)
12 | private readonly userModel: Model,
13 | ) {
14 | super(userModel);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.resolver.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { UserResolver } from './user.resolver';
3 | import { UserService } from './user.service';
4 |
5 | describe('UserResolver', () => {
6 | let resolver: UserResolver;
7 |
8 | beforeEach(async () => {
9 | const module: TestingModule = await Test.createTestingModule({
10 | providers: [UserResolver, UserService],
11 | }).compile();
12 |
13 | resolver = module.get(UserResolver);
14 | });
15 |
16 | it('should be defined', () => {
17 | expect(resolver).toBeDefined();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
2 | import { UserService } from './user.service';
3 | import { CreateUserDto } from './dto/create-user.dto';
4 | import { UpdateUserDto } from './dto/update-user.dto';
5 | import { UseGuards } from '@nestjs/common';
6 | import {
7 | GqlCurrentUser,
8 | GqlJwtAuthGuard,
9 | Roles,
10 | TokenPayloadInterface,
11 | UserRoles,
12 | } from '@app/common';
13 |
14 | @Resolver('User')
15 | export class UserResolver {
16 | constructor(private readonly userService: UserService) {}
17 |
18 | @Mutation('createUser')
19 | @UseGuards(GqlJwtAuthGuard)
20 | @Roles(UserRoles.admin)
21 | create(@Args('createUserInput') createUserInput: CreateUserDto) {
22 | return this.userService.create(createUserInput);
23 | }
24 |
25 | @Query('users')
26 | @UseGuards(GqlJwtAuthGuard)
27 | findAll() {
28 | return this.userService.findAll();
29 | }
30 |
31 | @Query('user')
32 | @UseGuards(GqlJwtAuthGuard)
33 | findOne(@Args('id') id: string) {
34 | return this.userService.findOne(id);
35 | }
36 |
37 | @Query('currentUser')
38 | @UseGuards(GqlJwtAuthGuard)
39 | currentUser(@GqlCurrentUser() user: TokenPayloadInterface) {
40 | return this.userService.findOne(user.id);
41 | }
42 |
43 | @Mutation('updateUser')
44 | @UseGuards(GqlJwtAuthGuard)
45 | update(@Args('updateUserInput') updateUserInput: UpdateUserDto) {
46 | return this.userService.update(updateUserInput.id, updateUserInput);
47 | }
48 |
49 | @Mutation('removeUser')
50 | @UseGuards(GqlJwtAuthGuard)
51 | remove(@Args('id') id: string) {
52 | return this.userService.remove(id);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { UserService } from './user.service';
3 |
4 | describe('UserService', () => {
5 | let service: UserService;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | providers: [UserService],
10 | }).compile();
11 |
12 | service = module.get(UserService);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(service).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/apps/auth/src/user/user.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Injectable,
3 | UnauthorizedException,
4 | UnprocessableEntityException,
5 | } from '@nestjs/common';
6 | import { CreateUserDto } from './dto/create-user.dto';
7 | import { UpdateUserDto } from './dto/update-user.dto';
8 | import { UserRepository } from './user.repository';
9 | import { UserDocument } from './models/user.schema';
10 | import * as bcrypt from 'bcrypt';
11 | import { User } from './graphql';
12 |
13 | @Injectable()
14 | export class UserService {
15 | constructor(private readonly userRepository: UserRepository) {}
16 | async create(createUserInput: CreateUserDto): Promise {
17 | await this.validateCreateUserDto(createUserInput);
18 | const passwordHashed: string = await bcrypt.hash(
19 | createUserInput.password,
20 | 12,
21 | );
22 | return this.userRepository.create({
23 | ...createUserInput,
24 | password: passwordHashed,
25 | });
26 | }
27 |
28 | findAll(): Promise {
29 | return this.userRepository.find({});
30 | }
31 |
32 | findOne(id: string) {
33 | return this.userRepository.findOne({ _id: id });
34 | }
35 |
36 | update(id: string, updateUserInput: UpdateUserDto): Promise {
37 | return this.userRepository.findOneAndUpdate(
38 | { _id: id },
39 | { $set: { ...updateUserInput } },
40 | );
41 | }
42 |
43 | remove(id: string): Promise {
44 | return this.userRepository.findOneAndDelete({ _id: id });
45 | }
46 |
47 | async verifyUser(email: string, password: string): Promise {
48 | const user = await this.userRepository.findOne({ email });
49 | const passCheck = await bcrypt.compare(password, user.password);
50 | if (!passCheck)
51 | throw new UnauthorizedException('User or password is incorrect');
52 | return user;
53 | }
54 |
55 | private async validateCreateUserDto(createUserDto: CreateUserDto) {
56 | try {
57 | await this.userRepository.findOne({ email: createUserDto.email });
58 | } catch (e) {
59 | return;
60 | }
61 | throw new UnprocessableEntityException('Email already exists');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/apps/auth/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AuthModule } from './../src/auth.module';
5 |
6 | describe('AuthController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AuthModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/apps/auth/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/auth/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": false,
5 | "outDir": "../../dist/apps/auth"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/reservations/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine as development
2 |
3 | WORKDIR usr/src/app
4 |
5 | COPY package.json ./
6 | COPY package-lock.json ./
7 |
8 | RUN npm install
9 | COPY . .
10 | RUN npm run build
11 |
12 | FROM node:alpine as production
13 | ARG NODE_ENV=production
14 |
15 | ENV NODE_ENV=${NODE_ENV}
16 | WORKDIR usr/src/app
17 |
18 |
19 | COPY package.json ./
20 | COPY package-lock.json ./
21 |
22 | RUN npm install --prod
23 | COPY --from=development /usr/src/app/dist ./dist
24 | CMD ["node","dist/apps/reservations/main"]
--------------------------------------------------------------------------------
/apps/reservations/src/dto/create-reservation.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsDate, IsString } from 'class-validator';
2 | import { Type } from 'class-transformer';
3 |
4 | export class CreateReservationDto {
5 | @IsDate()
6 | @Type(() => Date)
7 | timestamp: Date;
8 |
9 | @IsDate()
10 | @Type(() => Date)
11 | startDate: Date;
12 |
13 | @IsDate()
14 | @Type(() => Date)
15 | endDate: Date;
16 |
17 | @IsString()
18 | userId: string;
19 |
20 | @IsString()
21 | placeId: string;
22 |
23 | @IsString()
24 | invoiceId: string;
25 | }
26 |
--------------------------------------------------------------------------------
/apps/reservations/src/dto/update-reservation.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/mapped-types';
2 | import { CreateReservationDto } from './create-reservation.dto';
3 |
4 | export class UpdateReservationDto extends PartialType(CreateReservationDto) {}
5 |
--------------------------------------------------------------------------------
/apps/reservations/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core';
2 | import { ReservationsModule } from './reservations.module';
3 | import { Logger } from 'nestjs-pino';
4 | import { ConfigService } from '@nestjs/config';
5 |
6 | async function bootstrap() {
7 | const app = await NestFactory.create(ReservationsModule);
8 | const config = app.get(ConfigService);
9 | app.useLogger(app.get(Logger));
10 | await app.listen(config.get('PORT'));
11 | }
12 | bootstrap();
13 |
--------------------------------------------------------------------------------
/apps/reservations/src/models/reservation.schema.ts:
--------------------------------------------------------------------------------
1 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
2 | import { AbstractDocument } from '@app/common';
3 |
4 | @Schema({ versionKey: false })
5 | export class ReservationDocument extends AbstractDocument {
6 | @Prop({ required: true, type: Date })
7 | timestamp: Date;
8 |
9 | @Prop({ required: true, type: Date })
10 | startDate: Date;
11 |
12 | @Prop({ required: true, type: Date })
13 | endDate: Date;
14 |
15 | @Prop({
16 | type: String,
17 | required: true,
18 | })
19 | userId: string;
20 |
21 | @Prop({
22 | type: String,
23 | required: true,
24 | })
25 | placeId: string;
26 |
27 | @Prop({
28 | type: String,
29 | required: true,
30 | })
31 | invoiceId: string;
32 | }
33 |
34 | export const ReservationSchema =
35 | SchemaFactory.createForClass(ReservationDocument);
36 |
--------------------------------------------------------------------------------
/apps/reservations/src/reservations.controller.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { ReservationsController } from './reservations.controller';
3 | import { ReservationsService } from './reservations.service';
4 |
5 | describe('ReservationsController', () => {
6 | let controller: ReservationsController;
7 |
8 | beforeEach(async () => {
9 | const module: TestingModule = await Test.createTestingModule({
10 | controllers: [ReservationsController],
11 | providers: [ReservationsService],
12 | }).compile();
13 |
14 | controller = module.get(ReservationsController);
15 | });
16 |
17 | it('should be defined', () => {
18 | expect(controller).toBeDefined();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/apps/reservations/src/reservations.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
2 | import { ReservationsService } from './reservations.service';
3 | import { CreateReservationDto } from './dto/create-reservation.dto';
4 | import { UpdateReservationDto } from './dto/update-reservation.dto';
5 |
6 | @Controller('reservations')
7 | export class ReservationsController {
8 | constructor(private readonly reservationsService: ReservationsService) {}
9 |
10 | @Post()
11 | create(@Body() createReservationDto: CreateReservationDto) {
12 | return this.reservationsService.create(createReservationDto);
13 | }
14 |
15 | @Get()
16 | findAll() {
17 | return this.reservationsService.findAll();
18 | }
19 |
20 | @Get(':id')
21 | findOne(@Param('id') id: string) {
22 | return this.reservationsService.findOne(+id);
23 | }
24 |
25 | @Patch(':id')
26 | update(@Param('id') id: string, @Body() updateReservationDto: UpdateReservationDto) {
27 | return this.reservationsService.update(+id, updateReservationDto);
28 | }
29 |
30 | @Delete(':id')
31 | remove(@Param('id') id: string) {
32 | return this.reservationsService.remove(+id);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/apps/reservations/src/reservations.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, ValidationPipe } from '@nestjs/common';
2 | import { ReservationsService } from './reservations.service';
3 | import { ReservationsController } from './reservations.controller';
4 | import { DatabaseModule, LoggerModule } from '@app/common';
5 | import { ReservationsRepository } from './reservations.repository';
6 | import {
7 | ReservationDocument,
8 | ReservationSchema,
9 | } from './models/reservation.schema';
10 | import { APP_PIPE } from '@nestjs/core';
11 | import { ConfigModule } from '@nestjs/config';
12 | import * as Joi from 'joi';
13 |
14 | @Module({
15 | imports: [
16 | DatabaseModule,
17 | DatabaseModule.forFeature([
18 | { name: ReservationDocument.name, schema: ReservationSchema },
19 | ]),
20 | LoggerModule,
21 | ConfigModule.forRoot({
22 | validationSchema: Joi.object({
23 | MONGODB_URI: Joi.string().required(),
24 | NODE_ENV: Joi.string()
25 | .valid('development', 'production', 'test')
26 | .required(),
27 | JWT_SECRET: Joi.string().required(),
28 | JWT_EXPIRATION: Joi.number().required(),
29 | PORT: Joi.number().port().required(),
30 | }),
31 | isGlobal: true,
32 | }),
33 | ],
34 | controllers: [ReservationsController],
35 | providers: [
36 | ReservationsService,
37 | ReservationsRepository,
38 | {
39 | provide: APP_PIPE,
40 | useValue: new ValidationPipe({ whitelist: true, transform: true }),
41 | },
42 | ],
43 | })
44 | export class ReservationsModule {}
45 |
--------------------------------------------------------------------------------
/apps/reservations/src/reservations.repository.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Logger } from '@nestjs/common';
2 | import { AbstractRepository } from '@app/common';
3 | import { ReservationDocument } from './models/reservation.schema';
4 | import { InjectModel } from '@nestjs/mongoose';
5 | import { Model } from 'mongoose';
6 |
7 | @Injectable()
8 | export class ReservationsRepository extends AbstractRepository {
9 | protected readonly logger: Logger = new Logger(ReservationsRepository.name);
10 | constructor(
11 | @InjectModel(ReservationDocument.name)
12 | private reservationModel: Model,
13 | ) {
14 | super(reservationModel);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/reservations/src/reservations.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { ReservationsService } from './reservations.service';
3 |
4 | describe('ReservationsService', () => {
5 | let service: ReservationsService;
6 |
7 | beforeEach(async () => {
8 | const module: TestingModule = await Test.createTestingModule({
9 | providers: [ReservationsService],
10 | }).compile();
11 |
12 | service = module.get(ReservationsService);
13 | });
14 |
15 | it('should be defined', () => {
16 | expect(service).toBeDefined();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/apps/reservations/src/reservations.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { CreateReservationDto } from './dto/create-reservation.dto';
3 | import { UpdateReservationDto } from './dto/update-reservation.dto';
4 | import { ReservationsRepository } from './reservations.repository';
5 |
6 | @Injectable()
7 | export class ReservationsService {
8 | constructor(private repo: ReservationsRepository) {}
9 | create(createReservationDto: CreateReservationDto) {
10 | return this.repo.create(createReservationDto);
11 | }
12 |
13 | findAll() {
14 | return `This action returns all reservations`;
15 | }
16 |
17 | findOne(id: number) {
18 | return `This action returns a #${id} reservation`;
19 | }
20 |
21 | update(id: number, updateReservationDto: UpdateReservationDto) {
22 | return `This action updates a #${id} reservation`;
23 | }
24 |
25 | remove(id: number) {
26 | return `This action removes a #${id} reservation`;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apps/reservations/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { ReservationsModule } from './../src/reservations.module';
5 |
6 | describe('ReservationsController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [ReservationsModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/apps/reservations/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/reservations/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": false,
5 | "outDir": "../../dist/apps/reservations"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | reservations:
3 | build:
4 | context: .
5 | dockerfile: ./apps/reservations/Dockerfile
6 | target: development
7 | command: npm run start:dev reservations
8 | env_file:
9 | - ./apps/reservations/.env
10 | ports:
11 | - 3000:3000
12 | volumes:
13 | - .:/usr/src/app
14 | auth:
15 | build:
16 | context: .
17 | dockerfile: ./apps/auth/Dockerfile
18 | target: development
19 | command: npm run start:dev auth
20 | env_file:
21 | - ./apps/auth/.env
22 | ports:
23 | - 3001:3001
24 | volumes:
25 | - .:/usr/src/app
26 | mongo:
27 | image: mongo
--------------------------------------------------------------------------------
/libs/common/src/database/abstract.repository.ts:
--------------------------------------------------------------------------------
1 | import { AbstractDocument } from '@app/common/database/abstract.schema';
2 | import { FilterQuery, Model, Types, UpdateQuery } from 'mongoose';
3 | import { Logger, NotFoundException } from '@nestjs/common';
4 |
5 | export abstract class AbstractRepository {
6 | protected abstract readonly logger: Logger;
7 | constructor(protected readonly model: Model) {}
8 | async create(document: Omit): Promise {
9 | const createdDocument = new this.model({
10 | ...document,
11 | _id: new Types.ObjectId(),
12 | });
13 | return (await createdDocument.save()).toJSON() as unknown as TDocument;
14 | }
15 |
16 | async findOne(filterQuery: FilterQuery) {
17 | const document = await this.model.findOne(filterQuery, {}, { lean: true });
18 | if (!document) {
19 | this.logger.warn('Document Not Found with this filterQuery', filterQuery);
20 | throw new NotFoundException('Document Not Found!');
21 | }
22 | return document;
23 | }
24 | async find(filterQuery: FilterQuery) {
25 | return this.model.find(filterQuery, {}, { lean: true });
26 | }
27 |
28 | async findOneAndUpdate(
29 | filterQuery: FilterQuery,
30 | updateQuery: UpdateQuery,
31 | ) {
32 | const document = await this.model.findOneAndUpdate(
33 | filterQuery,
34 | updateQuery,
35 | { lean: true, new: true },
36 | );
37 | if (!document) {
38 | this.logger.warn('Document Not Found with this filterQuery', filterQuery);
39 | throw new NotFoundException('Document Not Found!');
40 | }
41 | return document;
42 | }
43 |
44 | async findOneAndDelete(filterQuery: FilterQuery) {
45 | return this.model.findOneAndDelete(filterQuery, { lean: true });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/libs/common/src/database/abstract.schema.ts:
--------------------------------------------------------------------------------
1 | import { Prop, Schema } from '@nestjs/mongoose';
2 | import { Types, SchemaTypes } from 'mongoose';
3 |
4 | @Schema()
5 | export class AbstractDocument {
6 | @Prop({ type: SchemaTypes.ObjectId })
7 | _id: Types.ObjectId;
8 | }
9 |
--------------------------------------------------------------------------------
/libs/common/src/database/database.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 |
3 | import {
4 | ModelDefinition,
5 | MongooseModule,
6 | MongooseModuleFactoryOptions,
7 | } from '@nestjs/mongoose';
8 | import { ConfigService } from '@nestjs/config';
9 |
10 | @Module({
11 | imports: [
12 | MongooseModule.forRootAsync({
13 | inject: [ConfigService],
14 | useFactory: (config: ConfigService): MongooseModuleFactoryOptions => ({
15 | uri: config.get('MONGODB_URI'),
16 | }),
17 | }),
18 | ],
19 | })
20 | export class DatabaseModule {
21 | static forFeature(models: ModelDefinition[]) {
22 | return MongooseModule.forFeature(models);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/libs/common/src/database/index.ts:
--------------------------------------------------------------------------------
1 | export * from './database.module';
2 | export * from './abstract.schema';
3 | export * from './abstract.repository';
4 |
--------------------------------------------------------------------------------
/libs/common/src/decorators/current-user.decorator.ts:
--------------------------------------------------------------------------------
1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2 |
3 | export const CurrentUser = createParamDecorator(
4 | (_data: unknown, ctx: ExecutionContext) => {
5 | return ctx.switchToHttp().getRequest().user;
6 | },
7 | );
8 |
--------------------------------------------------------------------------------
/libs/common/src/decorators/gql-current-user.decorator.ts:
--------------------------------------------------------------------------------
1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2 | import { GqlExecutionContext } from '@nestjs/graphql';
3 |
4 | export const GqlCurrentUser = createParamDecorator(
5 | (_data: unknown, ctx: ExecutionContext) => {
6 | const context = GqlExecutionContext.create(ctx);
7 | return context.getContext().req.user;
8 | },
9 | );
10 |
--------------------------------------------------------------------------------
/libs/common/src/decorators/index.ts:
--------------------------------------------------------------------------------
1 | export * from './current-user.decorator';
2 | export * from './gql-current-user.decorator';
3 | export * from './roles.decorator';
4 |
--------------------------------------------------------------------------------
/libs/common/src/decorators/roles.decorator.ts:
--------------------------------------------------------------------------------
1 | import { SetMetadata } from '@nestjs/common';
2 | import { UserRoles } from '@app/common/enums';
3 |
4 | export const Roles = (...roles: UserRoles[]) => SetMetadata('roles', roles);
5 |
--------------------------------------------------------------------------------
/libs/common/src/enums/index.ts:
--------------------------------------------------------------------------------
1 | export * from './roles.enum';
2 |
--------------------------------------------------------------------------------
/libs/common/src/enums/roles.enum.ts:
--------------------------------------------------------------------------------
1 | export enum UserRoles {
2 | admin = 'admin',
3 | user = 'user',
4 | }
5 |
--------------------------------------------------------------------------------
/libs/common/src/guards/gql-jwt-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { AuthGuard } from '@nestjs/passport';
2 | import { ExecutionContext, Injectable } from '@nestjs/common';
3 | import { GqlExecutionContext } from '@nestjs/graphql';
4 |
5 | @Injectable()
6 | export class GqlJwtAuthGuard extends AuthGuard('jwt') {
7 | getRequest(ctx: ExecutionContext) {
8 | const context = GqlExecutionContext.create(ctx);
9 | return context.getContext().req;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/libs/common/src/guards/index.ts:
--------------------------------------------------------------------------------
1 | export * from './gql-jwt-auth.guard';
2 | export * from './jwt-auth.guard';
3 |
--------------------------------------------------------------------------------
/libs/common/src/guards/jwt-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, Injectable } from '@nestjs/common';
2 | import { AuthGuard } from '@nestjs/passport';
3 | import { Observable } from 'rxjs';
4 | import { Reflector } from '@nestjs/core';
5 | import { TokenPayloadInterface } from '@app/common/interfaces';
6 | import { UserRoles } from '@app/common/enums';
7 |
8 | Injectable();
9 | export class JwtAuthGuard extends AuthGuard('jwt') {
10 | constructor(private readonly reflector: Reflector) {
11 | super();
12 | }
13 | canActivate(
14 | context: ExecutionContext,
15 | ): boolean | Promise | Observable {
16 | const requiredRoles = this.reflector.getAllAndOverride(
17 | 'roles',
18 | [context.getHandler(), context.getClass()],
19 | );
20 |
21 | if (!requiredRoles) {
22 | return true;
23 | }
24 |
25 | const user: TokenPayloadInterface = context
26 | .switchToHttp()
27 | .getRequest().user;
28 |
29 | return requiredRoles.some((role: UserRoles) => user.roles?.includes(role));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/libs/common/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './database';
2 | export * from './logger';
3 | export * from './guards';
4 | export * from './decorators';
5 | export * from './interfaces';
6 | export * from './strategies';
7 | export * from './enums';
8 |
--------------------------------------------------------------------------------
/libs/common/src/interfaces/index.ts:
--------------------------------------------------------------------------------
1 | export * from './token-payload.interface';
2 |
--------------------------------------------------------------------------------
/libs/common/src/interfaces/token-payload.interface.ts:
--------------------------------------------------------------------------------
1 | import { UserRoles } from '@app/common/enums';
2 |
3 | export interface TokenPayloadInterface {
4 | id: string;
5 | roles: UserRoles[];
6 | }
7 |
--------------------------------------------------------------------------------
/libs/common/src/logger/index.ts:
--------------------------------------------------------------------------------
1 | export * from './logger.module';
2 |
--------------------------------------------------------------------------------
/libs/common/src/logger/logger.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { LoggerModule as NestPino } from 'nestjs-pino';
3 |
4 | @Module({
5 | imports: [
6 | NestPino.forRoot({
7 | pinoHttp: {
8 | transport: {
9 | target: 'pino-pretty',
10 | options: {
11 | singleLine: true,
12 | },
13 | },
14 | },
15 | }),
16 | ],
17 | })
18 | export class LoggerModule {}
19 |
--------------------------------------------------------------------------------
/libs/common/src/strategies/index.ts:
--------------------------------------------------------------------------------
1 | export * from './jwt.strategy';
2 |
--------------------------------------------------------------------------------
/libs/common/src/strategies/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, UnauthorizedException } from '@nestjs/common';
2 | import { ExtractJwt, Strategy } from 'passport-jwt';
3 | import { PassportStrategy } from '@nestjs/passport';
4 | import { Request } from 'express';
5 |
6 | import { ConfigService } from '@nestjs/config';
7 | import { TokenPayloadInterface } from '@app/common/interfaces';
8 |
9 | @Injectable()
10 | export class JwtStrategy extends PassportStrategy(Strategy) {
11 | constructor(private configService: ConfigService) {
12 | super({
13 | ignoreExpiration: false,
14 | secretOrKey: configService.get('JWT_SECRET'),
15 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
16 | });
17 | }
18 |
19 | async validate(payload: TokenPayloadInterface) {
20 | return payload;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/libs/common/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "outDir": "../../dist/libs/common"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "apps/reservations/src",
5 | "compilerOptions": {
6 | "deleteOutDir": true,
7 | "webpack": true,
8 | "tsConfigPath": "apps/reservations/tsconfig.app.json"
9 | },
10 | "projects": {
11 | "common": {
12 | "type": "library",
13 | "root": "libs/common",
14 | "entryFile": "index",
15 | "sourceRoot": "libs/common/src",
16 | "compilerOptions": {
17 | "tsConfigPath": "libs/common/tsconfig.lib.json"
18 | }
19 | },
20 | "reservations": {
21 | "type": "application",
22 | "root": "apps/reservations",
23 | "entryFile": "main",
24 | "sourceRoot": "apps/reservations/src",
25 | "compilerOptions": {
26 | "tsConfigPath": "apps/reservations/tsconfig.app.json"
27 | }
28 | },
29 | "auth": {
30 | "type": "application",
31 | "root": "apps/auth",
32 | "entryFile": "main",
33 | "sourceRoot": "apps/auth/src",
34 | "compilerOptions": {
35 | "tsConfigPath": "apps/auth/tsconfig.app.json"
36 | }
37 | }
38 | },
39 | "monorepo": true,
40 | "root": "apps/reservations"
41 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "micpro",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "build": "nest build",
10 | "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
11 | "start": "nest start",
12 | "start:dev": "nest start --watch",
13 | "start:debug": "nest start --debug --watch",
14 | "start:prod": "node dist/apps/micpro/main",
15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
16 | "test": "jest",
17 | "test:watch": "jest --watch",
18 | "test:cov": "jest --coverage",
19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20 | "test:e2e": "jest --config ./apps/micpro/test/jest-e2e.json"
21 | },
22 | "dependencies": {
23 | "@apollo/server": "^4.7.1",
24 | "@nestjs/apollo": "^11.0.6",
25 | "@nestjs/common": "^9.0.0",
26 | "@nestjs/config": "^2.3.1",
27 | "@nestjs/core": "^9.0.0",
28 | "@nestjs/graphql": "^11.0.6",
29 | "@nestjs/jwt": "^10.0.3",
30 | "@nestjs/mapped-types": "*",
31 | "@nestjs/microservices": "^9.4.2",
32 | "@nestjs/mongoose": "^9.2.2",
33 | "@nestjs/passport": "^9.0.3",
34 | "@nestjs/platform-express": "^9.0.0",
35 | "bcrypt": "^5.1.0",
36 | "class-transformer": "^0.5.1",
37 | "class-validator": "^0.14.0",
38 | "graphql": "^16.6.0",
39 | "joi": "^17.9.2",
40 | "mongoose": "^7.1.0",
41 | "nestjs-pino": "^3.2.0",
42 | "passport": "^0.6.0",
43 | "passport-jwt": "^4.0.1",
44 | "passport-local": "^1.0.0",
45 | "pino-http": "^8.3.3",
46 | "pino-pretty": "^10.0.0",
47 | "reflect-metadata": "^0.1.13",
48 | "rxjs": "^7.2.0"
49 | },
50 | "devDependencies": {
51 | "@nestjs/cli": "^9.0.0",
52 | "@nestjs/schematics": "^9.0.0",
53 | "@nestjs/testing": "^9.0.0",
54 | "@types/bcrypt": "^5.0.0",
55 | "@types/express": "^4.17.13",
56 | "@types/jest": "29.2.4",
57 | "@types/node": "18.11.18",
58 | "@types/passport-jwt": "^3.0.8",
59 | "@types/passport-local": "^1.0.35",
60 | "@types/supertest": "^2.0.11",
61 | "@typescript-eslint/eslint-plugin": "^5.0.0",
62 | "@typescript-eslint/parser": "^5.0.0",
63 | "eslint": "^8.0.1",
64 | "eslint-config-prettier": "^8.3.0",
65 | "eslint-plugin-prettier": "^4.0.0",
66 | "jest": "29.3.1",
67 | "prettier": "^2.3.2",
68 | "source-map-support": "^0.5.20",
69 | "supertest": "^6.1.3",
70 | "ts-jest": "29.0.3",
71 | "ts-loader": "^9.2.3",
72 | "ts-morph": "^18.0.0",
73 | "ts-node": "^10.0.0",
74 | "tsconfig-paths": "4.1.1",
75 | "typescript": "^4.7.4"
76 | },
77 | "jest": {
78 | "moduleFileExtensions": [
79 | "js",
80 | "json",
81 | "ts"
82 | ],
83 | "rootDir": ".",
84 | "testRegex": ".*\\.spec\\.ts$",
85 | "transform": {
86 | "^.+\\.(t|j)s$": "ts-jest"
87 | },
88 | "collectCoverageFrom": [
89 | "**/*.(t|j)s"
90 | ],
91 | "coverageDirectory": "./coverage",
92 | "testEnvironment": "node",
93 | "roots": [
94 | "/libs/",
95 | "/apps/"
96 | ],
97 | "moduleNameMapper": {
98 | "^@app/common(|/.*)$": "/libs/common/src/$1"
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "es2017",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "baseUrl": "./",
13 | "incremental": true,
14 | "skipLibCheck": true,
15 | "strictNullChecks": false,
16 | "noImplicitAny": false,
17 | "strictBindCallApply": false,
18 | "forceConsistentCasingInFileNames": false,
19 | "noFallthroughCasesInSwitch": false,
20 | "paths": {
21 | "@app/common": [
22 | "libs/common/src"
23 | ],
24 | "@app/common/*": [
25 | "libs/common/src/*"
26 | ]
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------