├── .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 |
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 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 | }
--------------------------------------------------------------------------------