├── README.md ├── auth ├── .docker │ └── mysql │ │ └── Dockerfile ├── .gitgnore └── docker-compose.yml ├── backend-api ├── .devcontainer │ ├── devcontainer.json │ └── docker-compose.yml ├── .docker │ ├── entrypoint.sh │ └── mysql │ │ └── Dockerfile ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Dockerfile ├── README.md ├── docker-compose.yml ├── keycloak.http ├── nest-cli.json ├── package-lock.json ├── package.json ├── room.http ├── src │ ├── api-doc │ │ ├── room.response.ts │ │ └── user.response.ts │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── controllers │ │ ├── room │ │ │ ├── room.controller.spec.ts │ │ │ └── room.controller.ts │ │ └── user │ │ │ ├── user.controller.spec.ts │ │ │ └── user.controller.ts │ ├── dto │ │ ├── room.dto.ts │ │ └── user.dto.ts │ ├── exception-filters │ │ └── entity-not-found.exception-filter.ts │ ├── main.ts │ ├── migrations │ │ ├── 1597362274197-CreateUserTable.ts │ │ └── 1597518554584-CreateRoomTable.ts │ ├── models │ │ ├── room.model.ts │ │ └── user.model.ts │ └── websocket │ │ ├── websocket.service.spec.ts │ │ └── websocket.service.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json └── user.http ├── frontend ├── .devcontainer │ ├── devcontainer.json │ └── docker-compose.yml ├── .docker │ └── entrypoint.sh ├── .env ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── AppRouter.tsx │ ├── components │ │ ├── Chat.tsx │ │ ├── Login.tsx │ │ ├── PrivateRoute.tsx │ │ └── Room.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── setupTests.ts │ └── utils │ │ └── auth.ts └── tsconfig.json └── presentations ├── Nest.js_ desenvolvimento de APIs.pdf └── Realtime com websockets usando Nest.js e React.pdf /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Descrição 6 | 7 | Repositórios do Esquenta Maratona FullCycle 4.0 8 | 9 | ## Rodar a aplicação 10 | 11 | #### Antes de começar 12 | 13 | A aplicação foi construída utilizando os conceitos de microsserviços e arquitetada com Docker. 14 | 15 | Para roda-la será necessário basicamente rodar o comando **docker-compose up**. 16 | 17 | Acesse cada microsserviço respectivamente e leia o README.md para ver mais detalhes de como rodar o microsserviço. 18 | 19 | * [Backend API REST com Nest.js](https://github.com/codeedu/maratona4-esquenta/tree/master/backend-api) 20 | 21 | ### Para Windows 22 | 23 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=g4HKttouVxA](https://www.youtube.com/watch?v=g4HKttouVxA) 24 | 25 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 26 | -------------------------------------------------------------------------------- /auth/.docker/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.7 2 | 3 | RUN usermod -u 1000 mysql -------------------------------------------------------------------------------- /auth/.gitgnore: -------------------------------------------------------------------------------- 1 | .docker/dbdata/ 2 | .history/ -------------------------------------------------------------------------------- /auth/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | 5 | app: 6 | image: quay.io/keycloak/keycloak:11.0.0 7 | container_name: esquenta-4-auth-app 8 | environment: 9 | KEYCLOAK_USER: admin 10 | KEYCLOAK_PASSWORD: admin 11 | DB_ADDR: db 12 | DB_VENDOR: mysql 13 | DB_DATABASE: auth 14 | DB_PORT: 3306 15 | DB_USER: root 16 | DB_PASSWORD: root 17 | JDBC_PARAMS: 'useSSL=false' 18 | ports: 19 | - 8080:8080 20 | depends_on: 21 | - db 22 | networks: 23 | - esquenta-rede 24 | 25 | db: 26 | build: ./.docker/mysql 27 | container_name: esquenta-4-auth-db 28 | restart: always 29 | tty: true 30 | command: [mysqld, --default_authentication_plugin=mysql_native_password, --character-set-server=utf8, --collation-server=utf8_bin, --default-storage-engine=INNODB] 31 | ports: 32 | - "33007:3306" 33 | volumes: 34 | - ./.docker/dbdata:/var/lib/mysql 35 | environment: 36 | MYSQL_DATABASE: auth 37 | MYSQL_ROOT_PASSWORD: root 38 | MYSQL_USER: root 39 | networks: 40 | - esquenta-rede 41 | 42 | networks: 43 | esquenta-rede: 44 | driver: bridge -------------------------------------------------------------------------------- /backend-api/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.128.0/containers/docker-existing-docker-compose 3 | // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. 4 | { 5 | "name": "nest-esquenta-4", 6 | 7 | // Update the 'dockerComposeFile' list if you have more compose files or use different names. 8 | // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. 9 | "dockerComposeFile": [ 10 | "../docker-compose.yml", 11 | "docker-compose.yml" 12 | ], 13 | 14 | // The 'service' property is the name of the service for the container that VS Code should 15 | // use. Update this value and .devcontainer/docker-compose.yml to the real service name. 16 | "service": "app", 17 | 18 | // The optional 'workspaceFolder' property is the path VS Code should open by default when 19 | // connected. This is typically a file mount in .devcontainer/docker-compose.yml 20 | "workspaceFolder": "/nest", 21 | 22 | // Set *default* container specific settings.json values on container create. 23 | "settings": { 24 | "terminal.integrated.shell.linux": null 25 | }, 26 | 27 | // Add the IDs of extensions you want installed when the container is created. 28 | "extensions": [ 29 | "humao.rest-client" 30 | ] 31 | 32 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 33 | // "forwardPorts": [], 34 | 35 | // Uncomment the next line if you want start specific services in your Docker Compose config. 36 | // "runServices": [], 37 | 38 | // Uncomment the next line if you want to keep your containers running after VS Code shuts down. 39 | // "shutdownAction": "none", 40 | 41 | // Uncomment the next line to run commands after the container is created - for example installing curl. 42 | // "postCreateCommand": "apt-get update && apt-get install -y curl", 43 | 44 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 45 | // "remoteUser": "vscode" 46 | } 47 | -------------------------------------------------------------------------------- /backend-api/.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | version: '3' 7 | services: 8 | # Update this to the name of the service you want to work with in your docker-compose.yml file 9 | app: 10 | # If you want add a non-root user to your Dockerfile, you can use the "remoteUser" 11 | # property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, 12 | # debugging) to execute as the user. Uncomment the next line if you want the entire 13 | # container to run as this user instead. Note that, on Linux, you may need to 14 | # ensure the UID and GID of the container user you create matches your local user. 15 | # See https://aka.ms/vscode-remote/containers/non-root for details. 16 | # 17 | # user: vscode 18 | 19 | # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer 20 | # folder. Note that the path of the Dockerfile and context is relative to the *primary* 21 | # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" 22 | # array). The sample below assumes your primary file is in the root of your project. 23 | # 24 | # build: 25 | # context: . 26 | # dockerfile: .devcontainer/Dockerfile 27 | 28 | volumes: 29 | # Update this to wherever you want VS Code to mount the folder of your project 30 | - .:/nest:cached 31 | 32 | # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details. 33 | # - /var/run/docker.sock:/var/run/docker.sock 34 | 35 | # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. 36 | # cap_add: 37 | # - SYS_PTRACE 38 | # security_opt: 39 | # - seccomp:unconfined 40 | 41 | # Overrides default command so things don't shut down after the process ends. 42 | command: /bin/sh -c "while sleep 1000; do :; done" 43 | 44 | -------------------------------------------------------------------------------- /backend-api/.docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /home/node/app 4 | 5 | if [ ! -f ".env" ]; then 6 | cp .env.example .env 7 | fi 8 | 9 | npm install 10 | npm run typeorm migration:run 11 | npm run start:dev 12 | -------------------------------------------------------------------------------- /backend-api/.docker/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.7 2 | 3 | RUN usermod -u 1000 mysql -------------------------------------------------------------------------------- /backend-api/.env.example: -------------------------------------------------------------------------------- 1 | TYPEORM_CONNECTION=mysql 2 | TYPEORM_HOST=db 3 | TYPEORM_USERNAME=root 4 | TYPEORM_PASSWORD=root 5 | TYPEORM_DATABASE=esquenta 6 | TYPEORM_PORT=3306 7 | TYPEORM_ENTITIES=src/**/*.entity.ts 8 | TYPEORM_ENTITIES_DIR=src/models 9 | TYPEORM_MIGRATIONS=src/migrations/**/*.ts 10 | TYPEORM_MIGRATIONS_DIR=src/migrations -------------------------------------------------------------------------------- /backend-api/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /backend-api/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | .env 37 | .npm-cache 38 | .docker/dbdata 39 | .history 40 | .idea -------------------------------------------------------------------------------- /backend-api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /backend-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.14.0-alpine3.11 2 | 3 | RUN apk add --no-cache bash git 4 | 5 | RUN touch /home/node/.bashrc | echo "PS1='\w\$ '" >> /home/node/.bashrc 6 | 7 | RUN npm config set cache /home/node/app/.npm-cache --global 8 | 9 | RUN npm i -g @nestjs/cli@7.4.1 10 | 11 | USER node 12 | 13 | WORKDIR /home/node/app 14 | -------------------------------------------------------------------------------- /backend-api/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## Descrição 6 | 7 | API Rest com Nest.js 8 | 9 | ## Rodar a aplicação 10 | 11 | #### Crie os containers com Docker 12 | 13 | ```bash 14 | $ docker-compose up 15 | ``` 16 | 17 | #### Accesse no browser 18 | 19 | ``` 20 | http://localhost:3000/api 21 | ``` 22 | -------------------------------------------------------------------------------- /backend-api/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | entrypoint: ./.docker/entrypoint.sh 8 | container_name: esquenta-4-app 9 | environment: 10 | - CHOKIDAR_USEPOLLING=true 11 | ports: 12 | - 3000:3000 13 | volumes: 14 | - .:/home/node/app 15 | 16 | db: 17 | build: ./.docker/mysql 18 | container_name: esquenta-4-db 19 | restart: always 20 | tty: true 21 | ports: 22 | - "33006:3306" 23 | volumes: 24 | - ./.docker/dbdata:/var/lib/mysql 25 | environment: 26 | - MYSQL_DATABASE=esquenta 27 | - MYSQL_ROOT_PASSWORD=root 28 | - MYSQL_USER=root -------------------------------------------------------------------------------- /backend-api/keycloak.http: -------------------------------------------------------------------------------- 1 | GET http://host.docker.internal:3000/rooms 2 | Content-Type: application/json 3 | Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ4TW4wRHU4ZHdOZlotbWpVcklYYkRLTk5OZkdMOWJZWUwxc2JrNHV4bERJIn0.eyJleHAiOjE1OTc4ODQ5NDMsImlhdCI6MTU5Nzg4NDY0MywiYXV0aF90aW1lIjoxNTk3ODgyODI0LCJqdGkiOiI2YjdlZGE5ZS1mYmVjLTQ0ZGMtYjUwNC1jZTYzMWQxMjM1ZDQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvQ2hhdCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJjNzdmM2U2OC1mNDZjLTRhYWItOGNmZi1lM2M3ODQ3NDI0MTkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJyZWFjdCIsIm5vbmNlIjoiNzM2OTQyNGEtYWRmNi00OWIzLTlkMDQtNmM3NjBkMTZhZmM3Iiwic2Vzc2lvbl9zdGF0ZSI6IjdhZDk0NDBjLTNiZGMtNDRkMS04NjE4LTE3ZTUxNzI4ZmFkMyIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjMwMDEiLCJodHRwOi8vbG9jYWxob3N0OjMwMDEiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJUZXN0MSIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QxQHRlc3QuY29tIiwiZ2l2ZW5fbmFtZSI6IlRlc3QxIiwiZW1haWwiOiJ0ZXN0MkB0ZXN0LmNvbSJ9.kfWCrCnZapmLiQHakpUCXQv4SHHMJvvzWkZewqhspeEXV48QZxlMy2PNj9apMAjhatfgibV0REC0uctplwdqojfq_BiHNd5JtPKzDQNtnvFLODR3Cfcd9xcP_wgaMLp9r-IBm1SiBIRyxfr9YcKxWJ3X63c2u2pmQl6qvjMG2jHPLxEj6o2K23VTDmO9kEqDnQZHnLJ7YN76MmQbcZ5JIKrNo2_JDlz1UAvaJ2vf3M-PL1JBHtNHZLMAxRjB6Kb-ynSq0SgYzFzX7h9OMsHzDyPqokIsb3UOeRidqL1ALFvofMq5l5fpPGp6zpy0N-_Z8uKrzjVmWo63AOVgmotlQQ -------------------------------------------------------------------------------- /backend-api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /backend-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-api", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json", 22 | "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js" 23 | }, 24 | "dependencies": { 25 | "@nestjs/common": "^7.0.0", 26 | "@nestjs/config": "^0.5.0", 27 | "@nestjs/core": "^7.0.0", 28 | "@nestjs/platform-express": "^7.0.0", 29 | "@nestjs/platform-socket.io": "^7.4.2", 30 | "@nestjs/swagger": "^4.5.12", 31 | "@nestjs/typeorm": "^7.1.0", 32 | "@nestjs/websockets": "^7.4.2", 33 | "class-transformer": "^0.3.1", 34 | "class-validator": "^0.12.2", 35 | "mysql": "^2.18.1", 36 | "nestjs-keycloak-admin": "^1.3.1", 37 | "reflect-metadata": "^0.1.13", 38 | "rimraf": "^3.0.2", 39 | "rxjs": "^6.5.4", 40 | "swagger-ui-express": "^4.1.4", 41 | "typeorm": "^0.2.25" 42 | }, 43 | "devDependencies": { 44 | "@nestjs/cli": "^7.0.0", 45 | "@nestjs/schematics": "^7.0.0", 46 | "@nestjs/testing": "^7.0.0", 47 | "@types/express": "^4.17.3", 48 | "@types/jest": "25.2.3", 49 | "@types/node": "^13.9.1", 50 | "@types/socket.io": "^2.1.11", 51 | "@types/supertest": "^2.0.8", 52 | "@typescript-eslint/eslint-plugin": "3.0.2", 53 | "@typescript-eslint/parser": "3.0.2", 54 | "eslint": "7.1.0", 55 | "eslint-config-prettier": "^6.10.0", 56 | "eslint-plugin-import": "^2.20.1", 57 | "jest": "26.0.1", 58 | "prettier": "^1.19.1", 59 | "supertest": "^4.0.2", 60 | "ts-jest": "26.1.0", 61 | "ts-loader": "^6.2.1", 62 | "ts-node": "^8.6.2", 63 | "tsconfig-paths": "^3.9.0", 64 | "typescript": "^3.7.4" 65 | }, 66 | "jest": { 67 | "moduleFileExtensions": [ 68 | "js", 69 | "json", 70 | "ts" 71 | ], 72 | "rootDir": "src", 73 | "testRegex": ".spec.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "coverageDirectory": "../coverage", 78 | "testEnvironment": "node" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /backend-api/room.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/rooms 2 | Content-type: application/json 3 | 4 | { 5 | "name": "Sala 2" 6 | } 7 | 8 | ### 9 | 10 | GET http://localhost:3000/rooms 11 | 12 | ### 13 | GET http://localhost:3000/rooms/1 -------------------------------------------------------------------------------- /backend-api/src/api-doc/room.response.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | export class RoomResponse { 4 | 5 | @ApiProperty({ 6 | description: 'Id of User' 7 | }) 8 | id: number; 9 | 10 | @ApiProperty() 11 | name: string; 12 | 13 | @ApiProperty() 14 | created_at: Date 15 | } 16 | -------------------------------------------------------------------------------- /backend-api/src/api-doc/user.response.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | export class UserResponse { 4 | @ApiProperty() 5 | id: number; 6 | 7 | @ApiProperty({ 8 | type: String, 9 | description: 'name of user' 10 | }) 11 | name: string; 12 | 13 | @ApiProperty() 14 | email: string; 15 | 16 | @ApiProperty() 17 | created_at:Date 18 | } -------------------------------------------------------------------------------- /backend-api/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /backend-api/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend-api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { User } from './models/user.model'; 6 | import { ConfigModule } from '@nestjs/config'; 7 | import { UserController } from './controllers/user/user.controller'; 8 | import { RoomController } from './controllers/room/room.controller'; 9 | import { Room } from './models/room.model'; 10 | import { WebsocketService } from './websocket/websocket.service'; 11 | import KeycloakModule, { AuthGuard } from 'nestjs-keycloak-admin'; 12 | import { APP_GUARD } from '@nestjs/core'; 13 | @Module({ 14 | imports: [ 15 | ConfigModule.forRoot(), 16 | TypeOrmModule.forRoot({ 17 | // @ts-ignore 18 | type: process.env.TYPEORM_CONNECTION, 19 | host: process.env.TYPEORM_HOST, 20 | port: parseInt(process.env.TYPEORM_PORT), 21 | username: process.env.TYPEORM_USERNAME, 22 | password: process.env.TYPEORM_PASSWORD, 23 | database: process.env.TYPEORM_DATABASE, 24 | entities: [User, Room], 25 | }), 26 | TypeOrmModule.forFeature([User, Room]), 27 | KeycloakModule.registerAsync({ 28 | useFactory: () => { 29 | const keycloakConfig = JSON.parse(process.env.KEYCLOAK_JSON); 30 | return { 31 | baseUrl: keycloakConfig['auth-server-url'], 32 | realmName: keycloakConfig['realm'], 33 | clientId: keycloakConfig['resource'], 34 | clientSecret: keycloakConfig['credentials']['secret'], 35 | }; 36 | }, 37 | }), 38 | ], 39 | controllers: [AppController, UserController, RoomController], 40 | providers: [AppService, WebsocketService, {provide: APP_GUARD, useClass: AuthGuard}], 41 | }) 42 | export class AppModule {} 43 | -------------------------------------------------------------------------------- /backend-api/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend-api/src/controllers/room/room.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RoomController } from './room.controller'; 3 | 4 | describe('Room Controller', () => { 5 | let controller: RoomController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [RoomController], 10 | }).compile(); 11 | 12 | controller = module.get(RoomController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend-api/src/controllers/room/room.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Param, 4 | Body, 5 | Get, 6 | Post, 7 | Put, 8 | Delete, 9 | HttpCode, 10 | UseInterceptors, 11 | ClassSerializerInterceptor, 12 | ValidationPipe, 13 | } from '@nestjs/common'; 14 | import { Repository } from 'typeorm'; 15 | import { Room } from 'src/models/room.model'; 16 | import { InjectRepository } from '@nestjs/typeorm'; 17 | import { RoomDto } from 'src/dto/room.dto'; 18 | import { 19 | ApiOkResponse, 20 | ApiCreatedResponse, 21 | ApiNoContentResponse, 22 | } from '@nestjs/swagger'; 23 | import { RoomResponse } from 'src/api-doc/room.response'; 24 | import { Public } from 'nestjs-keycloak-admin'; 25 | 26 | @UseInterceptors(ClassSerializerInterceptor) 27 | @Controller('rooms') 28 | export class RoomController { 29 | constructor( 30 | @InjectRepository(Room) 31 | private roomRepo: Repository, 32 | ) {} 33 | 34 | // @Public() 35 | @ApiOkResponse({ type: RoomResponse }) 36 | @Get() 37 | async index(): Promise { 38 | return this.roomRepo.find(); 39 | } 40 | 41 | @ApiOkResponse({ 42 | type: RoomResponse, 43 | }) 44 | @Get(':id') 45 | async show(@Param('id') id: string): Promise { 46 | return this.roomRepo.findOneOrFail(+id); 47 | } 48 | 49 | @ApiCreatedResponse({ 50 | type: RoomResponse, 51 | status: 201, 52 | }) 53 | @Post() 54 | async store(@Body(new ValidationPipe()) roomDto: RoomDto): Promise { 55 | const room = this.roomRepo.create(roomDto); 56 | return this.roomRepo.save(room); 57 | } 58 | 59 | @ApiOkResponse({ 60 | type: RoomResponse, 61 | }) 62 | @Put(':id') 63 | async update( 64 | @Param('id') id: string, 65 | @Body(new ValidationPipe()) roomDto: RoomDto, 66 | ): Promise { 67 | await this.roomRepo.findOneOrFail(+id); 68 | this.roomRepo.update({ id: +id }, roomDto); 69 | return this.roomRepo.findOneOrFail(+id); 70 | } 71 | 72 | @ApiNoContentResponse() 73 | @Delete(':id') 74 | @HttpCode(204) 75 | async destroy(@Param('id') id: string): Promise { 76 | await this.roomRepo.findOneOrFail(+id); 77 | this.roomRepo.delete(+id); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /backend-api/src/controllers/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserController } from './user.controller'; 3 | 4 | describe('User Controller', () => { 5 | let controller: UserController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UserController], 10 | }).compile(); 11 | 12 | controller = module.get(UserController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend-api/src/controllers/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Put, 6 | Delete, 7 | Body, 8 | Param, 9 | HttpCode, 10 | ValidationPipe, 11 | UseInterceptors, 12 | ClassSerializerInterceptor, 13 | } from '@nestjs/common'; 14 | import { Repository } from 'typeorm'; 15 | import { User } from 'src/models/user.model'; 16 | import { InjectRepository } from '@nestjs/typeorm'; 17 | import { ApiOkResponse, ApiCreatedResponse } from '@nestjs/swagger'; 18 | import { UserResponse } from 'src/api-doc/user.response'; 19 | import { UserDto } from 'src/dto/user.dto'; 20 | 21 | //API REST - POST users 22 | @UseInterceptors(ClassSerializerInterceptor) 23 | @Controller('users') 24 | export class UserController { 25 | constructor( 26 | @InjectRepository(User) 27 | private userRepo: Repository, //Generic 28 | ) {} 29 | 30 | @Get() 31 | async index(): Promise { 32 | return this.userRepo.find(); 33 | } 34 | 35 | @ApiOkResponse({ 36 | type: UserResponse 37 | }) 38 | @Get(':id') 39 | show(@Param('id') id: string): Promise { 40 | return this.userRepo.findOneOrFail(id); 41 | } 42 | 43 | @ApiCreatedResponse({ 44 | type: UserResponse 45 | }) 46 | @Post() 47 | async store(@Body(new ValidationPipe({ 48 | errorHttpStatusCode: 422 49 | })) body: UserDto): Promise { 50 | const user = this.userRepo.create(body); 51 | return this.userRepo.save(user); 52 | } 53 | 54 | @Put(':id') 55 | async update(@Param('id') id: string, @Body() body: User): Promise { 56 | await this.userRepo.findOneOrFail(+id); 57 | this.userRepo.update({id: +id}, body); 58 | return this.userRepo.findOneOrFail(+id); 59 | } 60 | 61 | @Delete(':id') 62 | @HttpCode(204) 63 | async destroy(@Param('id') id: string): Promise { ///204 - No content 64 | await this.userRepo.findOneOrFail(+id); 65 | this.userRepo.delete(id); 66 | } 67 | } -------------------------------------------------------------------------------- /backend-api/src/dto/room.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsNotEmpty } from 'class-validator'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class RoomDto { 5 | @ApiProperty({ 6 | type: String, 7 | description: "Name of room", 8 | }) 9 | @IsString() 10 | @IsNotEmpty() 11 | readonly name: string; 12 | } -------------------------------------------------------------------------------- /backend-api/src/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import {IsString, IsNotEmpty, IsEmail} from 'class-validator'; 3 | 4 | export class UserDto{ 5 | 6 | @ApiProperty({ 7 | type: String, 8 | description: 'name of user' 9 | }) 10 | @IsString() 11 | @IsNotEmpty() 12 | name: string; 13 | 14 | @ApiProperty() 15 | @IsEmail() 16 | @IsNotEmpty() 17 | email: string; 18 | } -------------------------------------------------------------------------------- /backend-api/src/exception-filters/entity-not-found.exception-filter.ts: -------------------------------------------------------------------------------- 1 | import { ExceptionFilter, ArgumentsHost } from '@nestjs/common'; 2 | import { EntityNotFoundError } from 'typeorm/error/EntityNotFoundError'; 3 | import { Response } from 'express'; 4 | 5 | export class EntityNotFoundExceptionFilter implements ExceptionFilter { 6 | catch(exception: EntityNotFoundError, host: ArgumentsHost) { 7 | const ctx = host.switchToHttp(); 8 | const response = ctx.getResponse(); 9 | return response 10 | .status(404) 11 | .json({ 12 | message: { 13 | statusCode: 404, 14 | error: 'Not Found', 15 | message: exception.message, 16 | }, 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend-api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import {DocumentBuilder, SwaggerModule} from '@nestjs/swagger'; 4 | import { EntityNotFoundExceptionFilter } from './exception-filters/entity-not-found.exception-filter'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule, {cors: true}); 8 | app.useGlobalFilters(new EntityNotFoundExceptionFilter()); 9 | 10 | const options = new DocumentBuilder() 11 | .setTitle('Nest.js API Fullcycle') 12 | .setDescription('Documentação da API do Nest.js') 13 | .build(); 14 | 15 | const document = SwaggerModule.createDocument(app, options); 16 | SwaggerModule.setup('api', app, document); 17 | 18 | await app.listen(3000); 19 | } 20 | bootstrap(); 21 | -------------------------------------------------------------------------------- /backend-api/src/migrations/1597362274197-CreateUserTable.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner, Table} from "typeorm"; 2 | 3 | export class CreateUserTable1597362274197 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable(new Table({ 7 | name: 'user', 8 | columns: [ 9 | { 10 | name: 'id', 11 | type: 'int', 12 | isPrimary: true, 13 | isGenerated: true, 14 | generationStrategy: 'increment' 15 | }, 16 | { 17 | name: 'name', 18 | type: 'varchar', 19 | }, 20 | { 21 | name: 'email', 22 | type: 'varchar', 23 | }, 24 | { 25 | name: 'created_at', 26 | type: 'timestamp', 27 | default: 'CURRENT_TIMESTAMP' 28 | } 29 | ] 30 | })) 31 | } 32 | 33 | public async down(queryRunner: QueryRunner): Promise { 34 | await queryRunner.dropTable('user'); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /backend-api/src/migrations/1597518554584-CreateRoomTable.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner, Table} from "typeorm"; 2 | 3 | export class CreateRoomTable1597518554584 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable(new Table({ 7 | name: 'room', 8 | columns: [ 9 | { 10 | name: 'id', 11 | type: 'int', 12 | isPrimary: true, 13 | isGenerated: true, 14 | generationStrategy: 'increment' 15 | }, 16 | { 17 | name: 'name', 18 | type: 'text' 19 | }, 20 | { 21 | name: 'created_at', 22 | type: 'timestamp', 23 | default: 'CURRENT_TIMESTAMP' 24 | } 25 | ] 26 | })) 27 | } 28 | 29 | public async down(queryRunner: QueryRunner): Promise { 30 | await queryRunner.dropTable('room') 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /backend-api/src/models/room.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn} from "typeorm"; 2 | 3 | @Entity() 4 | export class Room { 5 | 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column() 10 | name: string; 11 | 12 | @CreateDateColumn({type: 'timestamp'}) 13 | created_at: Date; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend-api/src/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn} from "typeorm"; 2 | import {Exclude} from 'class-transformer'; 3 | // 4 | @Entity() 5 | export class User{ 6 | 7 | @PrimaryGeneratedColumn() 8 | id: number; 9 | 10 | @Column() 11 | name: string; 12 | 13 | @Column() 14 | email: string; 15 | 16 | @Exclude() 17 | @CreateDateColumn({type: 'timestamp'}) 18 | created_at: Date 19 | } -------------------------------------------------------------------------------- /backend-api/src/websocket/websocket.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { WebsocketService } from './websocket.service'; 3 | 4 | describe('WebsocketService', () => { 5 | let service: WebsocketService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [WebsocketService], 10 | }).compile(); 11 | 12 | service = module.get(WebsocketService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /backend-api/src/websocket/websocket.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | WebSocketGateway, 3 | WebSocketServer, 4 | OnGatewayConnection, 5 | SubscribeMessage, 6 | MessageBody, 7 | ConnectedSocket, 8 | } from '@nestjs/websockets'; 9 | import { Server, Socket } from 'socket.io'; 10 | 11 | @WebSocketGateway(0, { namespace: 'room' }) 12 | export class WebsocketService implements OnGatewayConnection { 13 | @WebSocketServer() 14 | private server: Server; 15 | 16 | private users = {}; 17 | 18 | handleConnection(client: Socket, ...args: any[]) { 19 | client.disconnect(true) 20 | // const {name} = client.handshake.query; 21 | // this.users[client.id] = {name}; 22 | // console.log(this.users); 23 | } 24 | //web - Nginx | Apache - Aplicação 25 | @SubscribeMessage('join') 26 | joinRoom( 27 | @ConnectedSocket() client: Socket, 28 | @MessageBody() body: { name: string; room_id: string }, 29 | ): void { 30 | //varro o array de users e todos da room_id 23 31 | //contar, número é atual 2 disconnect 32 | // 33 | const { name, room_id } = body; 34 | this.users[client.id] = { name, room_id }; 35 | client.join(room_id); 36 | 37 | console.log('join', client.id, body); 38 | } 39 | 40 | @SubscribeMessage('send-message') 41 | sendMessage( 42 | @ConnectedSocket() client: Socket, 43 | @MessageBody() body: { message: string }, 44 | ): void { 45 | const { name, room_id } = this.users[client.id]; 46 | client.broadcast.to(room_id).emit('receive-message', { ...body, name }); 47 | console.log(body); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backend-api/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 { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 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 | -------------------------------------------------------------------------------- /backend-api/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 | -------------------------------------------------------------------------------- /backend-api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /backend-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend-api/user.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:3000/users 2 | Content-type: application/json 3 | 4 | { 5 | "name": "Luiz Carlos", 6 | "email": "argentinaluiz@gmail.com" 7 | } 8 | 9 | ### 10 | 11 | GET http://localhost:3000/users 12 | 13 | ### 14 | GET http://localhost:3000/users/1 15 | 16 | ### 17 | PUT http://localhost:3000/users/1 18 | Content-type: application/json 19 | 20 | { 21 | "name": "Luiz Carlos - teste44444", 22 | "email": "argentinaluiz1@gmail.com" 23 | } 24 | 25 | ### 26 | DELETE http://localhost:3000/users/1 27 | 28 | ### 29 | POST http://localhost:3000/users 30 | Content-type: application/json 31 | 32 | { 33 | "name": "adfadsf", 34 | "email": "teste@teste.com" 35 | } -------------------------------------------------------------------------------- /frontend/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/docker-existing-docker-compose 3 | // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. 4 | { 5 | "name": "react-esquenta-4", 6 | 7 | // Update the 'dockerComposeFile' list if you have more compose files or use different names. 8 | // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. 9 | "dockerComposeFile": [ 10 | "../docker-compose.yml", 11 | "docker-compose.yml" 12 | ], 13 | 14 | // The 'service' property is the name of the service for the container that VS Code should 15 | // use. Update this value and .devcontainer/docker-compose.yml to the real service name. 16 | "service": "app", 17 | 18 | // The optional 'workspaceFolder' property is the path VS Code should open by default when 19 | // connected. This is typically a file mount in .devcontainer/docker-compose.yml 20 | "workspaceFolder": "/react", 21 | 22 | // Set *default* container specific settings.json values on container create. 23 | "settings": { 24 | "terminal.integrated.shell.linux": null 25 | }, 26 | 27 | // Add the IDs of extensions you want installed when the container is created. 28 | "extensions": [] 29 | 30 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 31 | // "forwardPorts": [], 32 | 33 | // Uncomment the next line if you want start specific services in your Docker Compose config. 34 | // "runServices": [], 35 | 36 | // Uncomment the next line if you want to keep your containers running after VS Code shuts down. 37 | // "shutdownAction": "none", 38 | 39 | // Uncomment the next line to run commands after the container is created - for example installing curl. 40 | // "postCreateCommand": "apt-get update && apt-get install -y curl", 41 | 42 | // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root. 43 | // "remoteUser": "vscode" 44 | } 45 | -------------------------------------------------------------------------------- /frontend/.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | # Update this to the name of the service you want to work with in your docker-compose.yml file 4 | app: 5 | # If you want add a non-root user to your Dockerfile, you can use the "remoteUser" 6 | # property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks, 7 | # debugging) to execute as the user. Uncomment the next line if you want the entire 8 | # container to run as this user instead. Note that, on Linux, you may need to 9 | # ensure the UID and GID of the container user you create matches your local user. 10 | # See https://aka.ms/vscode-remote/containers/non-root for details. 11 | # 12 | # user: vscode 13 | 14 | # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer 15 | # folder. Note that the path of the Dockerfile and context is relative to the *primary* 16 | # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" 17 | # array). The sample below assumes your primary file is in the root of your project. 18 | # 19 | # build: 20 | # context: . 21 | # dockerfile: .devcontainer/Dockerfile 22 | 23 | volumes: 24 | # Update this to wherever you want VS Code to mount the folder of your project 25 | - .:/react:cached 26 | 27 | # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details. 28 | # - /var/run/docker.sock:/var/run/docker.sock 29 | 30 | # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. 31 | # cap_add: 32 | # - SYS_PTRACE 33 | # security_opt: 34 | # - seccomp:unconfined 35 | 36 | # Overrides default command so things don't shut down after the process ends. 37 | command: /bin/sh -c "while sleep 1000; do :; done" 38 | 39 | -------------------------------------------------------------------------------- /frontend/.docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #if [ ! -f ".env" ]; then 3 | # cp .env.example .env 4 | #fi 5 | 6 | npm install 7 | 8 | npm start -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_KEYCLOAK_JSON={"realm":"Chat","auth-server-url":"http://host.docker.internal:8080/auth/","ssl-required":"external","resource":"react","public-client":true,"confidential-port":0} -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .npm-cache/ 25 | .history/ 26 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.14.0-alpine3.11 2 | 3 | RUN apk add --no-cache bash 4 | 5 | RUN touch /home/node/.bashrc | echo "PS1='\w\$ '" >> /home/node/.bashrc 6 | 7 | RUN npm config set cache /home/node/app/.npm-cache --global 8 | 9 | USER node 10 | 11 | WORKDIR /home/node/app 12 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /frontend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | container_name: esquenta-4-frontend 8 | entrypoint: ./.docker/entrypoint.sh 9 | environment: 10 | - CHOKIDAR_USEPOLLING=true 11 | ports: 12 | - 3001:3000 13 | volumes: 14 | - .:/home/node/app 15 | tty: true -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@react-keycloak/web": "^2.1.3", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.5.0", 9 | "@testing-library/user-event": "^7.2.1", 10 | "@types/jest": "^24.9.1", 11 | "@types/node": "^12.12.54", 12 | "@types/react": "^16.9.46", 13 | "@types/react-dom": "^16.9.8", 14 | "@types/react-router-dom": "^5.1.5", 15 | "@types/socket.io-client": "^1.4.33", 16 | "axios": "^0.19.2", 17 | "keycloak-js": "^11.0.0", 18 | "react": "^16.13.1", 19 | "react-dom": "^16.13.1", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "3.4.3", 22 | "socket.io-client": "^2.3.0", 23 | "typescript": "^3.7.5" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": "react-app" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratona4-esquenta/f04199435be783afeb62e974f8165c43bc9c09f8/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratona4-esquenta/f04199435be783afeb62e974f8165c43bc9c09f8/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratona4-esquenta/f04199435be783afeb62e974f8165c43bc9c09f8/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { KeycloakProvider } from "@react-keycloak/web"; 3 | import { keycloak, keycloakProviderInitConfig } from "./utils/auth"; 4 | import { AppRouter } from "./AppRouter"; 5 | //JSX 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /frontend/src/AppRouter.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import { BrowserRouter, Switch, Route } from "react-router-dom"; 4 | import { Room } from "./components/Room"; 5 | import { Chat } from "./components/Chat"; 6 | import { useKeycloak } from "@react-keycloak/web"; 7 | import { PrivateRoute } from "./components/PrivateRoute"; 8 | import { Login } from "./components/Login"; 9 | type Props = {}; 10 | export const AppRouter = (props: Props) => { 11 | const {initialized} = useKeycloak(); 12 | 13 | if(!initialized){ 14 | return
Carregando...
15 | } 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /frontend/src/components/Chat.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import io from "socket.io-client"; 4 | import { useLocation } from "react-router-dom"; 5 | 6 | type Props = {}; 7 | //memory 8 | interface Message { 9 | name: string; 10 | message: string; 11 | } 12 | 13 | export const Chat = (props: Props) => { 14 | const location = useLocation(); 15 | const queryParams = React.useMemo(() => { 16 | const query = new URLSearchParams(location.search); 17 | return { 18 | room_id: query.get("room_id"), 19 | name: query.get("name"), 20 | }; 21 | }, [location]); 22 | 23 | // const socket = React.useMemo( 24 | // () => io("http://localhost:3000/room", { query: { name: queryParams.name } }), 25 | // [queryParams] 26 | // ); 27 | const socket = React.useMemo( 28 | () => io("http://localhost:3000/room"), 29 | [] 30 | ); 31 | const inputRef = React.useRef() as React.MutableRefObject; 32 | 33 | const [messages, setMessages] = React.useState([]); 34 | 35 | React.useEffect(() => { 36 | socket.on( 37 | "receive-message", 38 | (content: { message: string; name: string }) => { 39 | console.log(content); 40 | setMessages((prevState) => [...prevState, content]); 41 | } 42 | ); 43 | 44 | socket.on("connect", () => { 45 | console.log("abriu a conexão"); 46 | socket.emit('join', { 47 | name: queryParams.name, 48 | room_id: queryParams.room_id 49 | }) 50 | }); 51 | }, [socket, queryParams]); 52 | 53 | function sendMessage() { 54 | const message = inputRef.current.value; 55 | socket.emit("send-message", { message }); 56 | const name = queryParams.name as string; 57 | const content = { message, name }; 58 | setMessages((prevState) => [...prevState, content]); 59 | } 60 | 61 | return ( 62 |
63 |

Chat Fullcycle

64 |
    65 | {messages.map((message, key) => ( 66 |
  • 67 | {message.name} - {message.message} 68 |
  • 69 | ))} 70 |
71 |

72 | 73 | 74 | 77 |

78 |
79 | ); 80 | }; 81 | -------------------------------------------------------------------------------- /frontend/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import { useKeycloak } from "@react-keycloak/web"; 4 | import { useLocation, Redirect } from "react-router-dom"; 5 | type Props = {}; 6 | export const Login = (props: Props) => { 7 | const { keycloak } = useKeycloak(); 8 | const location = useLocation(); 9 | 10 | //@ts-ignore 11 | const { from } = location.state || { from: { pathname: "/" } }; 12 | if (keycloak?.authenticated) { 13 | return ; 14 | } else { 15 | keycloak?.login(); 16 | return
Carregando...
; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/components/PrivateRoute.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import { RouteProps, Route, Redirect } from "react-router-dom"; 4 | import { keycloak } from "../utils/auth"; 5 | interface PrivateRouteProps extends RouteProps {} 6 | export const PrivateRoute: React.FC = (props) => { 7 | const { component, ...other } = props; 8 | const Component: any = component; 9 | return ( 10 | { 13 | return keycloak.authenticated ? ( 14 | 15 | ) : ( 16 | 22 | ); 23 | }} 24 | /> 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /frontend/src/components/Room.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { useState, useEffect, useRef, MutableRefObject } from "react"; 3 | import axios from "axios"; 4 | import { useHistory } from "react-router-dom"; 5 | import { useKeycloak } from "@react-keycloak/web"; 6 | import { hostname } from "os"; 7 | type Props = {}; 8 | 9 | interface RoomModel { 10 | id: string; 11 | name: string; 12 | } 13 | //ky 14 | let countRender = 0; 15 | export const Room = (props: Props) => { 16 | const { keycloak } = useKeycloak(); 17 | 18 | const [rooms, setRooms] = useState([]); 19 | const inputRef = useRef() as MutableRefObject; 20 | const history = useHistory(); 21 | console.log(++countRender); 22 | 23 | useEffect(() => { 24 | // (async () => { 25 | // const response = await axios.get("http://localhost:3000/rooms"); 26 | // setRooms(response.data); 27 | // })(); 28 | //interceptors 29 | //axiosInst.intercetpros.request.use((config) => config.headers return config) 30 | axios 31 | .get("http://localhost:3000/rooms", { 32 | headers: { 33 | Authorization: `Bearer ${keycloak?.token}`, 34 | }, 35 | }) 36 | .then((response) => { 37 | setRooms(response.data); 38 | }); 39 | return () => { 40 | //destruir coisas aqui 41 | }; 42 | }, []); 43 | 44 | function toChat(roomId: string) { 45 | const name = inputRef.current.value; 46 | history.push(`/chat?room_id=${roomId}&name=${name}`); 47 | //console.log(roomId, inputRef.current.value); 48 | } 49 | 50 | return ( 51 |
52 |

Salas

53 |

54 | 55 | 56 |

57 |
    58 | {rooms.map((room, key) => ( 59 |
  • toChat(room.id)}> 60 | {room.name} 61 |
  • 62 | ))} 63 |
64 |
65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /frontend/src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import Keycloak from "keycloak-js"; 2 | 3 | // @ts-ignore 4 | const keycloakConfig = JSON.parse(process.env.REACT_APP_KEYCLOAK_JSON); 5 | export const keycloak = Keycloak({ 6 | url: keycloakConfig["auth-server-url"], 7 | realm: keycloakConfig["realm"], 8 | clientId: keycloakConfig["resource"], 9 | }); 10 | //login-required | check-sso 11 | export const keycloakProviderInitConfig: Keycloak.KeycloakInitOptions = { 12 | onLoad: "check-sso" 13 | }; -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /presentations/Nest.js_ desenvolvimento de APIs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratona4-esquenta/f04199435be783afeb62e974f8165c43bc9c09f8/presentations/Nest.js_ desenvolvimento de APIs.pdf -------------------------------------------------------------------------------- /presentations/Realtime com websockets usando Nest.js e React.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/maratona4-esquenta/f04199435be783afeb62e974f8165c43bc9c09f8/presentations/Realtime com websockets usando Nest.js e React.pdf --------------------------------------------------------------------------------