├── .dockerignore ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── apps ├── auth │ ├── Dockerfile │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── auth.controller.ts │ │ ├── auth.module.ts │ │ ├── auth.service.ts │ │ ├── guards │ │ │ ├── jwt-auth.guard.ts │ │ │ └── local-auth.guard.ts │ │ ├── interfaces │ │ │ └── token-payload.interface.ts │ │ ├── main.ts │ │ ├── strategies │ │ │ ├── jwt.strategy.ts │ │ │ └── local.strategy.ts │ │ └── users │ │ │ ├── dto │ │ │ ├── create-user.dto.ts │ │ │ └── update-user.dto.ts │ │ │ ├── users.controller.spec.ts │ │ │ ├── users.controller.ts │ │ │ ├── users.module.ts │ │ │ ├── users.repository.ts │ │ │ ├── users.service.spec.ts │ │ │ └── users.service.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ └── tsconfig.app.json ├── notifications │ ├── Dockerfile │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── main.ts │ │ ├── notifications.controller.spec.ts │ │ ├── notifications.controller.ts │ │ ├── notifications.module.ts │ │ └── notifications.service.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ └── tsconfig.app.json ├── payments │ ├── Dockerfile │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── main.ts │ │ ├── payments.controller.spec.ts │ │ ├── payments.controller.ts │ │ ├── payments.module.ts │ │ └── payments.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 ├── cloudbuild.yaml ├── docker-compose.yaml ├── image.jpg ├── k8s └── sleeper │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── auth │ │ ├── deployment.yaml │ │ ├── service-http.yaml │ │ └── service-tcp.yaml │ ├── notifications │ │ ├── deployment.yaml │ │ └── service.yaml │ ├── payments │ │ ├── deployment.yaml │ │ └── service.yaml │ └── reservations │ │ ├── deployment.yaml │ │ └── service.yaml │ └── values.yaml ├── libs └── common │ ├── src │ ├── auth │ │ ├── index.ts │ │ └── jwt-auth.guard.ts │ ├── constants │ │ ├── index.ts │ │ └── services.ts │ ├── database │ │ ├── abstract.repository.ts │ │ ├── abstract.schema.ts │ │ ├── database.module.ts │ │ ├── index.ts │ │ └── utils │ │ │ └── error-handler.ts │ ├── decorators │ │ ├── current-user.decorator.ts │ │ └── index.ts │ ├── dto │ │ ├── card.dto.ts │ │ ├── create-charge.dto.ts │ │ ├── index.ts │ │ ├── notify-email.dto.ts │ │ ├── payments-create-charge.dto.ts │ │ └── user.dto.ts │ ├── health │ │ ├── health.controller.ts │ │ ├── health.module.ts │ │ └── index.ts │ ├── index.ts │ ├── logger │ │ ├── index.ts │ │ └── logger.module.ts │ └── models │ │ ├── index.ts │ │ └── user.schema.ts │ └── tsconfig.lib.json ├── nest-cli.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.build.json └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | README.md 4 | LICENCE 5 | .vscode 6 | .env.example 7 | .dockerrc 8 | .prettierrc 9 | .eslintrc.js 10 | .gitignore -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MONGODB_URI=mongodb://mongo:27017/db_name 2 | JWT_SECRET= 3 | JWT_EXPIRATION_TIME= 4 | HTTP_PORT= 5 | TCP_PORT= 6 | GOOGLE_OAUTH_CLIENT_ID= 7 | GOOGLE_OAUTH_CLIENT_SECRET= 8 | GOOGLE_OAUTH_REFRESH_TOKEN= 9 | SMTP_USER= -------------------------------------------------------------------------------- /.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 | # compiled output 2 | /dist 3 | **/node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | .dockerrc 30 | 31 | # IDE - VSCode 32 | .vscode 33 | .vscode/* 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json 38 | 39 | # Env files 40 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Augusto da Silva 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | app-image 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 |

9 | GitHub language count 10 | 11 | Repository size 12 | 13 | 14 | GitHub last commit 15 | 16 | 17 | 18 | Repository issues 19 | 20 | 21 | License 22 |

23 | 24 | 26 | 27 | ## ℹ️ Description 28 | 29 | This repo contains a microservices API built with NestJS, Stripe, Nodemailer, JWT, and RabbitMQ. It is a great starting point for building a scalable and secure API. 30 | 31 | ## 🔌 Installation 32 | 33 | If you want to run the apps all you have to do is install the packages and run the server 34 | 35 | ```bash 36 | $ pnpm install 37 | ``` 38 | 39 | ## 🚀 Running the app 40 | 41 | ```bash 42 | $ pnpm start 43 | 44 | # development 45 | $ pnpm start:dev 46 | ``` 47 | 48 | ## 👨🏽‍💻 Author 49 | 50 | [twitter/x](https://twitter.com/carllos_4) 51 | [linkedIn](https://www.linkedin.com/in/augusto-carlos96) 52 | -------------------------------------------------------------------------------- /apps/auth/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine As development 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json ./ 6 | COPY pnpm-workspace.yaml ./ 7 | COPY pnpm-lock.yaml ./ 8 | COPY tsconfig.json tsconfig.json 9 | COPY nest-cli.json nest-cli.json 10 | 11 | RUN npm install -g pnpm 12 | 13 | COPY apps/auth apps/auth 14 | COPY libs libs 15 | 16 | RUN pnpm install 17 | 18 | RUN pnpm run build auth 19 | 20 | FROM node:alpine as production 21 | 22 | ARG NODE_ENV=production 23 | ENV NODE_ENV=${NODE_ENV} 24 | 25 | WORKDIR /usr/src/app 26 | 27 | COPY package.json ./ 28 | COPY pnpm-workspace.yaml ./ 29 | COPY pnpm-lock.yaml ./ 30 | 31 | RUN npm install -g pnpm 32 | 33 | RUN pnpm install --prod 34 | 35 | COPY --from=development /usr/src/app/dist ./dist 36 | 37 | CMD ["node", "dist/apps/auth/main"] -------------------------------------------------------------------------------- /apps/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sleeper/auth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@nestjs/jwt": "^10.1.0", 14 | "@nestjs/passport": "^10.0.0", 15 | "bcryptjs": "^2.4.3", 16 | "passport": "^0.6.0", 17 | "passport-jwt": "^4.0.1", 18 | "passport-local": "^1.0.0" 19 | }, 20 | "devDependencies": { 21 | "@types/bcryptjs": "^2.4.2", 22 | "@types/passport-jwt": "^3.0.9", 23 | "@types/passport-local": "^1.0.35" 24 | } 25 | } -------------------------------------------------------------------------------- /apps/auth/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | -------------------------------------------------------------------------------- /apps/auth/src/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Res, UseGuards } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { Response } from 'express'; 4 | import { LocalAuthGuard } from './guards/local-auth.guard'; 5 | import { JwtAuthGuard } from './guards/jwt-auth.guard'; 6 | import { MessagePattern, Payload } from '@nestjs/microservices'; 7 | import { CurrentUser, UserDocument } from '@app/common'; 8 | 9 | @Controller('auth') 10 | export class AuthController { 11 | constructor(private readonly authService: AuthService) {} 12 | 13 | @UseGuards(LocalAuthGuard) 14 | @Post('login') 15 | async login( 16 | @CurrentUser() user: UserDocument, 17 | @Res({ passthrough: true }) response: Response, 18 | ) { 19 | await this.authService.login(user, response); 20 | response.send(user); 21 | } 22 | 23 | @UseGuards(JwtAuthGuard) 24 | @MessagePattern('authenticate') 25 | async authenticate(@Payload() data: any) { 26 | return data.user; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/auth/src/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthController } from './auth.controller'; 3 | import { AuthService } from './auth.service'; 4 | import { UsersModule } from './users/users.module'; 5 | import { JwtModule } from '@nestjs/jwt'; 6 | import { ConfigModule, ConfigService } from '@nestjs/config'; 7 | import { HealthModule, LoggerModule } from '@app/common'; 8 | import * as Joi from 'joi'; 9 | import { LocalStrategy } from './strategies/local.strategy'; 10 | import { PassportModule } from '@nestjs/passport'; 11 | import { JwtStrategy } from './strategies/jwt.strategy'; 12 | 13 | @Module({ 14 | imports: [ 15 | UsersModule, 16 | LoggerModule, 17 | PassportModule, 18 | HealthModule, 19 | ConfigModule.forRoot({ 20 | isGlobal: true, 21 | validationSchema: Joi.object({ 22 | MONGODB_URI: Joi.string(), 23 | JWT_SECRET: Joi.string(), 24 | JWT_EXPIRATION_TIME: Joi.string(), 25 | HTTP_PORT: Joi.number(), 26 | TCP_PORT: Joi.number(), 27 | }), 28 | }), 29 | JwtModule.registerAsync({ 30 | useFactory: (configService: ConfigService) => ({ 31 | secret: configService.get('JWT_SECRET'), 32 | signOptions: { 33 | expiresIn: `${configService.get('JWT_EXPIRATION_TIME')}s`, 34 | }, 35 | }), 36 | inject: [ConfigService], 37 | }), 38 | ], 39 | controllers: [AuthController], 40 | providers: [AuthService, LocalStrategy, JwtStrategy], 41 | }) 42 | export class AuthModule {} 43 | -------------------------------------------------------------------------------- /apps/auth/src/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { JwtService } from '@nestjs/jwt'; 4 | import { Response } from 'express'; 5 | 6 | @Injectable() 7 | export class AuthService { 8 | constructor( 9 | private readonly configService: ConfigService, 10 | private readonly jwtService: JwtService, 11 | ) {} 12 | 13 | async login(user: any, response: Response) { 14 | const tokenPaylod = { 15 | userId: user._id.toHexString(), 16 | }; 17 | 18 | const expires = new Date(); 19 | expires.setSeconds( 20 | expires.getSeconds() + this.configService.get('JWT_EXPIRATION_TIME'), 21 | ); 22 | 23 | const token = this.jwtService.sign(tokenPaylod); 24 | 25 | response.cookie('Authentication', token, { 26 | httpOnly: true, 27 | expires, 28 | }); 29 | 30 | return token; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/auth/src/guards/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | 3 | export class JwtAuthGuard extends AuthGuard('jwt') {} 4 | -------------------------------------------------------------------------------- /apps/auth/src/guards/local-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | 3 | export class LocalAuthGuard extends AuthGuard('local') {} 4 | -------------------------------------------------------------------------------- /apps/auth/src/interfaces/token-payload.interface.ts: -------------------------------------------------------------------------------- 1 | export interface TokenPayload { 2 | userId: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/auth/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { NestFactory } from '@nestjs/core'; 4 | import { Transport } from '@nestjs/microservices'; 5 | import * as cookieParser from 'cookie-parser'; 6 | import { Logger } from 'nestjs-pino'; 7 | import { AuthModule } from './auth.module'; 8 | 9 | async function bootstrap() { 10 | const app = await NestFactory.create(AuthModule); 11 | const configService = app.get(ConfigService); 12 | 13 | app.connectMicroservice({ 14 | transport: Transport.TCP, 15 | options: { 16 | host: '0.0.0.0', 17 | port: configService.get('TCP_PORT'), 18 | }, 19 | }); 20 | 21 | app.use(cookieParser()); 22 | app.useGlobalPipes(new ValidationPipe({ whitelist: true })); 23 | app.useLogger(app.get(Logger)); 24 | 25 | await app.startAllMicroservices(); 26 | await app.listen(configService.get('HTTP_PORT')); 27 | } 28 | bootstrap(); 29 | -------------------------------------------------------------------------------- /apps/auth/src/strategies/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { ExtractJwt, Strategy } from 'passport-jwt'; 5 | import { UsersService } from '../users/users.service'; 6 | import { TokenPayload } from '../interfaces/token-payload.interface'; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor( 11 | configService: ConfigService, 12 | private readonly usersService: UsersService, 13 | ) { 14 | super({ 15 | jwtFromRequest: ExtractJwt.fromExtractors([ 16 | (req: any) => req?.cookies?.Authentication || req?.Authentication, 17 | ]), 18 | secretOrKey: configService.get('JWT_SECRET'), 19 | }); 20 | } 21 | 22 | async validate({ userId }: TokenPayload) { 23 | return this.usersService.findOne(userId); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/auth/src/strategies/local.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from '@nestjs/passport'; 2 | import { Strategy } from 'passport-local'; 3 | import { UsersService } from '../users/users.service'; 4 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class LocalStrategy extends PassportStrategy(Strategy) { 8 | constructor(private readonly userService: UsersService) { 9 | super({ usernameField: 'email' }); 10 | } 11 | 12 | async validate(email: string, password: string) { 13 | try { 14 | return this.userService.verifyUser(email, password); 15 | } catch (err) { 16 | throw new UnauthorizedException(err); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/auth/src/users/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, MinLength } from 'class-validator'; 2 | 3 | export class CreateUserDto { 4 | @IsEmail() 5 | email: string; 6 | 7 | @IsString() 8 | @MinLength(8) 9 | password: string; 10 | } 11 | -------------------------------------------------------------------------------- /apps/auth/src/users/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { CreateUserDto } from './create-user.dto'; 2 | 3 | export class UpdateUserDto implements Partial {} 4 | -------------------------------------------------------------------------------- /apps/auth/src/users/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersController } from './users.controller'; 3 | 4 | describe('UsersController', () => { 5 | let controller: UsersController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UsersController], 10 | }).compile(); 11 | 12 | controller = module.get(UsersController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /apps/auth/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Post, 7 | Query, 8 | UseGuards, 9 | } from '@nestjs/common'; 10 | import { CreateUserDto } from './dto/create-user.dto'; 11 | import { UsersService } from './users.service'; 12 | import { FilterQuery } from 'mongoose'; 13 | import { JwtAuthGuard } from '../guards/jwt-auth.guard'; 14 | 15 | @Controller('users') 16 | export class UsersController { 17 | constructor(private readonly service: UsersService) {} 18 | 19 | @Post() 20 | async create(@Body() createUserDto: CreateUserDto) { 21 | return this.service.create(createUserDto); 22 | } 23 | 24 | @Get() 25 | @UseGuards(JwtAuthGuard) 26 | getUser(@Query() filterQuery: FilterQuery) { 27 | return this.service.findAll(filterQuery); 28 | } 29 | 30 | @Delete() 31 | delete(_id: string) { 32 | return this.service.remove(_id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/auth/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UsersController } from './users.controller'; 3 | import { UsersService } from './users.service'; 4 | import { DatabaseModule, LoggerModule, UserDocument } from '@app/common'; 5 | import { UsersRepository } from './users.repository'; 6 | 7 | @Module({ 8 | imports: [ 9 | DatabaseModule, 10 | DatabaseModule.forFeature([UserDocument]), 11 | LoggerModule, 12 | ], 13 | controllers: [UsersController], 14 | providers: [UsersService, UsersRepository], 15 | exports: [UsersService], 16 | }) 17 | export class UsersModule {} 18 | -------------------------------------------------------------------------------- /apps/auth/src/users/users.repository.ts: -------------------------------------------------------------------------------- 1 | import { AbstractRepository, UserDocument } from '@app/common'; 2 | import { Injectable, Logger } from '@nestjs/common'; 3 | import { InjectModel } from '@nestjs/mongoose'; 4 | import { Model } from 'mongoose'; 5 | 6 | @Injectable() 7 | export class UsersRepository extends AbstractRepository { 8 | protected readonly logger = new Logger(UsersRepository.name); 9 | 10 | constructor( 11 | @InjectModel(UserDocument.collectionName) 12 | userModel: Model, 13 | ) { 14 | super(userModel); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/auth/src/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UsersService } from './users.service'; 3 | 4 | describe('UsersService', () => { 5 | let service: UsersService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UsersService], 10 | }).compile(); 11 | 12 | service = module.get(UsersService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /apps/auth/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | UnauthorizedException, 4 | UnprocessableEntityException, 5 | } from '@nestjs/common'; 6 | import { CreateUserDto } from './dto/create-user.dto'; 7 | import { UsersRepository } from './users.repository'; 8 | import { FilterQuery } from 'mongoose'; 9 | import { UpdateUserDto } from './dto/update-user.dto'; 10 | import * as bcrypt from 'bcryptjs'; 11 | import { UserDocument } from '@app/common'; 12 | 13 | @Injectable() 14 | export class UsersService { 15 | constructor(private readonly repository: UsersRepository) {} 16 | 17 | async create(createUserDto: CreateUserDto) { 18 | await this.validateCreateUserDto(createUserDto); 19 | return this.repository.create({ 20 | ...createUserDto, 21 | password: await bcrypt.hash(createUserDto.password, 10), 22 | }); 23 | } 24 | 25 | private async validateCreateUserDto(createUserDto: CreateUserDto) { 26 | try { 27 | await this.repository.findOne({ email: createUserDto.email }); 28 | } catch (err) { 29 | return; 30 | } 31 | throw new UnprocessableEntityException('Email already exists.'); 32 | } 33 | 34 | async verifyUser(email: string, password: string): Promise { 35 | const user = await this.repository.findOne({ email }); 36 | 37 | const passwordIsValid = await bcrypt.compare(password, user.password); 38 | if (!passwordIsValid) { 39 | throw new UnauthorizedException('Credentials are not valid'); 40 | } 41 | 42 | return user; 43 | } 44 | 45 | findAll(filterQuery: FilterQuery) { 46 | return this.repository.find(filterQuery); 47 | } 48 | 49 | findOne(_id: string) { 50 | return this.repository.findOne({ _id }); 51 | } 52 | 53 | update(_id: string, updateUserDto: UpdateUserDto) { 54 | return this.repository.findOneAndUpdate({ _id }, { $set: updateUserDto }); 55 | } 56 | 57 | remove(_id: string) { 58 | return this.repository.findOneAndDelete({ _id }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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/notifications/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine As development 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json ./ 6 | COPY pnpm-workspace.yaml ./ 7 | COPY pnpm-lock.yaml ./ 8 | COPY tsconfig.json tsconfig.json 9 | COPY nest-cli.json nest-cli.json 10 | 11 | RUN npm install -g pnpm 12 | 13 | COPY apps/notifications apps/notifications 14 | COPY libs libs 15 | 16 | RUN pnpm install 17 | 18 | RUN pnpm run build notifications 19 | 20 | FROM node:alpine as production 21 | 22 | ARG NODE_ENV=production 23 | ENV NODE_ENV=${NODE_ENV} 24 | 25 | WORKDIR /usr/src/app 26 | 27 | COPY package.json ./ 28 | COPY pnpm-workspace.yaml ./ 29 | COPY pnpm-lock.yaml ./ 30 | 31 | RUN npm install -g pnpm 32 | 33 | RUN pnpm install --prod 34 | 35 | COPY --from=development /usr/src/app/dist ./dist 36 | 37 | CMD ["node", "dist/apps/notifications/main"] -------------------------------------------------------------------------------- /apps/notifications/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sleeper/notifications", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "nodemailer": "^6.9.4" 14 | }, 15 | "devDependencies": { 16 | "@types/nodemailer": "^6.4.9" 17 | } 18 | } -------------------------------------------------------------------------------- /apps/notifications/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | -------------------------------------------------------------------------------- /apps/notifications/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ConfigService } from '@nestjs/config'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { Transport } from '@nestjs/microservices'; 4 | import { Logger } from 'nestjs-pino'; 5 | import { NotificationsModule } from './notifications.module'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(NotificationsModule); 9 | const configService = app.get(ConfigService); 10 | app.connectMicroservice({ 11 | transport: Transport.TCP, 12 | options: { 13 | host: '0.0.0.0', 14 | port: configService.get('TCP_PORT'), 15 | }, 16 | }); 17 | app.useLogger(app.get(Logger)); 18 | await app.startAllMicroservices(); 19 | } 20 | bootstrap(); 21 | -------------------------------------------------------------------------------- /apps/notifications/src/notifications.controller.spec.ts: -------------------------------------------------------------------------------- 1 | // import { Test, TestingModule } from '@nestjs/testing'; 2 | // import { NotificationsController } from './notifications.controller'; 3 | // import { NotificationsService } from './notifications.service'; 4 | 5 | // describe('NotificationsController', () => { 6 | // let notificationsController: NotificationsController; 7 | 8 | // beforeEach(async () => { 9 | // const app: TestingModule = await Test.createTestingModule({ 10 | // controllers: [NotificationsController], 11 | // providers: [NotificationsService], 12 | // }).compile(); 13 | 14 | // notificationsController = app.get(NotificationsController); 15 | // }); 16 | 17 | // describe('root', () => { 18 | // it('should return "Hello World!"', () => { 19 | // expect(notificationsController.getHello()).toBe('Hello World!'); 20 | // }); 21 | // }); 22 | // }); 23 | -------------------------------------------------------------------------------- /apps/notifications/src/notifications.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | import { NotificationsService } from './notifications.service'; 3 | import { EventPattern, Payload } from '@nestjs/microservices'; 4 | import { NotifyEmailDto } from '../../../libs/common/src/dto/notify-email.dto'; 5 | 6 | @Controller() 7 | export class NotificationsController { 8 | constructor(private readonly notificationsService: NotificationsService) {} 9 | 10 | @EventPattern('notify_email') 11 | async notifyEmail(@Payload() data: NotifyEmailDto) { 12 | return this.notificationsService.notifyEmail(data); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/notifications/src/notifications.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { NotificationsController } from './notifications.controller'; 3 | import { NotificationsService } from './notifications.service'; 4 | import { ConfigModule } from '@nestjs/config'; 5 | import * as Joi from 'joi'; 6 | import { LoggerModule } from '@app/common'; 7 | 8 | @Module({ 9 | imports: [ 10 | LoggerModule, 11 | ConfigModule.forRoot({ 12 | isGlobal: true, 13 | validationSchema: Joi.object({ 14 | TCP_PORT: Joi.number(), 15 | GOOGLE_OAUTH_CLIENT_ID: Joi.string(), 16 | GOOGLE_OAUTH_CLIENT_SECRET: Joi.string(), 17 | GOOGLE_OAUTH_REFRESH_TOKEN: Joi.string(), 18 | SMTP_USER: Joi.string(), 19 | }), 20 | }), 21 | ], 22 | controllers: [NotificationsController], 23 | providers: [NotificationsService], 24 | }) 25 | export class NotificationsModule {} 26 | -------------------------------------------------------------------------------- /apps/notifications/src/notifications.service.ts: -------------------------------------------------------------------------------- 1 | import { NotifyEmailDto } from '@app/common'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import * as nodemailer from 'nodemailer'; 5 | 6 | @Injectable() 7 | export class NotificationsService { 8 | constructor(private readonly configService: ConfigService) {} 9 | 10 | private readonly transporter = nodemailer.createTransport({ 11 | service: 'gmail', 12 | auth: { 13 | type: 'OAuth2', 14 | user: this.configService.get('SMTP_USER'), 15 | clientId: this.configService.get('GOOGLE_OAUTH_CLIENT_ID'), 16 | clientSecret: this.configService.get('GOOGLE_OAUTH_CLIENT_SECRET'), 17 | refreshToken: this.configService.get('GOOGLE_OAUTH_REFRESH_TOKEN'), 18 | }, 19 | }); 20 | 21 | async notifyEmail({ email, text, subject }: NotifyEmailDto) { 22 | await this.transporter.sendMail({ 23 | from: this.configService.get('SMTP_USER'), 24 | to: email, 25 | subject, 26 | text, 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/notifications/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 { NotificationsModule } from './../src/notifications.module'; 5 | 6 | describe('NotificationsController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [NotificationsModule], 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/notifications/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/notifications/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "outDir": "../../dist/apps/notifications" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/payments/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine As development 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json ./ 6 | COPY pnpm-workspace.yaml ./ 7 | COPY pnpm-lock.yaml ./ 8 | COPY tsconfig.json tsconfig.json 9 | COPY nest-cli.json nest-cli.json 10 | 11 | RUN npm install -g pnpm 12 | 13 | COPY apps/payments apps/payments 14 | COPY libs libs 15 | 16 | RUN pnpm install 17 | 18 | RUN pnpm run build payments 19 | 20 | FROM node:alpine as production 21 | 22 | ARG NODE_ENV=production 23 | ENV NODE_ENV=${NODE_ENV} 24 | 25 | WORKDIR /usr/src/app 26 | 27 | COPY package.json ./ 28 | COPY pnpm-workspace.yaml ./ 29 | COPY pnpm-lock.yaml ./ 30 | 31 | RUN npm install -g pnpm 32 | 33 | RUN pnpm install --prod 34 | 35 | COPY --from=development /usr/src/app/dist ./dist 36 | 37 | CMD ["node", "dist/apps/payments/main"] -------------------------------------------------------------------------------- /apps/payments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sleeper/payments", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "stripe": "^13.2.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/payments/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | -------------------------------------------------------------------------------- /apps/payments/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ConfigService } from '@nestjs/config'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { Transport } from '@nestjs/microservices'; 4 | import { Logger } from 'nestjs-pino'; 5 | import { PaymentsModule } from './payments.module'; 6 | 7 | async function bootstrap() { 8 | const app = await NestFactory.create(PaymentsModule); 9 | const configService = app.get(ConfigService); 10 | 11 | app.connectMicroservice({ 12 | transport: Transport.TCP, 13 | options: { 14 | host: '0.0.0.0', 15 | port: configService.get('TPC_PORT'), 16 | }, 17 | }); 18 | app.useLogger(app.get(Logger)); 19 | await app.startAllMicroservices(); 20 | } 21 | bootstrap(); 22 | -------------------------------------------------------------------------------- /apps/payments/src/payments.controller.spec.ts: -------------------------------------------------------------------------------- 1 | // import { Test, TestingModule } from '@nestjs/testing'; 2 | // import { PaymentsController } from './payments.controller'; 3 | // import { PaymentsService } from './payments.service'; 4 | 5 | // describe('PaymentsController', () => { 6 | // // let paymentsController: PaymentsController; 7 | 8 | // // beforeEach(async () => { 9 | // // const app: TestingModule = await Test.createTestingModule({ 10 | // // controllers: [PaymentsController], 11 | // // providers: [PaymentsService], 12 | // // }).compile(); 13 | 14 | // // paymentsController = app.get(PaymentsController); 15 | // // }); 16 | 17 | // // describe('root', () => { 18 | // // it('should return "Hello World!"', () => { 19 | // // expect(paymentsController.getHello()).toBe('Hello World!'); 20 | // // }); 21 | // // }); 22 | // }); 23 | -------------------------------------------------------------------------------- /apps/payments/src/payments.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | import { PaymentsService } from './payments.service'; 3 | import { MessagePattern, Payload } from '@nestjs/microservices'; 4 | import { PaymentsCreateChargeDto } from '@app/common'; 5 | 6 | @Controller() 7 | export class PaymentsController { 8 | constructor(private readonly paymentsService: PaymentsService) {} 9 | 10 | @MessagePattern('create_change') 11 | async createChange(@Payload() data: PaymentsCreateChargeDto) { 12 | return this.paymentsService.createChange(data); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/payments/src/payments.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PaymentsController } from './payments.controller'; 3 | import { PaymentsService } from './payments.service'; 4 | import { ConfigModule, ConfigService } from '@nestjs/config'; 5 | import * as Joi from 'joi'; 6 | import { LoggerModule, NOTIFICATIONS_SERVICE } from '@app/common'; 7 | import { ClientsModule, Transport } from '@nestjs/microservices'; 8 | 9 | @Module({ 10 | imports: [ 11 | LoggerModule, 12 | ConfigModule.forRoot({ 13 | isGlobal: true, 14 | validationSchema: Joi.object({ 15 | TPC_PORT: Joi.number(), 16 | NOTIFICATIONS_SERVICE_HOST: Joi.string(), 17 | NOTIFICATIONS_SERVICE_TCP_PORT: Joi.string(), 18 | STRIPE_SECRET_KEY: Joi.string(), 19 | }), 20 | }), 21 | ClientsModule.registerAsync([ 22 | { 23 | name: NOTIFICATIONS_SERVICE, 24 | useFactory: (configService: ConfigService) => ({ 25 | transport: Transport.TCP, 26 | options: { 27 | host: configService.get('NOTIFICATIONS_SERVICE_HOST'), 28 | port: configService.get('NOTIFICATIONS_SERVICE_TCP_PORT'), 29 | }, 30 | }), 31 | inject: [ConfigService], 32 | }, 33 | ]), 34 | ], 35 | controllers: [PaymentsController], 36 | providers: [PaymentsService], 37 | }) 38 | export class PaymentsModule {} 39 | -------------------------------------------------------------------------------- /apps/payments/src/payments.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { Stripe } from 'stripe'; 4 | import { 5 | NOTIFICATIONS_SERVICE, 6 | NotifyEmailDto, 7 | PaymentsCreateChargeDto, 8 | } from '@app/common'; 9 | import { ClientProxy } from '@nestjs/microservices'; 10 | 11 | @Injectable() 12 | export class PaymentsService { 13 | constructor( 14 | private readonly configService: ConfigService, 15 | @Inject(NOTIFICATIONS_SERVICE) 16 | private readonly notificationsService: ClientProxy, 17 | ) {} 18 | 19 | private readonly stripe = new Stripe( 20 | this.configService.get('STRIPE_SECRET_KEY'), 21 | { 22 | apiVersion: '2023-08-16', 23 | }, 24 | ); 25 | 26 | async createChange({ amount, email }: PaymentsCreateChargeDto) { 27 | const paymentIntent = await this.stripe.paymentIntents.create({ 28 | payment_method: 'pm_card_visa', 29 | amount: amount * 100, 30 | confirm: true, 31 | currency: 'usd', 32 | receipt_email: email, 33 | return_url: 'http://localhost:3000/reservations', 34 | }); 35 | 36 | this.notificationsService.emit('notify_email', { 37 | email, 38 | subject: 'Payment Success', 39 | text: `Your payment of $${amount} has completed successfully.`, 40 | }); 41 | 42 | return paymentIntent; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/payments/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 { PaymentsModule } from './../src/payments.module'; 5 | 6 | describe('PaymentsController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [PaymentsModule], 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/payments/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/payments/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "outDir": "../../dist/apps/payments" 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 pnpm-workspace.yaml ./ 7 | COPY pnpm-lock.yaml ./ 8 | COPY tsconfig.json tsconfig.json 9 | COPY nest-cli.json nest-cli.json 10 | 11 | RUN npm install -g pnpm 12 | 13 | COPY apps/reservations apps/reservations 14 | COPY libs libs 15 | 16 | RUN pnpm install 17 | 18 | RUN pnpm run build reservations 19 | 20 | FROM node:alpine as production 21 | 22 | ARG NODE_ENV=production 23 | ENV NODE_ENV=${NODE_ENV} 24 | 25 | WORKDIR /usr/src/app 26 | 27 | COPY package.json ./ 28 | COPY pnpm-workspace.yaml ./ 29 | COPY pnpm-lock.yaml ./ 30 | 31 | RUN npm install -g pnpm 32 | 33 | RUN pnpm install --prod 34 | 35 | COPY --from=development /usr/src/app/dist ./dist 36 | 37 | CMD ["node", "dist/apps/reservations/main"] -------------------------------------------------------------------------------- /apps/reservations/src/dto/create-reservation.dto.ts: -------------------------------------------------------------------------------- 1 | import { ReservationDocument } from '../models/reservation.schema'; 2 | import { 3 | IsDate, 4 | IsString, 5 | IsNotEmpty, 6 | IsDefined, 7 | IsNotEmptyObject, 8 | ValidateNested, 9 | } from 'class-validator'; 10 | import { Type } from 'class-transformer'; 11 | import { CreateChargeDto } from '@app/common'; 12 | 13 | export class CreateReservationDto extends ReservationDocument { 14 | @IsDate() 15 | @Type(() => Date) 16 | startDate: Date; 17 | 18 | @IsDate() 19 | @Type(() => Date) 20 | endDate: Date; 21 | 22 | @IsString() 23 | @IsNotEmpty() 24 | placeId: string; 25 | 26 | @IsString() 27 | @IsNotEmpty() 28 | invoiceId: string; 29 | 30 | @IsDefined() 31 | @IsNotEmptyObject() 32 | @ValidateNested() 33 | @Type(() => CreateChargeDto) 34 | charge: any; 35 | } 36 | -------------------------------------------------------------------------------- /apps/reservations/src/dto/update-reservation.dto.ts: -------------------------------------------------------------------------------- 1 | import { CreateReservationDto } from './create-reservation.dto'; 2 | 3 | export class UpdateReservationDto implements Partial {} 4 | -------------------------------------------------------------------------------- /apps/reservations/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ValidationPipe } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { NestFactory } from '@nestjs/core'; 4 | import * as cookieParser from 'cookie-parser'; 5 | import { Logger } from 'nestjs-pino'; 6 | import { ReservationsModule } from './reservations.module'; 7 | 8 | async function bootstrap() { 9 | const app = await NestFactory.create(ReservationsModule); 10 | const configService = app.get(ConfigService); 11 | 12 | app.use(cookieParser()); 13 | app.useGlobalPipes(new ValidationPipe({ whitelist: true })); 14 | app.useLogger(app.get(Logger)); 15 | 16 | await app.startAllMicroservices(); 17 | await app.listen(configService.get('HTTP_PORT')); 18 | } 19 | bootstrap(); 20 | -------------------------------------------------------------------------------- /apps/reservations/src/models/reservation.schema.ts: -------------------------------------------------------------------------------- 1 | import { AbstractDocument } from '@app/common'; 2 | import { Prop, Schema } from '@nestjs/mongoose'; 3 | @Schema({ versionKey: false }) 4 | export class ReservationDocument extends AbstractDocument { 5 | static readonly collectionName = 'reservations'; 6 | 7 | @Prop() 8 | startDate: Date; 9 | 10 | @Prop() 11 | endDate: Date; 12 | 13 | @Prop() 14 | userId: string; 15 | 16 | @Prop() 17 | placeId: string; 18 | 19 | @Prop() 20 | invoiceId: string; 21 | } 22 | -------------------------------------------------------------------------------- /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 { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Patch, 7 | Param, 8 | Delete, 9 | Query, 10 | UseGuards, 11 | } from '@nestjs/common'; 12 | import { ReservationsService } from './reservations.service'; 13 | import { CreateReservationDto } from './dto/create-reservation.dto'; 14 | import { UpdateReservationDto } from './dto/update-reservation.dto'; 15 | import { FilterQuery } from 'mongoose'; 16 | import { CurrentUser, JwtAuthGuard, UserDto } from '@app/common'; 17 | 18 | @Controller('reservations') 19 | export class ReservationsController { 20 | constructor(private readonly service: ReservationsService) {} 21 | 22 | @Post() 23 | @UseGuards(JwtAuthGuard) 24 | create( 25 | @Body() createReservationDto: CreateReservationDto, 26 | @CurrentUser() user: UserDto, 27 | ) { 28 | return this.service.create(createReservationDto, user); 29 | } 30 | 31 | @Get() 32 | @UseGuards(JwtAuthGuard) 33 | findAll( 34 | @Query() filterQuery: FilterQuery, 35 | @CurrentUser() user: UserDto, 36 | ) { 37 | return this.service.findAll({ ...filterQuery, userId: user._id }); 38 | } 39 | 40 | @Get(':id') 41 | @UseGuards(JwtAuthGuard) 42 | findOne(@Param('id') id: string) { 43 | return this.service.findOne(id); 44 | } 45 | 46 | @Patch(':id') 47 | @UseGuards(JwtAuthGuard) 48 | update( 49 | @Param('id') id: string, 50 | @Body() updateReservationDto: UpdateReservationDto, 51 | ) { 52 | return this.service.update(id, updateReservationDto); 53 | } 54 | 55 | @Delete(':id') 56 | @UseGuards(JwtAuthGuard) 57 | remove(@Param('id') id: string) { 58 | return this.service.remove(id); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /apps/reservations/src/reservations.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ReservationsService } from './reservations.service'; 3 | import { ReservationsController } from './reservations.controller'; 4 | import { DatabaseModule, HealthModule, LoggerModule } from '@app/common'; 5 | import { ReservationsRepository } from './reservations.repository'; 6 | import { ReservationDocument } from './models/reservation.schema'; 7 | import { ConfigModule, ConfigService } from '@nestjs/config'; 8 | import * as Joi from 'joi'; 9 | import { ClientsModule, Transport } from '@nestjs/microservices'; 10 | import { AUTH_SERVICE, PAYMENTS_SERVICE } from '@app/common'; 11 | 12 | @Module({ 13 | imports: [ 14 | DatabaseModule, 15 | DatabaseModule.forFeature([ReservationDocument]), 16 | LoggerModule, 17 | HealthModule, 18 | ConfigModule.forRoot({ 19 | isGlobal: true, 20 | validationSchema: Joi.object({ 21 | MONGODB_URI: Joi.string(), 22 | HTTP_PORT: Joi.number(), 23 | AUTH_SERVICE_HOST: Joi.string(), 24 | AUTH_SERVICE_TCP_PORT: Joi.number(), 25 | PAYMENTS_SERVICE_HOST: Joi.string(), 26 | PAYMENTS_SERVICE_TCP_PORT: Joi.number(), 27 | }), 28 | }), 29 | ClientsModule.registerAsync([ 30 | { 31 | name: AUTH_SERVICE, 32 | useFactory: (configService: ConfigService) => ({ 33 | transport: Transport.TCP, 34 | options: { 35 | host: configService.get('AUTH_SERVICE_HOST'), 36 | port: configService.get('AUTH_SERVICE_TCP_PORT'), 37 | }, 38 | }), 39 | inject: [ConfigService], 40 | }, 41 | { 42 | name: PAYMENTS_SERVICE, 43 | useFactory: (configService: ConfigService) => ({ 44 | transport: Transport.TCP, 45 | options: { 46 | host: configService.get('PAYMENTS_SERVICE_HOST'), 47 | port: configService.get('PAYMENTS_SERVICE_TCP_PORT'), 48 | }, 49 | }), 50 | inject: [ConfigService], 51 | }, 52 | ]), 53 | ], 54 | controllers: [ReservationsController], 55 | providers: [ReservationsService, ReservationsRepository], 56 | }) 57 | export class ReservationsModule {} 58 | -------------------------------------------------------------------------------- /apps/reservations/src/reservations.repository.ts: -------------------------------------------------------------------------------- 1 | import { AbstractRepository } from '@app/common'; 2 | import { Injectable, Logger } from '@nestjs/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 = new Logger(ReservationsRepository.name); 10 | 11 | constructor( 12 | @InjectModel(ReservationDocument.collectionName) 13 | reservationModel: Model, 14 | ) { 15 | super(reservationModel); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 { Inject, 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 | import { FilterQuery } from 'mongoose'; 6 | import { PAYMENTS_SERVICE, UserDto } from '@app/common'; 7 | import { ClientProxy } from '@nestjs/microservices'; 8 | 9 | @Injectable() 10 | export class ReservationsService { 11 | constructor( 12 | private readonly repository: ReservationsRepository, 13 | @Inject(PAYMENTS_SERVICE) private readonly paymentsService: ClientProxy, 14 | ) {} 15 | 16 | create( 17 | createReservationDto: CreateReservationDto, 18 | { email, _id: userId }: UserDto, 19 | ) { 20 | this.paymentsService 21 | .send('create_change', { 22 | ...createReservationDto.charge, 23 | email, 24 | }) 25 | .subscribe({ 26 | next: (paymentIntent) => { 27 | this.repository.create({ 28 | ...createReservationDto, 29 | invoiceId: paymentIntent.id, 30 | userId, 31 | }); 32 | }, 33 | error(err) { 34 | console.log(err); 35 | }, 36 | }); 37 | } 38 | 39 | findAll(filterQuery: FilterQuery) { 40 | return this.repository.find(filterQuery); 41 | } 42 | 43 | findOne(_id: string) { 44 | return this.repository.findOne({ _id }); 45 | } 46 | 47 | update(_id: string, updateReservationDto: UpdateReservationDto) { 48 | return this.repository.findOneAndUpdate( 49 | { _id }, 50 | { $set: updateReservationDto }, 51 | ); 52 | } 53 | 54 | remove(_id: string) { 55 | return this.repository.findOneAndDelete({ _id }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Reversations 2 | steps: 3 | - name: 'gcr.io/cloud-builders/docker' 4 | args: 5 | [ 6 | 'build', 7 | '-t', 8 | 'southamerica-east1-docker.pkg.dev/slepper-api/reservations/prodution', 9 | '-f', 10 | 'apps/reservations/Dockerfile', 11 | '.', 12 | ] 13 | - name: 'gcr.io/cloud-builders/docker' 14 | args: 15 | [ 16 | 'push', 17 | 'southamerica-east1-docker.pkg.dev/slepper-api/reservations/prodution', 18 | ] 19 | 20 | # Auth 21 | - name: 'gcr.io/cloud-builders/docker' 22 | args: 23 | [ 24 | 'build', 25 | '-t', 26 | 'southamerica-east1-docker.pkg.dev/slepper-api/auth/prodution', 27 | '-f', 28 | 'apps/auth/Dockerfile', 29 | '.', 30 | ] 31 | - name: 'gcr.io/cloud-builders/docker' 32 | args: 33 | ['push', 'southamerica-east1-docker.pkg.dev/slepper-api/auth/prodution'] 34 | 35 | # Notifications 36 | - name: 'gcr.io/cloud-builders/docker' 37 | args: 38 | [ 39 | 'build', 40 | '-t', 41 | 'southamerica-east1-docker.pkg.dev/slepper-api/notifications/prodution', 42 | '-f', 43 | 'apps/notifications/Dockerfile', 44 | '.', 45 | ] 46 | - name: 'gcr.io/cloud-builders/docker' 47 | args: 48 | [ 49 | 'push', 50 | 'southamerica-east1-docker.pkg.dev/slepper-api/notifications/prodution', 51 | ] 52 | 53 | # Payments 54 | - name: 'gcr.io/cloud-builders/docker' 55 | args: 56 | [ 57 | 'build', 58 | '-t', 59 | 'southamerica-east1-docker.pkg.dev/slepper-api/payments/prodution', 60 | '-f', 61 | 'apps/payments/Dockerfile', 62 | '.', 63 | ] 64 | - name: 'gcr.io/cloud-builders/docker' 65 | args: 66 | [ 67 | 'push', 68 | 'southamerica-east1-docker.pkg.dev/slepper-api/payments/prodution', 69 | ] 70 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | reservations: 3 | build: 4 | context: . 5 | dockerfile: ./apps/reservations/Dockerfile 6 | target: development 7 | command: pnpm 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: pnpm run start:dev auth 20 | env_file: 21 | - ./apps/auth/.env 22 | 23 | ports: 24 | - '3001:3001' 25 | volumes: 26 | - .:/usr/src/app 27 | payments: 28 | build: 29 | context: . 30 | dockerfile: ./apps/payments/Dockerfile 31 | target: development 32 | command: pnpm run start:dev payments 33 | env_file: 34 | - ./apps/payments/.env 35 | volumes: 36 | - .:/usr/src/app 37 | notifications: 38 | build: 39 | context: . 40 | dockerfile: ./apps/notifications/Dockerfile 41 | target: development 42 | command: pnpm run start:dev notifications 43 | env_file: 44 | - ./apps/notifications/.env 45 | volumes: 46 | - .:/usr/src/app 47 | mongo: 48 | image: mongo -------------------------------------------------------------------------------- /image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augusto-carlos/Nestjs-Postgres-RabbitMQ-JWT-AWS/1e53e5574620214dfc8dccecfbe0b9e3028f4b64/image.jpg -------------------------------------------------------------------------------- /k8s/sleeper/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /k8s/sleeper/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: sleeper 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/auth/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: auth 6 | name: auth 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: auth 12 | template: 13 | metadata: 14 | labels: 15 | app: auth 16 | spec: 17 | containers: 18 | - image: southamerica-east1-docker.pkg.dev/slepper-api/auth/prodution 19 | name: auth 20 | env: 21 | - name: HTTP_PORT 22 | value: '3003' 23 | - name: TCP_PORT 24 | value: '3002' 25 | - name: JWT_EXPIRATION_TIME 26 | value: '3600' 27 | - name: MONGODB_URI 28 | valueFrom: 29 | secretKeyRef: 30 | name: mongodb 31 | key: connectionString 32 | - name: JWT_SECRET 33 | valueFrom: 34 | secretKeyRef: 35 | name: jwt 36 | key: jwtSecret 37 | ports: 38 | - containerPort: 3003 39 | - containerPort: 3002 40 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/auth/service-http.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: auth 6 | name: auth-http 7 | spec: 8 | ports: 9 | - name: 'http' 10 | port: 3003 11 | protocol: TCP 12 | targetPort: 3003 13 | selector: 14 | app: auth 15 | type: NodePort 16 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/auth/service-tcp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: auth 6 | name: auth-tcp 7 | spec: 8 | ports: 9 | - name: 'tcp' 10 | port: 3002 11 | protocol: TCP 12 | targetPort: 3002 13 | selector: 14 | app: auth 15 | type: ClusterIP 16 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/notifications/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: notifications 6 | name: notifications 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: notifications 12 | template: 13 | metadata: 14 | labels: 15 | app: notifications 16 | spec: 17 | containers: 18 | - image: southamerica-east1-docker.pkg.dev/slepper-api/notifications/prodution 19 | name: notifications 20 | env: 21 | - name: TCP_PORT 22 | value: '3000' 23 | - name: GOOGLE_OAUTH_CLIENT_ID 24 | value: 861387520592-smsr63m8tkcp8ocem12ojiqeh1jpcb73.apps.googleusercontent.com 25 | - name: SMTP_USER 26 | value: sleeperapplication@gmail.com 27 | - name: GOOGLE_OAUTH_CLIENT_SECRET 28 | valueFrom: 29 | secretKeyRef: 30 | name: google-oauth 31 | key: clientSecret 32 | - name: GOOGLE_OAUTH_REFRESH_TOKEN 33 | valueFrom: 34 | secretKeyRef: 35 | name: google-oauth 36 | key: refreshToken 37 | ports: 38 | - containerPort: 3000 -------------------------------------------------------------------------------- /k8s/sleeper/templates/notifications/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: notifications 6 | name: notifications 7 | spec: 8 | ports: 9 | - name: 'tcp' 10 | port: 3000 11 | protocol: TCP 12 | targetPort: 3000 13 | selector: 14 | app: notifications 15 | type: ClusterIP 16 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/payments/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: payments 6 | name: payments 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: payments 12 | template: 13 | metadata: 14 | labels: 15 | app: payments 16 | spec: 17 | containers: 18 | - image: southamerica-east1-docker.pkg.dev/slepper-api/payments/prodution 19 | name: payments 20 | env: 21 | - name: TPC_PORT 22 | value: '3001' 23 | - name: NOTIFICATIONS_SERVICE_HOST 24 | value: notifications 25 | - name: NOTIFICATIONS_SERVICE_TCP_PORT 26 | value: '3000' 27 | - name: STRIPE_SECRET_KEY 28 | valueFrom: 29 | secretKeyRef: 30 | name: stripe 31 | key: apiKey 32 | ports: 33 | - containerPort: 3001 34 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/payments/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: payments 6 | name: payments 7 | spec: 8 | ports: 9 | - name: 'tcp' 10 | port: 3001 11 | protocol: TCP 12 | targetPort: 3001 13 | selector: 14 | app: payments 15 | type: ClusterIP 16 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/reservations/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: reservations 6 | name: reservations 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: reservations 12 | template: 13 | metadata: 14 | labels: 15 | app: reservations 16 | spec: 17 | containers: 18 | - image: southamerica-east1-docker.pkg.dev/slepper-api/reservations/prodution 19 | name: reservations 20 | env: 21 | - name: HTTP_PORT 22 | value: '3004' 23 | - name: AUTH_SERVICE_HOST 24 | value: 'auth-tcp' 25 | - name: AUTH_SERVICE_TCP_PORT 26 | value: '3002' 27 | - name: PAYMENTS_SERVICE_HOST 28 | value: 'payments' 29 | - name: PAYMENTS_SERVICE_TCP_PORT 30 | value: '3001' 31 | - name: MONGODB_URI 32 | valueFrom: 33 | secretKeyRef: 34 | name: mongodb 35 | key: connectionString 36 | ports: 37 | - containerPort: 3004 38 | -------------------------------------------------------------------------------- /k8s/sleeper/templates/reservations/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: reservations 6 | name: reservations 7 | spec: 8 | ports: 9 | - name: 'http' 10 | port: 3004 11 | protocol: TCP 12 | targetPort: 3004 13 | selector: 14 | app: reservations 15 | type: NodePort 16 | -------------------------------------------------------------------------------- /k8s/sleeper/values.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augusto-carlos/Nestjs-Postgres-RabbitMQ-JWT-AWS/1e53e5574620214dfc8dccecfbe0b9e3028f4b64/k8s/sleeper/values.yaml -------------------------------------------------------------------------------- /libs/common/src/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './jwt-auth.guard'; 2 | -------------------------------------------------------------------------------- /libs/common/src/auth/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Inject, 5 | Injectable, 6 | Logger, 7 | UnauthorizedException, 8 | } from '@nestjs/common'; 9 | import { ClientProxy } from '@nestjs/microservices'; 10 | import { Observable, catchError, map, of, tap } from 'rxjs'; 11 | import { AUTH_SERVICE } from '../constants/services'; 12 | import { UserDto } from '../dto'; 13 | 14 | @Injectable() 15 | export class JwtAuthGuard implements CanActivate { 16 | private readonly logger = new Logger(JwtAuthGuard.name); 17 | constructor(@Inject(AUTH_SERVICE) private readonly authClient: ClientProxy) {} 18 | 19 | canActivate( 20 | context: ExecutionContext, 21 | ): boolean | Promise | Observable { 22 | const jwt = context.switchToHttp().getRequest().cookies?.Authentication; 23 | if (!jwt) { 24 | return false; 25 | } 26 | 27 | return this.authClient 28 | .send('authenticate', { Authentication: jwt }) 29 | .pipe( 30 | tap((res) => { 31 | context.switchToHttp().getRequest().user = res; 32 | }), 33 | map(() => true), 34 | catchError((err) => { 35 | this.logger.error(err); 36 | return of(false); 37 | }), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /libs/common/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './services'; 2 | -------------------------------------------------------------------------------- /libs/common/src/constants/services.ts: -------------------------------------------------------------------------------- 1 | export const AUTH_SERVICE = 'auth'; 2 | export const PAYMENTS_SERVICE = 'payments'; 3 | export const NOTIFICATIONS_SERVICE = 'notifications'; 4 | -------------------------------------------------------------------------------- /libs/common/src/database/abstract.repository.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery, Model, UpdateQuery } from 'mongoose'; 2 | import { AbstractDocument, AutoGeneratedField } from './abstract.schema'; 3 | import { Logger, NotFoundException } from '@nestjs/common'; 4 | 5 | export abstract class AbstractRepository { 6 | protected abstract readonly logger: Logger; 7 | 8 | constructor(protected readonly model: Model) {} 9 | 10 | async create( 11 | createDto: Omit, 12 | ): Promise { 13 | const createdDocument = new this.model(createDto); 14 | return (await createdDocument.save()).toJSON() as TDocument; 15 | } 16 | 17 | async findOne(filterQuery: FilterQuery): Promise { 18 | const document = await this.model.findOne(filterQuery, {}); 19 | if (!document) this.handleNotFoundError(filterQuery); 20 | 21 | return document; 22 | } 23 | 24 | async findOneAndUpdate( 25 | filterQuery: FilterQuery, 26 | update: UpdateQuery, 27 | ): Promise { 28 | const document = await this.model.findOneAndUpdate(filterQuery, update, { 29 | new: true, 30 | }); 31 | if (!document) this.handleNotFoundError(filterQuery); 32 | 33 | return document; 34 | } 35 | 36 | async find(filterQuery?: FilterQuery): Promise { 37 | const document = this.model.find(filterQuery, {}); 38 | if (!document) this.handleNotFoundError(filterQuery); 39 | 40 | return document; 41 | } 42 | 43 | async findOneAndDelete(filterQuery: FilterQuery): Promise { 44 | const document = await this.model.findOneAndDelete(filterQuery); 45 | 46 | if (!document) this.handleNotFoundError(filterQuery); 47 | return { 48 | message: 'Document deleted successfully', 49 | data: document, 50 | }; 51 | } 52 | 53 | private handleNotFoundError(filterQuery: FilterQuery) { 54 | this.logger.warn( 55 | `Document not found for filter query: ${JSON.stringify(filterQuery)}`, 56 | ); 57 | throw new NotFoundException('Document not found'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /libs/common/src/database/abstract.schema.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema } from '@nestjs/mongoose'; 2 | import { SchemaTypes, Types } from 'mongoose'; 3 | 4 | @Schema() 5 | export abstract class AbstractDocument { 6 | @Prop({ type: SchemaTypes.ObjectId, auto: true }) 7 | _id: Types.ObjectId; 8 | 9 | @Prop({ type: Date, default: Date.now }) 10 | _createdAt: Date; 11 | 12 | @Prop({ type: Date }) 13 | _updatedAt: Date; 14 | } 15 | 16 | export type AutoGeneratedField = '_id' | '_createdAt' | '_updatedAt'; 17 | -------------------------------------------------------------------------------- /libs/common/src/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { MongooseModule, SchemaFactory } from '@nestjs/mongoose'; 4 | 5 | @Module({ 6 | imports: [ 7 | MongooseModule.forRootAsync({ 8 | useFactory: (configService: ConfigService) => ({ 9 | uri: configService.get('MONGODB_URI'), 10 | }), 11 | inject: [ConfigService], 12 | }), 13 | ], 14 | }) 15 | export class DatabaseModule { 16 | static forFeature(documents: any[]) { 17 | return MongooseModule.forFeature( 18 | documents.map((doc) => ({ 19 | name: doc.collectionName, 20 | schema: SchemaFactory.createForClass(doc), 21 | })), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/common/src/database/index.ts: -------------------------------------------------------------------------------- 1 | export * from './database.module'; 2 | export * from './abstract.repository'; 3 | export * from './abstract.schema'; 4 | -------------------------------------------------------------------------------- /libs/common/src/database/utils/error-handler.ts: -------------------------------------------------------------------------------- 1 | // export class ErrorHandler { 2 | // static handleNotFoundError(filterQuery) { 3 | // this.logger.warn( 4 | // `Document not found for filter query: ${JSON.stringify(filterQuery)}`, 5 | // ); 6 | // throw new NotFoundException('Document not found'); 7 | // } 8 | // } 9 | -------------------------------------------------------------------------------- /libs/common/src/decorators/current-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, createParamDecorator } from '@nestjs/common'; 2 | import { UserDocument } from '../models'; 3 | 4 | function getCurrentUserByContext( 5 | _data: unknown, 6 | context: ExecutionContext, 7 | ): UserDocument { 8 | const user: UserDocument = context.switchToHttp().getRequest().user; 9 | return user; 10 | } 11 | 12 | export const CurrentUser = createParamDecorator(getCurrentUserByContext); 13 | -------------------------------------------------------------------------------- /libs/common/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './current-user.decorator'; 2 | -------------------------------------------------------------------------------- /libs/common/src/dto/card.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsCreditCard, IsNotEmpty, IsNumber, IsString } from 'class-validator'; 2 | 3 | export class CardDto { 4 | @IsString() 5 | @IsNotEmpty() 6 | cvc: string; 7 | 8 | @IsNumber() 9 | exp_month: number; 10 | 11 | @IsNumber() 12 | exp_year: number; 13 | 14 | @IsCreditCard() 15 | number: string; 16 | } 17 | -------------------------------------------------------------------------------- /libs/common/src/dto/create-charge.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { 3 | IsDefined, 4 | IsNotEmptyObject, 5 | IsNumber, 6 | ValidateNested, 7 | } from 'class-validator'; 8 | import { CardDto } from './card.dto'; 9 | 10 | export class CreateChargeDto { 11 | @IsDefined() 12 | @IsNotEmptyObject() 13 | @ValidateNested() 14 | @Type(() => CardDto) 15 | card: CardDto; 16 | 17 | @IsNumber() 18 | amount: number; 19 | } 20 | -------------------------------------------------------------------------------- /libs/common/src/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.dto'; 2 | export * from './create-charge.dto'; 3 | export * from './notify-email.dto'; 4 | export * from './payments-create-charge.dto'; 5 | -------------------------------------------------------------------------------- /libs/common/src/dto/notify-email.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString } from 'class-validator'; 2 | 3 | export class NotifyEmailDto { 4 | @IsEmail() 5 | email: string; 6 | 7 | @IsString() 8 | subject: string; 9 | 10 | @IsString() 11 | text: string; 12 | } 13 | -------------------------------------------------------------------------------- /libs/common/src/dto/payments-create-charge.dto.ts: -------------------------------------------------------------------------------- 1 | import { CreateChargeDto } from './create-charge.dto'; 2 | import { IsEmail } from 'class-validator'; 3 | 4 | export class PaymentsCreateChargeDto extends CreateChargeDto { 5 | @IsEmail() 6 | email: string; 7 | } 8 | -------------------------------------------------------------------------------- /libs/common/src/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | export interface UserDto { 2 | _id: string; 3 | email: string; 4 | password: string; 5 | roles?: string[]; 6 | } 7 | -------------------------------------------------------------------------------- /libs/common/src/health/health.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | @Controller('/') 4 | export class HealthController { 5 | @Get() 6 | health() { 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libs/common/src/health/health.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HealthController } from './health.controller'; 3 | 4 | @Module({ 5 | controllers: [HealthController], 6 | }) 7 | export class HealthModule {} 8 | -------------------------------------------------------------------------------- /libs/common/src/health/index.ts: -------------------------------------------------------------------------------- 1 | export * from './health.module'; 2 | -------------------------------------------------------------------------------- /libs/common/src/index.ts: -------------------------------------------------------------------------------- 1 | // export * from './common.module'; 2 | export * from './database'; 3 | export * from './logger'; 4 | export * from './auth'; 5 | export * from './constants'; 6 | export * from './models'; 7 | export * from './decorators'; 8 | export * from './dto'; 9 | export * from './health'; 10 | -------------------------------------------------------------------------------- /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 PinoLoggerModule } from 'nestjs-pino'; 3 | 4 | @Module({ 5 | imports: [ 6 | PinoLoggerModule.forRoot({ 7 | pinoHttp: { 8 | transport: { 9 | target: 'pino-pretty', 10 | options: { 11 | singleLine: true, 12 | colorize: true, 13 | ignore: 'pid,hostname', 14 | }, 15 | }, 16 | }, 17 | }), 18 | ], 19 | }) 20 | export class LoggerModule {} 21 | -------------------------------------------------------------------------------- /libs/common/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.schema'; 2 | -------------------------------------------------------------------------------- /libs/common/src/models/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { AbstractDocument } from '@app/common'; 2 | import { Prop, Schema } from '@nestjs/mongoose'; 3 | @Schema({ versionKey: false }) 4 | export class UserDocument extends AbstractDocument { 5 | static readonly collectionName = 'users'; 6 | 7 | @Prop() 8 | email: string; 9 | 10 | @Prop() 11 | password: string; 12 | } 13 | -------------------------------------------------------------------------------- /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 | "payments": { 39 | "type": "application", 40 | "root": "apps/payments", 41 | "entryFile": "main", 42 | "sourceRoot": "apps/payments/src", 43 | "compilerOptions": { 44 | "tsConfigPath": "apps/payments/tsconfig.app.json" 45 | } 46 | }, 47 | "notifications": { 48 | "type": "application", 49 | "root": "apps/notifications", 50 | "entryFile": "main", 51 | "sourceRoot": "apps/notifications/src", 52 | "compilerOptions": { 53 | "tsConfigPath": "apps/notifications/tsconfig.app.json" 54 | } 55 | } 56 | }, 57 | "monorepo": true, 58 | "root": "apps/reservations" 59 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sleeper", 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/sleeper/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/sleeper/test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/config": "^3.0.0", 25 | "@nestjs/core": "^10.0.0", 26 | "@nestjs/mapped-types": "*", 27 | "@nestjs/microservices": "^10.1.3", 28 | "@nestjs/mongoose": "^10.0.1", 29 | "@nestjs/platform-express": "^10.0.0", 30 | "class-transformer": "^0.2.3", 31 | "class-validator": "^0.11.1", 32 | "cookie-parser": "^1.4.6", 33 | "express": "^4.18.2", 34 | "joi": "^17.9.2", 35 | "mongoose": "^7.4.1", 36 | "nestjs-pino": "^3.3.0", 37 | "pino-http": "^8.4.0", 38 | "pino-pretty": "^10.2.0", 39 | "reflect-metadata": "^0.1.13", 40 | "rxjs": "^7.8.1" 41 | }, 42 | "devDependencies": { 43 | "@nestjs/cli": "^10.0.0", 44 | "@nestjs/schematics": "^10.0.0", 45 | "@nestjs/testing": "^10.0.0", 46 | "@types/cookie-parser": "^1.4.3", 47 | "@types/express": "^4.17.17", 48 | "@types/jest": "^29.5.2", 49 | "@types/node": "^20.3.1", 50 | "@types/supertest": "^2.0.12", 51 | "@typescript-eslint/eslint-plugin": "^5.59.11", 52 | "@typescript-eslint/parser": "^5.59.11", 53 | "eslint": "^8.42.0", 54 | "eslint-config-prettier": "^8.8.0", 55 | "eslint-plugin-prettier": "^4.2.1", 56 | "jest": "^29.5.0", 57 | "prettier": "^2.8.8", 58 | "source-map-support": "^0.5.21", 59 | "supertest": "^6.3.3", 60 | "ts-jest": "^29.1.0", 61 | "ts-loader": "^9.4.3", 62 | "ts-node": "^10.9.1", 63 | "tsconfig-paths": "^4.2.0", 64 | "typescript": "^5.1.3" 65 | }, 66 | "jest": { 67 | "moduleFileExtensions": [ 68 | "js", 69 | "json", 70 | "ts" 71 | ], 72 | "rootDir": ".", 73 | "testRegex": ".*\\.spec\\.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "collectCoverageFrom": [ 78 | "**/*.(t|j)s" 79 | ], 80 | "coverageDirectory": "./coverage", 81 | "testEnvironment": "node", 82 | "roots": [ 83 | "/libs/", 84 | "/apps/" 85 | ], 86 | "moduleNameMapper": { 87 | "^@app/common(|/.*)$": "/libs/common/src/$1" 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages 2 | - 'apps/*' -------------------------------------------------------------------------------- /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": "ES2021", 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 | } --------------------------------------------------------------------------------