├── .gitignore ├── README.md ├── apache-kafka ├── .gitignore ├── .vscode │ └── settings.json ├── README.md └── docker-compose.yaml ├── bank-api ├── .docker │ ├── entrypoint.prod.sh │ ├── entrypoint.sh │ └── postgres │ │ └── Dockerfile ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode │ └── settings.json ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── __docker-compose.yaml ├── docker-compose_bbx.yaml ├── docker-compose_cter.yaml ├── nest-cli.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── console.ts │ ├── controllers │ │ ├── bank-account │ │ │ ├── bank-account.controller.spec.ts │ │ │ └── bank-account.controller.ts │ │ ├── my-first │ │ │ ├── my-first.controller.spec.ts │ │ │ └── my-first.controller.ts │ │ ├── pix-key │ │ │ ├── pix-key.controller.spec.ts │ │ │ └── pix-key.controller.ts │ │ └── transaction │ │ │ ├── transaction.controller.spec.ts │ │ │ └── transaction.controller.ts │ ├── dto │ │ ├── pix-key-exists.dto.ts │ │ ├── pix-key.dto.ts │ │ └── transaction.dto.ts │ ├── exception-filters │ │ └── model-not-found.exception-filter.ts │ ├── fixtures │ │ ├── fixtures.command.ts │ │ └── fixtures │ │ │ ├── bank-001.ts │ │ │ └── bank-002.ts │ ├── grpc-types │ │ └── pix-service.grpc.ts │ ├── main.ts │ ├── migrations │ │ ├── 1612378578846-CreateBankAccountsTable.ts │ │ ├── 1612385462493-CreatePixKeyTable.ts │ │ └── 1612394636514-CreateTransactionsTable.ts │ ├── models │ │ ├── bank-account.model.ts │ │ ├── pix-key.model.ts │ │ └── transaction.model.ts │ ├── protofiles │ │ └── pixkey.proto │ └── subscribers │ │ └── transaction-subscriber │ │ ├── transaction-subscriber.service.spec.ts │ │ └── transaction-subscriber.service.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json ├── bank-frontend ├── .docker │ └── entrypoint.sh ├── .gitignore ├── .vscode │ └── settings.json ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── __docker-compose.yaml ├── docker-compose_bbx.yaml ├── docker-compose_cter.yaml ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── img │ │ ├── back.png │ │ ├── icon_banco.png │ │ ├── icon_user.png │ │ └── logo_pix.png │ └── vercel.svg ├── src │ ├── components │ │ ├── BankAccountBalance │ │ │ ├── BankAccountBalance.module.scss │ │ │ └── index.tsx │ │ ├── BankAccountCard │ │ │ ├── BankAccountCard.module.scss │ │ │ └── index.tsx │ │ ├── Button │ │ │ └── index.tsx │ │ ├── Card │ │ │ ├── Card.module.scss │ │ │ └── index.tsx │ │ ├── Footer │ │ │ ├── Footer.module.scss │ │ │ └── index.tsx │ │ ├── FormButtonActions │ │ │ ├── FormButtonActions.module.scss │ │ │ └── index.tsx │ │ ├── Input │ │ │ └── index.tsx │ │ ├── Layout.tsx │ │ ├── MainContent │ │ │ ├── MainContent.module.scss │ │ │ └── index.tsx │ │ ├── Navbar │ │ │ ├── Navbar.module.scss │ │ │ └── index.tsx │ │ ├── PixKeyCard │ │ │ ├── PixKeyCard.module.scss │ │ │ └── index.tsx │ │ ├── Select │ │ │ └── index.tsx │ │ └── Title │ │ │ ├── Title.module.scss │ │ │ └── index.tsx │ ├── context │ │ └── BankContext.ts │ ├── model.ts │ ├── pages │ │ ├── _app.tsx │ │ ├── api │ │ │ └── hello.tsx │ │ ├── bank-accounts │ │ │ ├── [id] │ │ │ │ ├── BankAccountDashboard.module.scss │ │ │ │ ├── _ActionLink.module.scss │ │ │ │ ├── _Header.module.scss │ │ │ │ ├── index.tsx │ │ │ │ └── pix │ │ │ │ │ ├── PixRegister.module.scss │ │ │ │ │ ├── register.tsx │ │ │ │ │ └── transactions │ │ │ │ │ └── register.tsx │ │ │ └── index.tsx │ │ └── pagina1 │ │ │ ├── index.tsx │ │ │ └── subpagina.tsx │ └── util │ │ ├── http.ts │ │ └── modal.ts ├── styles │ └── sass │ │ ├── _button.scss │ │ ├── _forms.scss │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ └── globals.scss └── tsconfig.json ├── codepix ├── .env.example ├── .gitignore ├── Dockerfile ├── Dockerfile.prod ├── LICENSE ├── README.md ├── application │ ├── factory │ │ └── factory.go │ ├── grpc │ │ ├── pb │ │ │ ├── pixkey.pb.go │ │ │ └── pixkey_grpc.pb.go │ │ ├── pix.go │ │ ├── protofiles │ │ │ └── pixkey.proto │ │ └── server.go │ ├── kafka │ │ ├── process.go │ │ └── producer.go │ ├── model │ │ └── transaction.go │ └── usecase │ │ ├── pix.go │ │ └── transaction.go ├── cmd │ ├── all.go │ ├── fixtures.go │ ├── grpc.go │ ├── kafka.go │ └── root.go ├── docker-compose.yaml ├── domain │ └── model │ │ ├── account.go │ │ ├── account_test.go │ │ ├── bank.go │ │ ├── bank_test.go │ │ ├── base.go │ │ ├── pixKey.go │ │ ├── pixKey_test.go │ │ ├── transaction.go │ │ └── transaction_test.go ├── go.mod ├── go.sum ├── infrastructure │ ├── db │ │ └── db.go │ └── repository │ │ ├── pix.go │ │ └── transaction.go └── main.go └── k8s ├── bankapi ├── configmap.yaml ├── deploy.yaml ├── secret.yaml └── service.yaml ├── bankapi002 ├── configmap.yaml ├── deploy.yaml ├── secret.yaml └── service.yaml ├── bankfrontend ├── configmap.yaml ├── deploy.yaml └── service.yaml ├── bankfrontend002 ├── configmap.yaml ├── deploy.yaml └── service.yaml └── codepix ├── configmap.yaml ├── deploy.yaml ├── secret.yaml └── service.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .pgdata 2 | .idea 3 | .env 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Imersão Fullcycle 1 - Codepix 2 | ![Imersão Full Stack && Full Cycle](https://events-fullcycle.s3.amazonaws.com/events-fullcycle/static/site/img/grupo_4417.png) 3 | 4 | Participe gratuitamente: https://imersao.fullcycle.com.br/ 5 | 6 | ## Sobre o repositório 7 | Esse repositório contém todo código utilizado durante as aulas para referência. 8 | 9 | Faça seu fork e também nos dê uma estrelinha para nos ajudar a divulgar o projeto. 10 | 11 | As instruções de instalações estão no README.md de cada projeto. 12 | -------------------------------------------------------------------------------- /apache-kafka/.gitignore: -------------------------------------------------------------------------------- 1 | .history/ -------------------------------------------------------------------------------- /apache-kafka/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#000000", 4 | "activityBar.activeBorder": "#782828", 5 | "activityBar.background": "#000000", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#782828", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "statusBar.background": "#000000", 11 | "statusBar.foreground": "#e7e7e7", 12 | "statusBarItem.hoverBackground": "#1a1a1a" 13 | }, 14 | "peacock.remoteColor": "#000000", 15 | "peacock.color": "#1a1a1a" 16 | } -------------------------------------------------------------------------------- /apache-kafka/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - CodePix 2 | 3 | ## Descrição 4 | 5 | Repositório do Apache Kafka (Mensageria) 6 | 7 | **Importante**: Roda-lo antes de qualquer apliação 8 | 9 | ## Configurar /etc/hosts 10 | 11 | A comunicação entre as aplicações se dá de forma direta através da rede da máquina. 12 | Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. 13 | 14 | Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): 15 | ``` 16 | 127.0.0.1 host.docker.internal 17 | ``` 18 | Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. 19 | 20 | ## Rodar a aplicação 21 | 22 | Execute os comandos: 23 | 24 | ``` 25 | docker-compose up 26 | ``` 27 | ***Importante***: Toda vez que para-lo de rodar, rode ```docker-compose down``` para destruir os volumes, senão ao rodar um UP novamente dará erro 28 | 29 | ### Para Windows 30 | 31 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=gCUPP4E8Msc](https://www.youtube.com/watch?v=gCUPP4E8Msc) 32 | 33 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 34 | -------------------------------------------------------------------------------- /apache-kafka/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | zookeeper: 4 | image: confluentinc/cp-zookeeper:latest 5 | environment: 6 | ZOOKEEPER_CLIENT_PORT: 2181 7 | 8 | kafka: 9 | image: confluentinc/cp-kafka:latest 10 | depends_on: 11 | - zookeeper 12 | ports: 13 | - "9092:9092" 14 | - "9094:9094" 15 | environment: 16 | KAFKA_BROKER_ID: 1 17 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 18 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 19 | KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL 20 | KAFKA_LISTENERS: INTERNAL://:9092,OUTSIDE://:9094 21 | KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,OUTSIDE://host.docker.internal:9094 22 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,OUTSIDE:PLAINTEXT 23 | extra_hosts: 24 | - "host.docker.internal:172.17.0.1" 25 | 26 | kafka-topics-generator: 27 | image: confluentinc/cp-kafka:latest 28 | depends_on: 29 | - kafka 30 | command: > 31 | bash -c 32 | "sleep 5s && 33 | kafka-topics --create --topic=transactions --if-not-exists --bootstrap-server=kafka:9092 && 34 | kafka-topics --create --topic=transaction_confirmation --if-not-exists --bootstrap-server=kafka:9092 && 35 | kafka-topics --create --topic=bank001 --if-not-exists --bootstrap-server=kafka:9092 && 36 | kafka-topics --create --topic=bank002 --if-not-exists --bootstrap-server=kafka:9092" 37 | 38 | control-center: 39 | image: confluentinc/cp-enterprise-control-center:6.0.1 40 | hostname: control-center 41 | depends_on: 42 | - kafka 43 | ports: 44 | - "9021:9021" 45 | environment: 46 | CONTROL_CENTER_BOOTSTRAP_SERVERS: 'kafka:9092' 47 | CONTROL_CENTER_REPLICATION_FACTOR: 1 48 | PORT: 9021 49 | 50 | -------------------------------------------------------------------------------- /bank-api/.docker/entrypoint.prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f ".env" ]; then 4 | cp .env.example .env 5 | fi 6 | 7 | npm run typeorm migration:run 8 | npm run start:prod -------------------------------------------------------------------------------- /bank-api/.docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -f ".env" ]; then 4 | cp .env.example .env 5 | fi 6 | 7 | npm install 8 | npm run typeorm migration:run 9 | npm run console fixtures 10 | npm run start:dev -------------------------------------------------------------------------------- /bank-api/.docker/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:11 2 | 3 | RUN usermod -u 1000 postgres -------------------------------------------------------------------------------- /bank-api/.env.example: -------------------------------------------------------------------------------- 1 | TYPEORM_CONNECTION=postgres 2 | #TYPEORM_HOST=db 3 | TYPEORM_USERNAME=postgres 4 | TYPEORM_PASSWORD=root 5 | #TYPEORM_DATABASE=nest 6 | TYPEORM_PORT=5432 7 | TYPEORM_ENTITIES=src/**/*.model.ts 8 | TYPEORM_ENTITIES_DIR=src/models 9 | TYPEORM_MIGRATIONS=src/migrations/**/*.ts 10 | TYPEORM_MIGRATIONS_DIR=src/migrations 11 | 12 | GRPC_URL=host.docker.internal:50051 13 | 14 | KAFKA_CLIENT_ID=codepix 15 | KAFKA_BROKER=host.docker.internal:9094 16 | KAFKA_CONSUMER_GROUP_ID= -------------------------------------------------------------------------------- /bank-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/recommended', 10 | 'prettier/@typescript-eslint', 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 | -------------------------------------------------------------------------------- /bank-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 | .history/ 37 | .docker/dbdata 38 | .docker/dbdatabbx 39 | .docker/dbdatacter 40 | dist-bbx 41 | dist-cter 42 | 43 | .env -------------------------------------------------------------------------------- /bank-api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /bank-api/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#b61a3d", 4 | "activityBar.activeBorder": "#133808", 5 | "activityBar.background": "#b61a3d", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#133808", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "statusBar.background": "#b61a3d", 11 | "statusBar.foreground": "#e7e7e7", 12 | "statusBarItem.hoverBackground": "#e0234e" 13 | }, 14 | "peacock.remoteColor": "#e0234e", 15 | "peacock.color": "#e0234e" 16 | } -------------------------------------------------------------------------------- /bank-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.15.4-alpine3.12 2 | 3 | RUN apk add --no-cache bash 4 | 5 | RUN npm i -g @nestjs/cli@7.4.1 6 | 7 | USER node 8 | 9 | WORKDIR /home/node/app -------------------------------------------------------------------------------- /bank-api/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:14.15.4-slim 2 | 3 | USER node 4 | 5 | RUN mkdir -p /home/node/app 6 | 7 | WORKDIR /home/node/app 8 | 9 | COPY --chown=node package*.json ./ 10 | 11 | RUN npm install 12 | 13 | COPY --chown=node ./ . 14 | 15 | RUN npm run build 16 | 17 | EXPOSE 3000 18 | 19 | CMD ["npm", "run", "start:prod"] -------------------------------------------------------------------------------- /bank-api/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - CodePix 2 | 3 | ## Descrição 4 | 5 | Repositório da API feita com Nest.js (API dos Bancos) 6 | 7 | **Importante**: A aplicação do Apache Kafka e Golang deve estar rodando primeiro. 8 | 9 | ## Configurar /etc/hosts 10 | 11 | A comunicação entre as aplicações se dá de forma direta através da rede da máquina. 12 | Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. 13 | 14 | Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): 15 | ``` 16 | 127.0.0.1 host.docker.internal 17 | ``` 18 | Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. 19 | 20 | ## Rodar a aplicação 21 | 22 | Execute os comandos: 23 | ``` 24 | mkdir dist-bbx 25 | mkdir dist-cter 26 | 27 | ## Rodar banco BBX 28 | docker-compose -f docker-compose_bbx.yaml up 29 | 30 | ## Rodar banco CTER 31 | docker-compose -f docker-compose_cter.yaml up 32 | ``` 33 | 34 | Espere os logs verdinhos do Nest para verificar se deu certo. 35 | 36 | * Acessar http://localhost:8001/bank-accounts para listar as contas bancárias do banco BBX. 37 | * Acessar http://localhost:8002/bank-accounts para listar as contas bancárias do banco CTER. 38 | 39 | ### Para Windows 40 | 41 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=gCUPP4E8Msc](https://www.youtube.com/watch?v=gCUPP4E8Msc) 42 | 43 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 44 | -------------------------------------------------------------------------------- /bank-api/__docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | entrypoint: .docker/entrypoint.sh 8 | container_name: imersao-fullcycle-nest-bank 9 | ports: 10 | - 3000:3000 11 | volumes: 12 | - .:/home/node/app 13 | depends_on: 14 | - db 15 | extra_hosts: 16 | - "host.docker.internal:172.17.0.1" 17 | 18 | db: 19 | build: .docker/postgres 20 | container_name: imersao-fullcycle-nest-bank-db 21 | restart: always 22 | tty: true 23 | volumes: 24 | - .docker/dbdata:/var/lib/postgresql/data 25 | environment: 26 | - POSTGRES_PASSWORD=root 27 | - POSTGRES_DB=nest -------------------------------------------------------------------------------- /bank-api/docker-compose_bbx.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app_bbx: 6 | build: . 7 | entrypoint: .docker/entrypoint.sh 8 | container_name: imersao-fullcycle-nest-bank-bbx 9 | environment: 10 | - TYPEORM_DATABASE=bbx 11 | - TYPEORM_HOST=db_bbx 12 | - BANK_CODE=001 13 | ports: 14 | - 8001:3000 15 | volumes: 16 | - .:/home/node/app 17 | - ./dist-bbx:/home/node/app/dist 18 | depends_on: 19 | - db_bbx 20 | extra_hosts: 21 | - "host.docker.internal:172.17.0.1" 22 | networks: 23 | - bbx-network 24 | 25 | db_bbx: 26 | build: .docker/postgres 27 | container_name: imersao-fullcycle-nest-bank-db-bbx 28 | restart: always 29 | tty: true 30 | volumes: 31 | - .docker/dbdatabbx:/var/lib/postgresql/data 32 | environment: 33 | - POSTGRES_PASSWORD=root 34 | - POSTGRES_DB=bbx 35 | networks: 36 | - bbx-network 37 | 38 | networks: 39 | bbx-network: 40 | driver: bridge -------------------------------------------------------------------------------- /bank-api/docker-compose_cter.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app_cter: 6 | build: . 7 | entrypoint: .docker/entrypoint.sh 8 | container_name: imersao-fullcycle-nest-bank-cter 9 | environment: 10 | - TYPEORM_DATABASE=cter 11 | - TYPEORM_HOST=db_cter 12 | - BANK_CODE=002 13 | ports: 14 | - 8002:3000 15 | volumes: 16 | - .:/home/node/app 17 | - ./dist-cter:/home/node/app/dist 18 | depends_on: 19 | - db_cter 20 | extra_hosts: 21 | - "host.docker.internal:172.17.0.1" 22 | networks: 23 | - cter-network 24 | 25 | db_cter: 26 | build: .docker/postgres 27 | container_name: imersao-fullcycle-nest-bank-db-cter 28 | restart: always 29 | tty: true 30 | volumes: 31 | - .docker/dbdatacter:/var/lib/postgresql/data 32 | environment: 33 | - POSTGRES_PASSWORD=root 34 | - POSTGRES_DB=cter 35 | networks: 36 | - cter-network 37 | 38 | networks: 39 | cter-network: 40 | driver: bridge -------------------------------------------------------------------------------- /bank-api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "assets": [ 6 | "**/*.proto" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bank-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bank-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 ./node_modules/typeorm/cli.js", 23 | "console": "ts-node -r tsconfig-paths/register src/console.ts" 24 | }, 25 | "dependencies": { 26 | "@grpc/proto-loader": "^0.5.6", 27 | "@nestjs/common": "^7.5.1", 28 | "@nestjs/config": "^0.6.3", 29 | "@nestjs/core": "^7.5.1", 30 | "@nestjs/microservices": "^7.6.11", 31 | "@nestjs/platform-express": "^7.5.1", 32 | "@nestjs/typeorm": "^7.1.5", 33 | "class-transformer": "^0.3.2", 34 | "class-validator": "^0.13.1", 35 | "commander": "^5.1.0", 36 | "grpc": "^1.24.4", 37 | "kafkajs": "^1.15.0", 38 | "nestjs-console": "^4.0.0", 39 | "pg": "^8.5.1", 40 | "reflect-metadata": "^0.1.13", 41 | "rimraf": "^3.0.2", 42 | "rxjs": "^6.6.3", 43 | "typeorm": "^0.2.30", 44 | "uuid": "^8.3.2" 45 | }, 46 | "devDependencies": { 47 | "@nestjs/cli": "^7.5.1", 48 | "@nestjs/schematics": "^7.1.3", 49 | "@nestjs/testing": "^7.5.1", 50 | "@types/express": "^4.17.8", 51 | "@types/jest": "^26.0.15", 52 | "@types/node": "^14.14.6", 53 | "@types/supertest": "^2.0.10", 54 | "@typescript-eslint/eslint-plugin": "^4.6.1", 55 | "@typescript-eslint/parser": "^4.6.1", 56 | "eslint": "^7.12.1", 57 | "eslint-config-prettier": "7.2.0", 58 | "eslint-plugin-prettier": "^3.1.4", 59 | "jest": "^26.6.3", 60 | "prettier": "^2.1.2", 61 | "supertest": "^6.0.0", 62 | "ts-jest": "^26.4.3", 63 | "ts-loader": "^8.0.8", 64 | "ts-node": "^9.0.0", 65 | "tsconfig-paths": "^3.9.0", 66 | "typescript": "^4.0.5" 67 | }, 68 | "jest": { 69 | "moduleFileExtensions": [ 70 | "js", 71 | "json", 72 | "ts" 73 | ], 74 | "rootDir": "src", 75 | "testRegex": ".*\\.spec\\.ts$", 76 | "transform": { 77 | "^.+\\.(t|j)s$": "ts-jest" 78 | }, 79 | "collectCoverageFrom": [ 80 | "**/*.(t|j)s" 81 | ], 82 | "coverageDirectory": "../coverage", 83 | "testEnvironment": "node" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /bank-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 | -------------------------------------------------------------------------------- /bank-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 | -------------------------------------------------------------------------------- /bank-api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { TypeOrmModule } from '@nestjs/typeorm'; 4 | import { AppController } from './app.controller'; 5 | import { AppService } from './app.service'; 6 | import { MyFirstController } from './controllers/my-first/my-first.controller'; 7 | import { BankAccount } from './models/bank-account.model'; 8 | import { BankAccountController } from './controllers/bank-account/bank-account.controller'; 9 | import { ConsoleModule } from 'nestjs-console'; 10 | import { FixturesCommand } from './fixtures/fixtures.command'; 11 | import { PixKeyController } from './controllers/pix-key/pix-key.controller'; 12 | import { PixKey } from './models/pix-key.model'; 13 | import { ClientsModule, Transport } from '@nestjs/microservices'; 14 | import { join } from 'path'; 15 | import { TransactionController } from './controllers/transaction/transaction.controller'; 16 | import { Transaction } from './models/transaction.model'; 17 | import { TransactionSubscriber } from './subscribers/transaction-subscriber/transaction-subscriber.service'; 18 | 19 | @Module({ 20 | imports: [ 21 | ConfigModule.forRoot(), 22 | ConsoleModule, 23 | TypeOrmModule.forRoot({ 24 | type: process.env.TYPEORM_CONNECTION as any, 25 | host: process.env.TYPEORM_HOST, 26 | port: parseInt(process.env.TYPEORM_PORT), 27 | username: process.env.TYPEORM_USERNAME, 28 | password: process.env.TYPEORM_PASSWORD, 29 | database: process.env.TYPEORM_DATABASE, 30 | entities: [BankAccount, PixKey, Transaction] 31 | }), 32 | TypeOrmModule.forFeature([BankAccount, PixKey, Transaction]), 33 | ClientsModule.register([ 34 | { 35 | name: 'CODEPIX_PACKAGE', 36 | transport: Transport.GRPC, 37 | options: { 38 | url: process.env.GRPC_URL, 39 | package: 'github.com.codeedu.codepix', 40 | protoPath: [join(__dirname, 'protofiles/pixkey.proto')] 41 | } 42 | } 43 | ]), 44 | ClientsModule.register([ 45 | { 46 | name: 'TRANSACTION_SERVICE', 47 | transport: Transport.KAFKA, 48 | options: { 49 | client: { 50 | clientId: process.env.KAFKA_CLIENT_ID, 51 | brokers: [process.env.KAFKA_BROKER] 52 | }, 53 | consumer: { 54 | groupId: !process.env.KAFKA_CONSUMER_GROUP_ID || 55 | process.env.KAFKA_CONSUMER_GROUP_ID === '' 56 | ? 'my-consumer-' + Math.random() 57 | : process.env.KAFKA_CONSUMER_GROUP_ID, 58 | } 59 | } 60 | } 61 | ]) 62 | ], 63 | controllers: [AppController, MyFirstController, BankAccountController, PixKeyController, TransactionController], 64 | providers: [AppService, FixturesCommand, TransactionSubscriber], 65 | }) 66 | export class AppModule {} 67 | -------------------------------------------------------------------------------- /bank-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 | -------------------------------------------------------------------------------- /bank-api/src/console.ts: -------------------------------------------------------------------------------- 1 | import { BootstrapConsole } from "nestjs-console"; 2 | import { AppModule } from "./app.module"; 3 | 4 | const bootstrap = new BootstrapConsole({ 5 | module: AppModule, 6 | useDecorators: true 7 | }) 8 | 9 | bootstrap.init().then(async app => { 10 | try{ 11 | await app.init(); 12 | await bootstrap.boot(); 13 | process.exit(0); 14 | }catch(e){ 15 | console.error(e); 16 | process.exit(1); 17 | } 18 | }) -------------------------------------------------------------------------------- /bank-api/src/controllers/bank-account/bank-account.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { BankAccountController } from './bank-account.controller'; 3 | 4 | describe('BankAccountController', () => { 5 | let controller: BankAccountController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [BankAccountController], 10 | }).compile(); 11 | 12 | controller = module.get(BankAccountController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /bank-api/src/controllers/bank-account/bank-account.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { BankAccount } from 'src/models/bank-account.model'; 4 | import { Repository } from 'typeorm'; 5 | 6 | @Controller('bank-accounts') 7 | export class BankAccountController { 8 | constructor( 9 | @InjectRepository(BankAccount) 10 | private bankAccountRepo: Repository, 11 | ) {} 12 | 13 | @Get() 14 | index() { 15 | return this.bankAccountRepo.find(); 16 | } 17 | 18 | @Get(':bankAccountId') 19 | show( 20 | @Param('bankAccountId', new ParseUUIDPipe({ version: '4' })) 21 | bankAccountId: string, 22 | ) { 23 | return this.bankAccountRepo.findOneOrFail(bankAccountId); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bank-api/src/controllers/my-first/my-first.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { MyFirstController } from './my-first.controller'; 3 | 4 | describe('MyFirstController', () => { 5 | let controller: MyFirstController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [MyFirstController], 10 | }).compile(); 11 | 12 | controller = module.get(MyFirstController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /bank-api/src/controllers/my-first/my-first.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post } from '@nestjs/common'; 2 | 3 | @Controller('my-first') 4 | export class MyFirstController { 5 | 6 | @Get('hello-world') 7 | index(){ 8 | return {'key': 'value'}; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /bank-api/src/controllers/pix-key/pix-key.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PixKeyController } from './pix-key.controller'; 3 | 4 | describe('PixKeyController', () => { 5 | let controller: PixKeyController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [PixKeyController], 10 | }).compile(); 11 | 12 | controller = module.get(PixKeyController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /bank-api/src/controllers/pix-key/pix-key.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | HttpCode, 6 | Inject, 7 | InternalServerErrorException, 8 | NotFoundException, 9 | Param, 10 | ParseUUIDPipe, 11 | Post, 12 | Query, 13 | UnprocessableEntityException, 14 | ValidationPipe, 15 | } from '@nestjs/common'; 16 | import { ClientGrpc } from '@nestjs/microservices'; 17 | import { InjectRepository } from '@nestjs/typeorm'; 18 | import { PixKeyExistsDto } from 'src/dto/pix-key-exists.dto'; 19 | import { PixKeyDto } from 'src/dto/pix-key.dto'; 20 | import { PixService } from 'src/grpc-types/pix-service.grpc'; 21 | import { BankAccount } from 'src/models/bank-account.model'; 22 | import { PixKey } from 'src/models/pix-key.model'; 23 | import { Repository } from 'typeorm'; 24 | 25 | @Controller('bank-accounts/:bankAccountId/pix-keys') 26 | export class PixKeyController { 27 | constructor( 28 | @InjectRepository(PixKey) 29 | private pixKeyRepo: Repository, 30 | @InjectRepository(BankAccount) 31 | private bankAccountRepo: Repository, 32 | @Inject('CODEPIX_PACKAGE') 33 | private client: ClientGrpc, 34 | ) {} 35 | @Get() 36 | index( 37 | @Param('bankAccountId', new ParseUUIDPipe({ version: '4' })) 38 | bankAccountId: string, 39 | ) { 40 | return this.pixKeyRepo.find({ 41 | where: { 42 | bank_account_id: bankAccountId, 43 | }, 44 | order: { 45 | created_at: 'DESC', 46 | }, 47 | }); 48 | } 49 | 50 | @Post() 51 | async store( 52 | @Param('bankAccountId', new ParseUUIDPipe({ version: '4' })) 53 | bankAccountId: string, 54 | @Body(new ValidationPipe({ errorHttpStatusCode: 422 })) 55 | body: PixKeyDto, 56 | ) { 57 | await this.bankAccountRepo.findOneOrFail(bankAccountId); 58 | 59 | const pixService: PixService = this.client.getService('PixService'); 60 | const notFound = await this.checkPixKeyNotFound(body); 61 | if (!notFound) { 62 | throw new UnprocessableEntityException('PixKey already exists'); 63 | } 64 | 65 | const createdPixKey = await pixService 66 | .registerPixKey({ 67 | ...body, 68 | accountId: bankAccountId, 69 | }) 70 | .toPromise(); 71 | 72 | if (createdPixKey.error) { 73 | throw new InternalServerErrorException(createdPixKey.error); 74 | } 75 | 76 | const pixKey = this.pixKeyRepo.create({ 77 | id: createdPixKey.id, 78 | bank_account_id: bankAccountId, 79 | ...body, 80 | }); 81 | return await this.pixKeyRepo.save(pixKey); 82 | } 83 | 84 | async checkPixKeyNotFound(params: { key: string; kind: string }) { 85 | const pixService: PixService = this.client.getService('PixService'); 86 | try { 87 | await pixService.find(params).toPromise(); 88 | return false; 89 | } catch (e) { 90 | if (e.details === 'no key was found') { 91 | return true; 92 | } 93 | 94 | throw new InternalServerErrorException('Server not available'); 95 | } 96 | } 97 | 98 | @Get('exists') 99 | @HttpCode(204) 100 | async exists( 101 | @Query(new ValidationPipe({ errorHttpStatusCode: 422 })) 102 | params: PixKeyExistsDto, 103 | ) { 104 | const pixService: PixService = this.client.getService('PixService'); 105 | try { 106 | await pixService.find(params).toPromise(); 107 | } catch (e) { 108 | if (e.details === 'no key was found') { 109 | throw new NotFoundException(e.details); 110 | } 111 | 112 | throw new InternalServerErrorException('Server not available'); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /bank-api/src/controllers/transaction/transaction.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TransactionController } from './transaction.controller'; 3 | 4 | describe('TransactionController', () => { 5 | let controller: TransactionController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [TransactionController], 10 | }).compile(); 11 | 12 | controller = module.get(TransactionController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /bank-api/src/controllers/transaction/transaction.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Get, 5 | Inject, 6 | OnModuleDestroy, 7 | OnModuleInit, 8 | Param, 9 | ParseUUIDPipe, 10 | Post, 11 | ValidationPipe, 12 | } from '@nestjs/common'; 13 | import { ClientKafka, MessagePattern, Payload } from '@nestjs/microservices'; 14 | import { Producer } from '@nestjs/microservices/external/kafka.interface'; 15 | import { InjectRepository } from '@nestjs/typeorm'; 16 | import { TransactionDto } from 'src/dto/transaction.dto'; 17 | import { BankAccount } from 'src/models/bank-account.model'; 18 | import { PixKey } from 'src/models/pix-key.model'; 19 | import { 20 | Transaction, 21 | TransactionOperation, 22 | TransactionStatus, 23 | } from 'src/models/transaction.model'; 24 | import { Repository } from 'typeorm'; 25 | 26 | @Controller('bank-accounts/:bankAccountId/transactions') 27 | export class TransactionController implements OnModuleInit, OnModuleDestroy { 28 | private kafkaProducer: Producer; 29 | constructor( 30 | @InjectRepository(BankAccount) 31 | private bankAccountRepo: Repository, 32 | @InjectRepository(Transaction) 33 | private transactionRepo: Repository, 34 | @InjectRepository(PixKey) 35 | private pixKeyRepo: Repository, 36 | @Inject('TRANSACTION_SERVICE') 37 | private kafkaClient: ClientKafka, 38 | ) {} 39 | 40 | async onModuleInit() { 41 | this.kafkaProducer = await this.kafkaClient.connect(); 42 | } 43 | 44 | async onModuleDestroy() { 45 | await this.kafkaProducer.disconnect(); 46 | } 47 | 48 | @Get() 49 | index( 50 | @Param( 51 | 'bankAccountId', 52 | new ParseUUIDPipe({ version: '4', errorHttpStatusCode: 422 }), 53 | ) 54 | bankAccountId: string, 55 | ) { 56 | return this.transactionRepo.find({ 57 | where: { 58 | bank_account_id: bankAccountId, 59 | }, 60 | order: { 61 | created_at: 'DESC', 62 | }, 63 | }); 64 | } 65 | 66 | @Post() 67 | async store( 68 | @Param( 69 | 'bankAccountId', 70 | new ParseUUIDPipe({ version: '4', errorHttpStatusCode: 422 }), 71 | ) 72 | bankAccountId: string, 73 | @Body(new ValidationPipe({ errorHttpStatusCode: 422 })) 74 | body: TransactionDto, 75 | ) { 76 | await this.bankAccountRepo.findOneOrFail(bankAccountId); 77 | 78 | let transaction = this.transactionRepo.create({ 79 | ...body, 80 | amount: body.amount * -1, 81 | bank_account_id: bankAccountId, 82 | operation: TransactionOperation.debit, 83 | }); 84 | transaction = await this.transactionRepo.save(transaction); 85 | 86 | const sendData = { 87 | id: transaction.external_id, 88 | accountId: bankAccountId, 89 | amount: body.amount, 90 | pixkeyto: body.pix_key_key, 91 | pixKeyKindTo: body.pix_key_kind, 92 | description: body.description, 93 | }; 94 | 95 | await this.kafkaProducer.send({ 96 | topic: 'transactions', 97 | messages: [{ key: 'transactions', value: JSON.stringify(sendData) }], 98 | }); 99 | 100 | return transaction; 101 | } 102 | 103 | @MessagePattern(`bank${process.env.BANK_CODE}`) 104 | async doTransaction(@Payload() message) { 105 | if (message.value.status === 'pending') { 106 | await this.receivedTransaction(message.value); 107 | } 108 | 109 | if (message.value.status === 'confirmed') { 110 | await this.confirmedTransaction(message.value); 111 | } 112 | } 113 | 114 | async receivedTransaction(data) { 115 | const pixKey = await this.pixKeyRepo.findOneOrFail({ 116 | where: { 117 | key: data.pixKeyTo, 118 | kind: data.pixKeyKindTo, 119 | }, 120 | }); 121 | 122 | const transaction = this.transactionRepo.create({ 123 | external_id: data.id, 124 | amount: data.amount, 125 | description: data.description, 126 | bank_account_id: pixKey.bank_account_id, 127 | bank_account_from_id: data.accountId, 128 | operation: TransactionOperation.credit, 129 | status: TransactionStatus.completed, 130 | }); 131 | 132 | this.transactionRepo.save(transaction); 133 | 134 | const sendData = { 135 | ...data, 136 | status: 'confirmed', 137 | }; 138 | 139 | await this.kafkaProducer.send({ 140 | topic: 'transaction_confirmation', 141 | messages: [ 142 | { key: 'transaction_confirmation', value: JSON.stringify(sendData) }, 143 | ], 144 | }); 145 | } 146 | 147 | async confirmedTransaction(data) { 148 | const transaction = await this.transactionRepo.findOneOrFail({ 149 | where: { 150 | external_id: data.id, 151 | }, 152 | }); 153 | 154 | await this.transactionRepo.update( 155 | { id: data.id }, 156 | { 157 | status: TransactionStatus.completed, 158 | }, 159 | ); 160 | 161 | const sendData = { 162 | id: data.id, 163 | accountId: transaction.bank_account_id, 164 | amount: Math.abs(transaction.amount), 165 | pixkeyto: transaction.pix_key_key, 166 | pixKeyKindTo: transaction.pix_key_kind, 167 | description: transaction.description, 168 | status: TransactionStatus.completed, 169 | }; 170 | 171 | await this.kafkaProducer.send({ 172 | topic: 'transaction_confirmation', 173 | messages: [ 174 | { key: 'transaction_confirmation', value: JSON.stringify(sendData) }, 175 | ], 176 | }); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /bank-api/src/dto/pix-key-exists.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsIn, IsNotEmpty, IsString } from "class-validator"; 2 | import { PixKeyKind } from "src/models/pix-key.model"; 3 | 4 | export class PixKeyExistsDto{ 5 | @IsString() 6 | @IsNotEmpty() 7 | readonly key: string; 8 | 9 | @IsString() 10 | @IsIn(Object.values(PixKeyKind)) 11 | @IsNotEmpty() 12 | readonly kind: PixKeyKind; 13 | } -------------------------------------------------------------------------------- /bank-api/src/dto/pix-key.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsIn, IsNotEmpty, IsString } from "class-validator"; 2 | import { PixKeyKind } from "src/models/pix-key.model"; 3 | 4 | export class PixKeyDto{ 5 | @IsString() 6 | @IsNotEmpty() 7 | readonly key: string; 8 | 9 | @IsString() 10 | @IsIn(Object.values(PixKeyKind)) 11 | @IsNotEmpty() 12 | readonly kind: PixKeyKind; 13 | } -------------------------------------------------------------------------------- /bank-api/src/dto/transaction.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsNotEmpty, IsNumber, Min, IsOptional } from 'class-validator'; 2 | import { PixKeyKind } from 'src/models/pix-key.model'; 3 | 4 | export class TransactionDto { 5 | @IsString() 6 | @IsNotEmpty() 7 | pix_key_key : string; 8 | 9 | @IsString() 10 | @IsNotEmpty() 11 | pix_key_kind : PixKeyKind; 12 | 13 | @IsString() 14 | @IsOptional() 15 | description : string = null; 16 | 17 | @IsNumber({ maxDecimalPlaces: 2 }) 18 | @Min(0.01) 19 | @IsNotEmpty() 20 | readonly amount: number; 21 | } 22 | -------------------------------------------------------------------------------- /bank-api/src/exception-filters/model-not-found.exception-filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; 2 | import { EntityNotFoundError } from 'typeorm/error/EntityNotFoundError'; 3 | import { Response } from 'express'; 4 | 5 | @Catch(EntityNotFoundError) 6 | export class ModelNotFoundExceptionFilter implements ExceptionFilter { 7 | catch(exception: EntityNotFoundError, host: ArgumentsHost) { 8 | const context = host.switchToHttp(); 9 | const response = context.getResponse(); 10 | return response.status(404).json({ 11 | error: { 12 | error: 'Not Found', 13 | message: exception.message, 14 | }, 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bank-api/src/fixtures/fixtures.command.ts: -------------------------------------------------------------------------------- 1 | import { Console, Command } from "nestjs-console"; 2 | import { getConnection } from "typeorm"; 3 | import * as chalk from 'chalk'; 4 | 5 | @Console() 6 | export class FixturesCommand{ 7 | 8 | @Command({ 9 | command: 'fixtures', 10 | description: 'Seed data in database' 11 | }) 12 | async command(){ 13 | await this.runMigrations(); 14 | const fixtures = (await import(`./fixtures/bank-${process.env.BANK_CODE}`)).default 15 | for(const fixture of fixtures){ 16 | await this.createInDatabase(fixture.model, fixture.fields); 17 | } 18 | 19 | console.log(chalk.green('Data generated')); 20 | } 21 | 22 | async runMigrations(){ 23 | const conn = getConnection('default'); 24 | for(const migration of conn.migrations.reverse()){ 25 | await conn.undoLastMigration(); 26 | } 27 | } 28 | 29 | async createInDatabase(model: any, data: any){ 30 | const repository = this.getRepository(model); 31 | const obj = repository.create(data); 32 | await repository.save(obj); 33 | } 34 | 35 | getRepository(model: any){ 36 | const conn = getConnection('default'); 37 | return conn.getRepository(model); 38 | } 39 | } -------------------------------------------------------------------------------- /bank-api/src/fixtures/fixtures/bank-001.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | model: 'BankAccount', 4 | fields: { 5 | id: '6e4635ce-88d1-4e58-9597-d13fc446ee47', 6 | account_number: '1111', 7 | owner_name: 'User BBX 1', 8 | }, 9 | }, 10 | { 11 | model: 'BankAccount', 12 | fields: { 13 | id: '51a720b2-5144-4d7f-921d-57023b1e24c1', 14 | account_number: '2222', 15 | owner_name: 'User BBX 2', 16 | }, 17 | }, 18 | ]; 19 | -------------------------------------------------------------------------------- /bank-api/src/fixtures/fixtures/bank-002.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | "model": "BankAccount", 4 | "fields": { 5 | "id": "103cc632-78e7-4476-ab63-d5ad3a562d26", 6 | "account_number": "3333", 7 | "owner_name": "User CTER 1", 8 | } 9 | }, 10 | { 11 | "model": "BankAccount", 12 | "fields": { 13 | "id": "463b1b2a-b5fa-4b88-9c31-e5c894a20ae3", 14 | "account_number": "4444", 15 | "owner_name": "User CTER 2", 16 | } 17 | }, 18 | ] -------------------------------------------------------------------------------- /bank-api/src/grpc-types/pix-service.grpc.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | 3 | interface Account { 4 | accountId: string; 5 | accountNumber: string; 6 | bankId: string; 7 | bankName: string; 8 | ownerName: string; 9 | createdAt: string; 10 | } 11 | 12 | export interface PixKeyFindResult { 13 | id: string; 14 | kind: string; 15 | key: string; 16 | account: Account; 17 | createdAt: string; 18 | } 19 | 20 | export interface PixService { 21 | registerPixKey: (data: { 22 | kind: string; 23 | key: string; 24 | accountId: string; 25 | }) => Observable<{ id: string; status: string; error: string }>; 26 | find: (data: { kind: string; key: string }) => Observable; 27 | } -------------------------------------------------------------------------------- /bank-api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { Transport } from '@nestjs/microservices'; 3 | import { AppModule } from './app.module'; 4 | import { ModelNotFoundExceptionFilter } from './exception-filters/model-not-found.exception-filter'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule, {cors: true}); 8 | app.setGlobalPrefix('api'); 9 | app.useGlobalFilters(new ModelNotFoundExceptionFilter()); 10 | app.connectMicroservice({ 11 | transport: Transport.KAFKA, 12 | options: { 13 | client: { 14 | brokers: [process.env.KAFKA_BROKER], 15 | }, 16 | consumer: { 17 | groupId: 18 | !process.env.KAFKA_CONSUMER_GROUP_ID || 19 | process.env.KAFKA_CONSUMER_GROUP_ID === '' 20 | ? 'my-consumer-' + Math.random() 21 | : process.env.KAFKA_CONSUMER_GROUP_ID, 22 | }, 23 | }, 24 | }); 25 | app.startAllMicroservices(); 26 | await app.listen(3000); 27 | } 28 | bootstrap(); 29 | -------------------------------------------------------------------------------- /bank-api/src/migrations/1612378578846-CreateBankAccountsTable.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner, Table} from "typeorm"; 2 | 3 | export class CreateBankAccountsTable1612378578846 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable( 7 | new Table({ 8 | name: 'bank_accounts', 9 | columns: [ 10 | { 11 | name: 'id', 12 | type: 'uuid', 13 | isPrimary: true 14 | }, 15 | { 16 | name: 'account_number', 17 | type: 'varchar' 18 | }, 19 | { 20 | name: 'owner_name', 21 | type: 'varchar' 22 | }, 23 | { 24 | name: 'balance', 25 | type: 'double precision' 26 | }, 27 | { 28 | name: 'created_at', 29 | type: 'timestamp', 30 | default: 'CURRENT_TIMESTAMP' 31 | } 32 | ] 33 | }) 34 | ) 35 | } 36 | 37 | public async down(queryRunner: QueryRunner): Promise { 38 | await queryRunner.dropTable('bank_accounts') 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /bank-api/src/migrations/1612385462493-CreatePixKeyTable.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; 2 | 3 | export class CreatePixKeyTable1612385462493 implements MigrationInterface { 4 | public async up(queryRunner: QueryRunner): Promise { 5 | await queryRunner.createTable( 6 | new Table({ 7 | name: 'pix_keys', 8 | columns: [ 9 | { 10 | name: 'id', 11 | type: 'uuid', 12 | isPrimary: true, 13 | }, 14 | { 15 | name: 'kind', 16 | type: 'varchar', 17 | }, 18 | { 19 | name: 'key', 20 | type: 'varchar', 21 | }, 22 | { 23 | name: 'bank_account_id', 24 | type: 'uuid', 25 | }, 26 | { 27 | name: 'created_at', 28 | type: 'timestamp', 29 | default: 'CURRENT_TIMESTAMP', 30 | }, 31 | ], 32 | }), 33 | ); 34 | 35 | await queryRunner.createForeignKey( 36 | 'pix_keys', 37 | new TableForeignKey({ 38 | name: 'pix_keys_bank_account_id_foreign_key', 39 | columnNames: ['bank_account_id'], 40 | referencedColumnNames: ['id'], 41 | referencedTableName: 'bank_accounts', 42 | }), 43 | ); 44 | } 45 | 46 | public async down(queryRunner: QueryRunner): Promise { 47 | await queryRunner.dropForeignKey( 48 | 'pix_keys', 49 | 'pix_keys_bank_account_id_foreign_key', 50 | ); 51 | await queryRunner.dropTable('pix_keys'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bank-api/src/migrations/1612394636514-CreateTransactionsTable.ts: -------------------------------------------------------------------------------- 1 | import {MigrationInterface, QueryRunner, Table, TableForeignKey} from "typeorm"; 2 | 3 | export class CreateTransactionsTable1612394636514 implements MigrationInterface { 4 | 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.createTable( 7 | new Table({ 8 | name: 'transactions', 9 | columns: [ 10 | { 11 | name: 'id', 12 | type: 'uuid', 13 | isPrimary: true, 14 | }, 15 | { 16 | name: 'external_id', 17 | type: 'uuid', 18 | }, 19 | { 20 | name: 'amount', 21 | type: 'double precision', 22 | }, 23 | { 24 | name: 'description', 25 | type: 'varchar', 26 | isNullable: true 27 | }, 28 | { 29 | name: 'bank_account_id', 30 | type: 'uuid', 31 | }, 32 | { 33 | name: 'bank_account_from_id', 34 | type: 'uuid', 35 | isNullable: true 36 | }, 37 | { 38 | name: 'pix_key_key', 39 | type: 'varchar', 40 | isNullable: true 41 | }, 42 | { 43 | name: 'pix_key_kind', 44 | type: 'varchar', 45 | isNullable: true 46 | }, 47 | { 48 | name: 'status', 49 | type: 'varchar', 50 | }, 51 | { 52 | name: 'operation', 53 | type: 'varchar', 54 | }, 55 | { 56 | name: 'created_at', 57 | type: 'timestamp', 58 | default: 'CURRENT_TIMESTAMP', 59 | }, 60 | ], 61 | }), 62 | ); 63 | 64 | await queryRunner.createForeignKey( 65 | 'transactions', 66 | new TableForeignKey({ 67 | name: 'transactions_bank_account_id_foreign_key', 68 | columnNames: ['bank_account_id'], 69 | referencedColumnNames: ['id'], 70 | referencedTableName: 'bank_accounts', 71 | }), 72 | ); 73 | 74 | } 75 | 76 | public async down(queryRunner: QueryRunner): Promise { 77 | await queryRunner.dropForeignKey( 78 | 'transactions', 79 | 'transactions_bank_account_id_foreign_key', 80 | ); 81 | await queryRunner.dropTable('transactions'); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /bank-api/src/models/bank-account.model.ts: -------------------------------------------------------------------------------- 1 | import {BeforeInsert, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn} from "typeorm"; 2 | import {v4 as uuidv4} from 'uuid'; 3 | 4 | @Entity({ 5 | name: 'bank_accounts' 6 | }) 7 | export class BankAccount { 8 | @PrimaryGeneratedColumn('uuid') 9 | id: string; 10 | 11 | @Column() 12 | account_number: string; 13 | 14 | @Column() 15 | owner_name: string; 16 | 17 | @Column() 18 | balance: number; 19 | 20 | @CreateDateColumn({type: 'timestamp'}) 21 | created_at: Date; 22 | 23 | @BeforeInsert() 24 | generateId(){ 25 | if(this.id){ 26 | return; 27 | } 28 | 29 | this.id = uuidv4(); 30 | } 31 | 32 | @BeforeInsert() 33 | initBalance(){ 34 | if(this.balance){ 35 | return; 36 | } 37 | 38 | this.balance = 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bank-api/src/models/pix-key.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BeforeInsert, 3 | Column, 4 | CreateDateColumn, 5 | Entity, 6 | JoinColumn, 7 | ManyToOne, 8 | PrimaryGeneratedColumn, 9 | } from 'typeorm'; 10 | import { BankAccount } from './bank-account.model'; 11 | import { v4 as uuidv4 } from 'uuid'; 12 | 13 | export enum PixKeyKind { 14 | cpf = 'cpf', 15 | email = 'email', 16 | } 17 | 18 | @Entity({ name: 'pix_keys' }) 19 | export class PixKey { 20 | @PrimaryGeneratedColumn('uuid') 21 | id: string; 22 | 23 | @Column() 24 | kind: PixKeyKind; 25 | 26 | @Column() 27 | key: string; 28 | 29 | @ManyToOne(() => BankAccount) 30 | @JoinColumn({ name: 'bank_account_id' }) 31 | bankAccount: BankAccount; 32 | 33 | @Column() 34 | bank_account_id: string; 35 | 36 | @CreateDateColumn({ type: 'timestamp' }) 37 | created_at: Date; 38 | 39 | @BeforeInsert() generateId() { 40 | if (this.id) { 41 | return; 42 | } 43 | this.id = uuidv4(); 44 | } 45 | } -------------------------------------------------------------------------------- /bank-api/src/models/transaction.model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BeforeInsert, 3 | Column, 4 | CreateDateColumn, 5 | Entity, 6 | JoinColumn, 7 | ManyToOne, 8 | PrimaryGeneratedColumn, 9 | } from 'typeorm'; 10 | import { BankAccount } from './bank-account.model'; 11 | import { PixKeyKind } from './pix-key.model'; 12 | import { v4 as uuidv4 } from 'uuid'; 13 | 14 | export enum TransactionStatus{ 15 | pending = 'pending', 16 | completed = 'completed', 17 | error = 'error' 18 | }; 19 | 20 | export enum TransactionOperation { 21 | debit = 'debit', 22 | credit = 'credit' 23 | } 24 | 25 | @Entity({ name: 'transactions' }) 26 | export class Transaction { 27 | @PrimaryGeneratedColumn("uuid") 28 | id: string; 29 | 30 | @Column() 31 | external_id: string; 32 | 33 | @Column() 34 | amount: number; 35 | 36 | @Column() 37 | description: string; 38 | 39 | @ManyToOne(() => BankAccount) 40 | @JoinColumn({name: "bank_account_id"}) 41 | bankAccount: BankAccount; 42 | 43 | @Column() 44 | bank_account_id: string; 45 | 46 | @ManyToOne(() => BankAccount) 47 | @JoinColumn({name: "bank_account_from_id"}) 48 | bankAccountFrom: BankAccount; 49 | 50 | @Column() 51 | bank_account_from_id: string; 52 | 53 | @Column() 54 | pix_key_key: string; 55 | 56 | @Column() 57 | pix_key_kind: PixKeyKind; 58 | 59 | @Column() 60 | status: TransactionStatus = TransactionStatus.pending; 61 | 62 | @Column() 63 | operation: TransactionOperation 64 | 65 | @CreateDateColumn({ type: 'timestamp' }) 66 | created_at: Date; 67 | 68 | @BeforeInsert() generateId() { 69 | if (this.id) { 70 | return; 71 | } 72 | this.id = uuidv4(); 73 | } 74 | 75 | @BeforeInsert() generateExternalId() { 76 | if (this.external_id) { 77 | return; 78 | } 79 | this.external_id = uuidv4(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /bank-api/src/protofiles/pixkey.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package github.com.codeedu.codepix; 4 | 5 | option go_package = "protofiles;pb"; 6 | 7 | message PixKeyRegistration { 8 | string kind = 1; 9 | string key = 2; 10 | string accountId = 3; 11 | } 12 | 13 | message PixKey { 14 | string kind = 1; 15 | string key = 2; 16 | } 17 | 18 | message Account { 19 | string accountId = 1; 20 | string accountNumber = 2; 21 | string bankId = 3; 22 | string bankName = 4; 23 | string ownerName = 5; 24 | string createdAt = 6; 25 | } 26 | 27 | message PixKeyInfo { 28 | string id = 1; 29 | string kind = 2; 30 | string key = 3; 31 | Account account = 4; 32 | string createdAt = 5; 33 | } 34 | 35 | message PixKeyCreatedResult { 36 | string id = 1; 37 | string status = 2; 38 | string error = 3; 39 | } 40 | 41 | service PixService { 42 | rpc RegisterPixKey (PixKeyRegistration) returns (PixKeyCreatedResult) {}; 43 | rpc Find (PixKey) returns (PixKeyInfo) {}; 44 | } -------------------------------------------------------------------------------- /bank-api/src/subscribers/transaction-subscriber/transaction-subscriber.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { TransactionSubscriber } from './transaction-subscriber.service'; 3 | 4 | describe('TransactionSubscriber', () => { 5 | let service: TransactionSubscriber; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [TransactionSubscriber], 10 | }).compile(); 11 | 12 | service = module.get(TransactionSubscriber); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /bank-api/src/subscribers/transaction-subscriber/transaction-subscriber.service.ts: -------------------------------------------------------------------------------- 1 | import { InjectRepository } from '@nestjs/typeorm'; 2 | import { BankAccount } from 'src/models/bank-account.model'; 3 | import { Transaction } from 'src/models/transaction.model'; 4 | import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent, Repository } from 'typeorm'; 5 | 6 | @EventSubscriber() 7 | export class TransactionSubscriber implements EntitySubscriberInterface{ 8 | 9 | constructor( 10 | connection: Connection, 11 | @InjectRepository(BankAccount) 12 | private bankAccountRepo: Repository 13 | ){ 14 | connection.subscribers.push(this); 15 | } 16 | 17 | listenTo(){ 18 | return Transaction; 19 | } 20 | 21 | async afterInsert(event: InsertEvent){ 22 | const transaction = event.entity; 23 | const bankAccount = await this.bankAccountRepo.findOneOrFail( 24 | transaction.bank_account_id 25 | ); 26 | bankAccount.balance = bankAccount.balance + transaction.amount; 27 | await this.bankAccountRepo.save(bankAccount); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bank-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 | -------------------------------------------------------------------------------- /bank-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 | -------------------------------------------------------------------------------- /bank-api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /bank-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 | "include": ["src"], 16 | "exclude": ["node_modules", "dist", ".docker"] 17 | } 18 | -------------------------------------------------------------------------------- /bank-frontend/.docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # if [ ! -f ".env" ]; then 4 | # cp .env.example .env 5 | # fi 6 | 7 | npm install 8 | 9 | tail -f /dev/null -------------------------------------------------------------------------------- /bank-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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | .history/ 37 | .next-* -------------------------------------------------------------------------------- /bank-frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#000000", 4 | "activityBar.activeBorder": "#782828", 5 | "activityBar.background": "#000000", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#782828", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "statusBar.background": "#000000", 11 | "statusBar.foreground": "#e7e7e7", 12 | "statusBarItem.hoverBackground": "#1a1a1a" 13 | }, 14 | "peacock.remoteColor": "#1a1a1a" 15 | } -------------------------------------------------------------------------------- /bank-frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.15.4-alpine3.12 2 | 3 | RUN apk add --no-cache bash 4 | 5 | USER node 6 | 7 | WORKDIR /home/node/app -------------------------------------------------------------------------------- /bank-frontend/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:14.15.4-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | EXPOSE 3000 14 | 15 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /bank-frontend/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - CodePix 2 | 3 | ## Descrição 4 | 5 | Repositório do front-end feito com Next.js (Front-end dos bancos) 6 | 7 | **Importante**: A aplicação do Apache Kafka, Golang e Nest.js deve estar rodando primeiro. 8 | 9 | ## Configurar /etc/hosts 10 | 11 | A comunicação entre as aplicações se dá de forma direta através da rede da máquina. 12 | Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. 13 | 14 | Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): 15 | ``` 16 | 127.0.0.1 host.docker.internal 17 | ``` 18 | Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. 19 | 20 | ## Rodar a aplicação 21 | 22 | Execute os comandos: 23 | 24 | ### Banco BBX 25 | ``` 26 | ## Levantar banco BBX 27 | docker-compose -f docker-compose_bbx.yaml up -d 28 | 29 | ## Entrar no container do Next.js 30 | docker-compose -f docker-compose_bbx.yaml exec app_bbx bash 31 | 32 | ## Rodar o Next.js 33 | npm run dev 34 | ``` 35 | 36 | Acessar http://localhost:3001 para ver as contas bancárias e poder fazer as operações. 37 | 38 | ### Banco CTER 39 | ``` 40 | ## Levantar banco CTER 41 | docker-compose -f docker-compose_cter.yaml up -d 42 | 43 | ## Entrar no container do Next.js 44 | docker-compose -f docker-compose_cter.yaml exec app_cter bash 45 | 46 | ## Rodar o Next.js 47 | npm run dev 48 | ``` 49 | 50 | Acessar http://localhost:3002 para ver as contas bancárias e poder fazer as operações. 51 | 52 | ### Para Windows 53 | 54 | Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=gCUPP4E8Msc](https://www.youtube.com/watch?v=gCUPP4E8Msc) 55 | 56 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 57 | -------------------------------------------------------------------------------- /bank-frontend/__docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | entrypoint: .docker/entrypoint.sh 8 | container_name: imersao-fullcycle-next-bank 9 | ports: 10 | - 3000:3000 11 | volumes: 12 | - .:/home/node/app 13 | extra_hosts: 14 | - "host.docker.internal:172.17.0.1" -------------------------------------------------------------------------------- /bank-frontend/docker-compose_bbx.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app_bbx: 6 | build: . 7 | entrypoint: .docker/entrypoint.sh 8 | container_name: imersao-fullcycle-next-bank-bbx 9 | ports: 10 | - 3001:3000 11 | environment: 12 | - NEXT_PUBLIC_NEST_API_URL=http://host.docker.internal:8001/api 13 | - NEXT_PUBLIC_BANK_NAME=BBX 14 | - NEXT_PUBLIC_BANK_CODE=001 15 | volumes: 16 | - .:/home/node/app 17 | extra_hosts: 18 | - "host.docker.internal:172.17.0.1" 19 | networks: 20 | - bbx-network 21 | 22 | networks: 23 | bbx-network: 24 | driver: bridge -------------------------------------------------------------------------------- /bank-frontend/docker-compose_cter.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app_cter: 6 | build: . 7 | entrypoint: .docker/entrypoint.sh 8 | container_name: imersao-fullcycle-next-bank-cter 9 | ports: 10 | - 3002:3000 11 | environment: 12 | - NEXT_PUBLIC_NEST_API_URL=http://host.docker.internal:8002/api 13 | - NEXT_PUBLIC_BANK_NAME=CTER 14 | - NEXT_PUBLIC_BANK_CODE=002 15 | volumes: 16 | - .:/home/node/app 17 | extra_hosts: 18 | - "host.docker.internal:172.17.0.1" 19 | networks: 20 | - cter-network 21 | 22 | networks: 23 | cter-network: 24 | driver: bridge -------------------------------------------------------------------------------- /bank-frontend/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /bank-frontend/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | distDir: process.env.NODE_ENV === "development" ? `.next-${process.env.NEXT_PUBLIC_BANK_CODE}` : '.next', 3 | async redirects(){ 4 | return [ 5 | { 6 | source: '/', 7 | destination: '/bank-accounts', 8 | permanent: true 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /bank-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bank-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-free": "^5.15.2", 12 | "axios": "^0.21.1", 13 | "bootstrap": "^4.6.0", 14 | "date-fns": "^2.16.1", 15 | "next": "10.0.6", 16 | "react": "17.0.1", 17 | "react-dom": "17.0.1", 18 | "react-hook-form": "^6.15.1", 19 | "sass": "^1.32.6", 20 | "slug": "^4.0.2", 21 | "sweetalert2": "^10.14.0", 22 | "sweetalert2-react-content": "^3.2.2" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^14.14.25", 26 | "@types/react": "^17.0.1", 27 | "typescript": "^4.1.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bank-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao-fullstack-fullcycle/38735f4d45fff050920aa62c3de47c30308969b1/bank-frontend/public/favicon.ico -------------------------------------------------------------------------------- /bank-frontend/public/img/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao-fullstack-fullcycle/38735f4d45fff050920aa62c3de47c30308969b1/bank-frontend/public/img/back.png -------------------------------------------------------------------------------- /bank-frontend/public/img/icon_banco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao-fullstack-fullcycle/38735f4d45fff050920aa62c3de47c30308969b1/bank-frontend/public/img/icon_banco.png -------------------------------------------------------------------------------- /bank-frontend/public/img/icon_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao-fullstack-fullcycle/38735f4d45fff050920aa62c3de47c30308969b1/bank-frontend/public/img/icon_user.png -------------------------------------------------------------------------------- /bank-frontend/public/img/logo_pix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao-fullstack-fullcycle/38735f4d45fff050920aa62c3de47c30308969b1/bank-frontend/public/img/logo_pix.png -------------------------------------------------------------------------------- /bank-frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /bank-frontend/src/components/BankAccountBalance/BankAccountBalance.module.scss: -------------------------------------------------------------------------------- 1 | @import "styles/sass/variables"; 2 | @import "styles/sass/mixins"; 3 | 4 | .root { 5 | padding: 0 0 0 20px; 6 | border-radius: 7px; 7 | margin-bottom: 15px; 8 | 9 | @include respond-to($_768) { 10 | margin-bottom: 0; 11 | } 12 | 13 | &.bank001 { 14 | background-color: $bank001; 15 | } 16 | 17 | &.bank002 { 18 | background-color: $bank002; 19 | } 20 | 21 | &.bank003 { 22 | background-color: $bank003; 23 | } 24 | 25 | &.bank004 { 26 | background-color: $bank004; 27 | } 28 | 29 | &.bank005 { 30 | background-color: $bank005; 31 | } 32 | 33 | &.bank006 { 34 | background-color: $bank006; 35 | } 36 | 37 | 38 | 39 | h2 { 40 | display: flex; 41 | flex-direction: row; 42 | align-items: center; 43 | justify-content: space-between; 44 | 45 | font-size: 1.2em; 46 | font-weight: 500; 47 | color: $white; 48 | margin: 0; 49 | 50 | span { 51 | margin-left: 15px; 52 | padding-left: 15px; 53 | 54 | font-size: 1.5em; 55 | font-weight: 400; 56 | padding: 15px 20px; 57 | 58 | background-color: rgba($color: #000000, $alpha: 0.2); 59 | border-bottom-right-radius: 7px; 60 | border-top-right-radius: 7px; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /bank-frontend/src/components/BankAccountBalance/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import BankContext from "../../context/BankContext"; 3 | import classes from "./BankAccountBalance.module.scss"; 4 | 5 | interface BankAccountBalanceProps { 6 | balance: number; 7 | } 8 | export const BankAccountBalance: React.FunctionComponent = ( 9 | props 10 | ) => { 11 | const { balance } = props; 12 | const bank = useContext(BankContext); 13 | return ( 14 |
15 |

16 | Saldo em conta Corrente{" "} 17 | R$ {balance.toLocaleString("pt-BR")} 18 |

19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /bank-frontend/src/components/BankAccountCard/BankAccountCard.module.scss: -------------------------------------------------------------------------------- 1 | @import "styles/sass/variables"; 2 | 3 | .root { 4 | border: 1px solid #ddd; 5 | display: flex; 6 | flex-direction: row; 7 | align-items: center; 8 | justify-content: space-between; 9 | background-color: $white; 10 | 11 | padding: 17px 20px; 12 | margin-bottom: 30px; 13 | 14 | &.bank001 { 15 | border-left: 4px solid $bank001; 16 | } 17 | 18 | &.bank002 { 19 | border-left: 4px solid $bank002; 20 | } 21 | 22 | &.bank003 { 23 | border-left: 4px solid $bank003; 24 | } 25 | 26 | &.bank004 { 27 | border-left: 4px solid $bank004; 28 | } 29 | 30 | &.bank005 { 31 | border-left: 4px solid $bank005; 32 | } 33 | 34 | &.bank006 { 35 | border-left: 4px solid $bank006; 36 | } 37 | 38 | .ownerName { 39 | font-size: 1.1em; 40 | font-weight: 400; 41 | color: $color-text; 42 | } 43 | 44 | .accountNumber { 45 | font-size: 0.7em; 46 | font-weight: 600; 47 | 48 | &.bank001 { 49 | color: $bank001; 50 | } 51 | 52 | &.bank002 { 53 | color: $bank002; 54 | } 55 | 56 | &.bank003 { 57 | color: $bank003; 58 | } 59 | 60 | &.bank004 { 61 | color: $bank004; 62 | } 63 | 64 | &.bank005 { 65 | color: $bank005; 66 | } 67 | 68 | &.bank006 { 69 | color: $bank006; 70 | } 71 | } 72 | 73 | .iconRight { 74 | border: 1px solid #ddd; 75 | border-radius: 50%; 76 | padding: 4px 7px; 77 | background-color: #f9f9f9; 78 | font-size: 1.2em; 79 | 80 | &.bank001 { 81 | color: $bank001; 82 | } 83 | 84 | &.bank002 { 85 | color: $bank002; 86 | } 87 | 88 | &.bank003 { 89 | color: $bank003; 90 | } 91 | 92 | &.bank004 { 93 | color: $bank004; 94 | } 95 | 96 | &.bank005 { 97 | color: $bank005; 98 | } 99 | 100 | &.bank006 { 101 | color: $bank006; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /bank-frontend/src/components/BankAccountCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import BankContext from "../../context/BankContext"; 3 | import { BankAccount } from "../../model"; 4 | import classes from "./BankAccountCard.module.scss"; 5 | interface BankAccountCardProps { 6 | bankAccount: BankAccount; 7 | } 8 | const BankAccountCard: React.FunctionComponent = ( 9 | props 10 | ) => { 11 | const { bankAccount } = props; 12 | const bank = useContext(BankContext); 13 | return ( 14 |
15 |
16 |

{bankAccount.owner_name}

17 |

18 | {bankAccount.account_number} 19 |

20 |
21 | 24 |
25 | ); 26 | }; 27 | 28 | export default BankAccountCard; 29 | -------------------------------------------------------------------------------- /bank-frontend/src/components/Button/index.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import BankContext from "../../context/BankContext"; 4 | interface ButtonProps 5 | extends React.DetailedHTMLProps< 6 | React.ButtonHTMLAttributes, 7 | HTMLButtonElement 8 | > { 9 | variant?: "primary" | "info"; 10 | } 11 | 12 | const buttonClasses = { 13 | primary: "btn-primary", 14 | info: "btn-info", 15 | }; 16 | const Button = React.forwardRef((props, ref) => { 17 | const { variant = "primary", ...rest } = props; 18 | const bank = React.useContext(BankContext); 19 | const className = [ 20 | "btn", 21 | buttonClasses[variant], 22 | bank.cssCode, 23 | props.className, 24 | ] 25 | .join(" ") 26 | .trim(); 27 | return 75 | 76 | 79 | 80 | 81 | 82 | 83 | 84 |
85 | Minhas chaves pix 86 | {pixKeys.map((p, key) => ( 87 | 88 | ))} 89 |
90 | 91 | 92 | ); 93 | }; 94 | 95 | export default PixRegister; 96 | 97 | export const getServerSideProps: GetServerSideProps = async (ctx) => { 98 | const { 99 | query: { id }, 100 | } = ctx; 101 | const [{ data: pixKeys }, {data: bankAccount}] = await Promise.all([ 102 | await bankHttp.get(`bank-accounts/${id}/pix-keys`), 103 | await bankHttp.get(`bank-accounts/${id}`), 104 | ]) 105 | 106 | return { 107 | props: { 108 | pixKeys, 109 | bankAccount 110 | }, 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /bank-frontend/src/pages/bank-accounts/[id]/pix/transactions/register.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { useRouter } from "next/dist/client/router"; 3 | import * as React from "react"; 4 | import { useForm } from "react-hook-form"; 5 | import Button from "../../../../../components/Button"; 6 | import Card from "../../../../../components/Card"; 7 | import Input from "../../../../../components/Input"; 8 | import Select from "../../../../../components/Select"; 9 | import FormButtonActions from "../../../../../components/FormButtonActions"; 10 | import Layout from "../../../../../components/Layout"; 11 | import Title from "../../../../../components/Title"; 12 | import { bankHttp } from "../../../../../util/http"; 13 | import Modal from "../../../../../util/modal"; 14 | import { GetServerSideProps, NextPage } from "next"; 15 | import Link from "next/link"; 16 | import { BankAccount } from "../../../../../model"; 17 | interface TransactionRegisterProps { 18 | bankAccount: BankAccount; 19 | } 20 | const TransactionRegister: NextPage = (props) => { 21 | const {bankAccount} = props; 22 | const { register, handleSubmit } = useForm(); 23 | const { 24 | query: { id }, 25 | push, 26 | } = useRouter(); 27 | 28 | async function onSubmit(data) { 29 | try { 30 | await bankHttp.post(`bank-accounts/${id}/transactions`, { 31 | ...data, 32 | amount: new Number(data.amount), 33 | }); 34 | Modal.fire({ 35 | title: "Transação realizada com sucesso", 36 | icon: "success", 37 | }); 38 | push(`/bank-accounts/${id}`); 39 | } catch (e) { 40 | console.error(e); 41 | Modal.fire({ 42 | title: "Ocorreu um erro. Verifique o console", 43 | icon: "error", 44 | }); 45 | } 46 | } 47 | 48 | return ( 49 | 50 | Realizar transferência 51 | 52 |
53 |
54 |
55 | 59 |
60 |
61 | 62 |
63 |
64 | 72 | 73 | 74 | 75 | 76 | 79 | 80 | 81 |
82 |
83 |
84 | ); 85 | }; 86 | 87 | export default TransactionRegister; 88 | 89 | export const getServerSideProps: GetServerSideProps = async (cxt) => { 90 | const { 91 | query: { id }, 92 | } = cxt; 93 | const { data: bankAccount } = await bankHttp.get(`bank-accounts/${id}`); 94 | 95 | return { 96 | props: { 97 | bankAccount, 98 | }, 99 | }; 100 | }; 101 | -------------------------------------------------------------------------------- /bank-frontend/src/pages/bank-accounts/index.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { GetServerSideProps, NextPage } from "next"; 3 | import Link from "next/link"; 4 | import * as React from "react"; 5 | import BankAccountCard from "../../components/BankAccountCard"; 6 | import Layout from "../../components/Layout"; 7 | import Title from "../../components/Title"; 8 | import { BankAccount } from "../../model"; 9 | import { bankHttp } from "../../util/http"; 10 | 11 | interface BankAccountsListProps { 12 | bankAccounts: BankAccount[]; 13 | } 14 | const BankAccountsList: NextPage = (props) => { 15 | const { bankAccounts } = props; 16 | return ( 17 | 18 | Contas bancárias 19 |
20 | {bankAccounts.map((b, key) => ( 21 | 26 | 27 | 28 | 29 | 30 | ))} 31 |
32 |
33 | ); 34 | }; 35 | 36 | export default BankAccountsList; 37 | 38 | export const getServerSideProps: GetServerSideProps = async () => { 39 | const { data: bankAccounts } = await bankHttp.get("bank-accounts"); 40 | return { 41 | props: { 42 | bankAccounts, 43 | }, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /bank-frontend/src/pages/pagina1/index.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | type Props = { 4 | 5 | }; 6 | const Pagina1 = (props: Props) => { 7 | return ( 8 |

9 | Pagina 1 10 |

11 | ); 12 | }; 13 | 14 | export default Pagina1; -------------------------------------------------------------------------------- /bank-frontend/src/pages/pagina1/subpagina.tsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react'; 3 | type Props = { 4 | 5 | }; 6 | const SubPagina = (props: Props) => { 7 | const [count, setCount] = React.useState(0); 8 | return ( 9 |

10 | Sub Página 11 | 12 | {count} 13 |

14 | ); 15 | }; 16 | 17 | export default SubPagina -------------------------------------------------------------------------------- /bank-frontend/src/util/http.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const bankHttp = axios.create({ 4 | baseURL: process.env.NEXT_PUBLIC_NEST_API_URL 5 | }) -------------------------------------------------------------------------------- /bank-frontend/src/util/modal.ts: -------------------------------------------------------------------------------- 1 | import Swal from 'sweetalert2'; 2 | import withReactContent from 'sweetalert2-react-content'; 3 | 4 | const Modal = withReactContent(Swal); 5 | 6 | export default Modal; -------------------------------------------------------------------------------- /bank-frontend/styles/sass/_button.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | font-size: 1.2em; 3 | margin-left: 15px; 4 | padding: 3px 20px; 5 | border-radius: 7px; 6 | 7 | &.btn-primary { 8 | &.bank001 { 9 | background-color: $bank001; 10 | border-color: $bank001; 11 | } 12 | 13 | &.bank002 { 14 | background-color: $bank002; 15 | border-color: $bank002; 16 | } 17 | 18 | &.bank003 { 19 | background-color: $bank003; 20 | border-color: $bank003; 21 | } 22 | 23 | &.bank004 { 24 | background-color: $bank004; 25 | border-color: $bank004; 26 | } 27 | 28 | &.bank005 { 29 | background-color: $bank005; 30 | border-color: $bank005; 31 | } 32 | 33 | &.bank006 { 34 | background-color: $bank006; 35 | border-color: $bank006; 36 | } 37 | } 38 | 39 | &.btn-info { 40 | background-color: #f8f8f8; 41 | border-color: #ddd; 42 | color: $color-text; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bank-frontend/styles/sass/_forms.scss: -------------------------------------------------------------------------------- 1 | .form-control { 2 | border-top: none; 3 | border-left: none; 4 | border-right: none; 5 | border-radius: 0; 6 | } 7 | 8 | label { 9 | font-size: 0.8em; 10 | font-weight: 400; 11 | color: $color-text; 12 | } 13 | -------------------------------------------------------------------------------- /bank-frontend/styles/sass/_mixins.scss: -------------------------------------------------------------------------------- 1 | $_240: 240px; 2 | $_576: 576px; 3 | $_768: 768px; 4 | $_992: 992px; 5 | $_1200: 1200px; 6 | $_1440: 1440px; 7 | $_1680: 1680px; 8 | 9 | @mixin respond-to($media, $type: min) { 10 | @if $type == max { 11 | $media: $media - 1px; 12 | } 13 | 14 | @media only screen and (#{$type}-width: $media) { 15 | @content; 16 | } 17 | } -------------------------------------------------------------------------------- /bank-frontend/styles/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary: #f9f9f9; 2 | $secondary: #dddddd; 3 | 4 | $white: #ffffff; 5 | 6 | $color-text: #707070; 7 | 8 | $bank001: #4DA4F7; 9 | $bank002: #76D57B; 10 | $bank003: #F8D7AF; 11 | $bank004: #9BC452; 12 | $bank005: #EB6351; 13 | $bank006: #F19D82; 14 | -------------------------------------------------------------------------------- /bank-frontend/styles/sass/globals.scss: -------------------------------------------------------------------------------- 1 | @import "~@fortawesome/fontawesome-free/scss/fontawesome.scss"; 2 | @import "~@fortawesome/fontawesome-free/scss/solid.scss"; 3 | @import "~bootstrap/scss/bootstrap"; 4 | 5 | @import "variables"; 6 | @import "forms"; 7 | @import "button"; -------------------------------------------------------------------------------- /bank-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 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /codepix/.env.example: -------------------------------------------------------------------------------- 1 | dbType="postgres" 2 | dsn="dbname=codepix sslmode=disable user=postgres password=root host=db" 3 | 4 | dbTypeTest="sqlite3" 5 | dsnTest=":memory:" 6 | 7 | env="dev" 8 | debug=true 9 | AutoMigrateDb=true 10 | 11 | kafkaBootstrapServers="host.docker.internal:9094" 12 | kafkaConsumerGroupId=codepix 13 | kafkaTransactionTopic="transactions" 14 | kafkaTransactionConfirmationTopic="transaction_confirmation" 15 | -------------------------------------------------------------------------------- /codepix/.gitignore: -------------------------------------------------------------------------------- 1 | .pgdata 2 | .idea 3 | .env 4 | .vscode 5 | -------------------------------------------------------------------------------- /codepix/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.15 2 | 3 | WORKDIR /go/src 4 | ENV PATH="/go/bin:${PATH}" 5 | ENV GO111MODULE=on 6 | ENV CGO_ENABLED=1 7 | 8 | RUN apt-get update && \ 9 | apt-get install build-essential protobuf-compiler librdkafka-dev -y && \ 10 | go get google.golang.org/grpc/cmd/protoc-gen-go-grpc && \ 11 | go get google.golang.org/protobuf/cmd/protoc-gen-go && \ 12 | go get github.com/spf13/cobra@v1.7.0 && \ 13 | wget https://github.com/ktr0731/evans/releases/download/0.9.1/evans_linux_amd64.tar.gz && \ 14 | tar -xzvf evans_linux_amd64.tar.gz && \ 15 | mv evans ../bin && rm -f evans_linux_amd64.tar.gz 16 | 17 | CMD ["tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /codepix/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM golang:1.15 2 | 3 | WORKDIR /go/src 4 | ENV PATH="/go/bin:${PATH}" 5 | ENV GO111MODULE=on 6 | ENV CGO_ENABLED=1 7 | 8 | RUN apt-get update && \ 9 | apt-get install build-essential protobuf-compiler librdkafka-dev -y && \ 10 | go get google.golang.org/grpc/cmd/protoc-gen-go-grpc && \ 11 | go get google.golang.org/protobuf/cmd/protoc-gen-go && \ 12 | go get github.com/spf13/cobra/cobra && \ 13 | wget https://github.com/ktr0731/evans/releases/download/0.9.1/evans_linux_amd64.tar.gz && \ 14 | tar -xzvf evans_linux_amd64.tar.gz && \ 15 | mv evans ../bin && rm -f evans_linux_amd64.tar.gz 16 | 17 | COPY . . 18 | 19 | RUN GOOS=linux go build -ldflags="-s -w" -o codepix 20 | ENTRYPOINT ["./codepix"] -------------------------------------------------------------------------------- /codepix/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /codepix/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack && Full Cycle 2 | 3 | Participe: https://imersao.fullcycle.com.br 4 | 5 | ## Microsserviço CodePix 6 | 7 | Esse microsserviço tem o objetivo de ser um hub de transações entre os bancos que simularemos durante o projeto. 8 | 9 | ## Como executar 10 | 11 | Utilizamos Docker para que todos os serviços que utilizaremos fiquem disponíveis. 12 | 13 | - Faça o clone do projeto 14 | - Tendo o docker instalado em sua máquina apenas execute: 15 | `docker-compose up -d` 16 | 17 | ### Como executar a aplicação 18 | - Acesse o container da aplicação executando: `docker exec -it codepix_app bash` 19 | - Rode `go run cmd/codepix/main.go` 20 | 21 | **Importante:** Esse código está sendo disponibilizado conforme o andamento das aulas, logo, o arquivo para executar o projeto talvez ainda não tenha sido criado. 22 | 23 | ### Serviços utilizados ao executar o docker-compose 24 | 25 | - Aplicação principal 26 | - Postgres 27 | - PgAdmin 28 | - Apache Kafka 29 | - Criador dos tópicos a serem utilizados pelo Kafka 30 | - Confluent control center 31 | - ZooKeeper 32 | 33 | 34 | -------------------------------------------------------------------------------- /codepix/application/factory/factory.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "github.com/codeedu/imersao/codepix-go/application/usecase" 5 | "github.com/codeedu/imersao/codepix-go/infrastructure/repository" 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | func TransactionUseCaseFactory(database *gorm.DB) usecase.TransactionUseCase { 10 | pixRepository := repository.PixKeyRepositoryDb{Db: database} 11 | transactionRepository := repository.TransactionRepositoryDb{Db: database} 12 | 13 | transactionUseCase := usecase.TransactionUseCase{ 14 | TransactionRepository: &transactionRepository, 15 | PixRepository: pixRepository, 16 | } 17 | 18 | return transactionUseCase 19 | } 20 | -------------------------------------------------------------------------------- /codepix/application/grpc/pb/pixkey.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.25.0 4 | // protoc v3.6.1 5 | // source: pixkey.proto 6 | 7 | package pb 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | type PixKeyRegistration struct { 29 | state protoimpl.MessageState 30 | sizeCache protoimpl.SizeCache 31 | unknownFields protoimpl.UnknownFields 32 | 33 | Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` 34 | Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` 35 | AccountId string `protobuf:"bytes,3,opt,name=accountId,proto3" json:"accountId,omitempty"` 36 | } 37 | 38 | func (x *PixKeyRegistration) Reset() { 39 | *x = PixKeyRegistration{} 40 | if protoimpl.UnsafeEnabled { 41 | mi := &file_pixkey_proto_msgTypes[0] 42 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 43 | ms.StoreMessageInfo(mi) 44 | } 45 | } 46 | 47 | func (x *PixKeyRegistration) String() string { 48 | return protoimpl.X.MessageStringOf(x) 49 | } 50 | 51 | func (*PixKeyRegistration) ProtoMessage() {} 52 | 53 | func (x *PixKeyRegistration) ProtoReflect() protoreflect.Message { 54 | mi := &file_pixkey_proto_msgTypes[0] 55 | if protoimpl.UnsafeEnabled && x != nil { 56 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 57 | if ms.LoadMessageInfo() == nil { 58 | ms.StoreMessageInfo(mi) 59 | } 60 | return ms 61 | } 62 | return mi.MessageOf(x) 63 | } 64 | 65 | // Deprecated: Use PixKeyRegistration.ProtoReflect.Descriptor instead. 66 | func (*PixKeyRegistration) Descriptor() ([]byte, []int) { 67 | return file_pixkey_proto_rawDescGZIP(), []int{0} 68 | } 69 | 70 | func (x *PixKeyRegistration) GetKind() string { 71 | if x != nil { 72 | return x.Kind 73 | } 74 | return "" 75 | } 76 | 77 | func (x *PixKeyRegistration) GetKey() string { 78 | if x != nil { 79 | return x.Key 80 | } 81 | return "" 82 | } 83 | 84 | func (x *PixKeyRegistration) GetAccountId() string { 85 | if x != nil { 86 | return x.AccountId 87 | } 88 | return "" 89 | } 90 | 91 | type PixKey struct { 92 | state protoimpl.MessageState 93 | sizeCache protoimpl.SizeCache 94 | unknownFields protoimpl.UnknownFields 95 | 96 | Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` 97 | Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` 98 | } 99 | 100 | func (x *PixKey) Reset() { 101 | *x = PixKey{} 102 | if protoimpl.UnsafeEnabled { 103 | mi := &file_pixkey_proto_msgTypes[1] 104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 105 | ms.StoreMessageInfo(mi) 106 | } 107 | } 108 | 109 | func (x *PixKey) String() string { 110 | return protoimpl.X.MessageStringOf(x) 111 | } 112 | 113 | func (*PixKey) ProtoMessage() {} 114 | 115 | func (x *PixKey) ProtoReflect() protoreflect.Message { 116 | mi := &file_pixkey_proto_msgTypes[1] 117 | if protoimpl.UnsafeEnabled && x != nil { 118 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 119 | if ms.LoadMessageInfo() == nil { 120 | ms.StoreMessageInfo(mi) 121 | } 122 | return ms 123 | } 124 | return mi.MessageOf(x) 125 | } 126 | 127 | // Deprecated: Use PixKey.ProtoReflect.Descriptor instead. 128 | func (*PixKey) Descriptor() ([]byte, []int) { 129 | return file_pixkey_proto_rawDescGZIP(), []int{1} 130 | } 131 | 132 | func (x *PixKey) GetKind() string { 133 | if x != nil { 134 | return x.Kind 135 | } 136 | return "" 137 | } 138 | 139 | func (x *PixKey) GetKey() string { 140 | if x != nil { 141 | return x.Key 142 | } 143 | return "" 144 | } 145 | 146 | type Account struct { 147 | state protoimpl.MessageState 148 | sizeCache protoimpl.SizeCache 149 | unknownFields protoimpl.UnknownFields 150 | 151 | AccountId string `protobuf:"bytes,1,opt,name=accountId,proto3" json:"accountId,omitempty"` 152 | AccountNumber string `protobuf:"bytes,2,opt,name=accountNumber,proto3" json:"accountNumber,omitempty"` 153 | BankId string `protobuf:"bytes,3,opt,name=bankId,proto3" json:"bankId,omitempty"` 154 | BankName string `protobuf:"bytes,4,opt,name=bankName,proto3" json:"bankName,omitempty"` 155 | OwnerName string `protobuf:"bytes,5,opt,name=OwnerName,proto3" json:"OwnerName,omitempty"` 156 | CreatedAt string `protobuf:"bytes,6,opt,name=createdAt,proto3" json:"createdAt,omitempty"` 157 | } 158 | 159 | func (x *Account) Reset() { 160 | *x = Account{} 161 | if protoimpl.UnsafeEnabled { 162 | mi := &file_pixkey_proto_msgTypes[2] 163 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 164 | ms.StoreMessageInfo(mi) 165 | } 166 | } 167 | 168 | func (x *Account) String() string { 169 | return protoimpl.X.MessageStringOf(x) 170 | } 171 | 172 | func (*Account) ProtoMessage() {} 173 | 174 | func (x *Account) ProtoReflect() protoreflect.Message { 175 | mi := &file_pixkey_proto_msgTypes[2] 176 | if protoimpl.UnsafeEnabled && x != nil { 177 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 178 | if ms.LoadMessageInfo() == nil { 179 | ms.StoreMessageInfo(mi) 180 | } 181 | return ms 182 | } 183 | return mi.MessageOf(x) 184 | } 185 | 186 | // Deprecated: Use Account.ProtoReflect.Descriptor instead. 187 | func (*Account) Descriptor() ([]byte, []int) { 188 | return file_pixkey_proto_rawDescGZIP(), []int{2} 189 | } 190 | 191 | func (x *Account) GetAccountId() string { 192 | if x != nil { 193 | return x.AccountId 194 | } 195 | return "" 196 | } 197 | 198 | func (x *Account) GetAccountNumber() string { 199 | if x != nil { 200 | return x.AccountNumber 201 | } 202 | return "" 203 | } 204 | 205 | func (x *Account) GetBankId() string { 206 | if x != nil { 207 | return x.BankId 208 | } 209 | return "" 210 | } 211 | 212 | func (x *Account) GetBankName() string { 213 | if x != nil { 214 | return x.BankName 215 | } 216 | return "" 217 | } 218 | 219 | func (x *Account) GetOwnerName() string { 220 | if x != nil { 221 | return x.OwnerName 222 | } 223 | return "" 224 | } 225 | 226 | func (x *Account) GetCreatedAt() string { 227 | if x != nil { 228 | return x.CreatedAt 229 | } 230 | return "" 231 | } 232 | 233 | type PixKeyInfo struct { 234 | state protoimpl.MessageState 235 | sizeCache protoimpl.SizeCache 236 | unknownFields protoimpl.UnknownFields 237 | 238 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 239 | Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"` 240 | Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` 241 | Account *Account `protobuf:"bytes,4,opt,name=account,proto3" json:"account,omitempty"` 242 | CreatedAt string `protobuf:"bytes,5,opt,name=createdAt,proto3" json:"createdAt,omitempty"` 243 | } 244 | 245 | func (x *PixKeyInfo) Reset() { 246 | *x = PixKeyInfo{} 247 | if protoimpl.UnsafeEnabled { 248 | mi := &file_pixkey_proto_msgTypes[3] 249 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 250 | ms.StoreMessageInfo(mi) 251 | } 252 | } 253 | 254 | func (x *PixKeyInfo) String() string { 255 | return protoimpl.X.MessageStringOf(x) 256 | } 257 | 258 | func (*PixKeyInfo) ProtoMessage() {} 259 | 260 | func (x *PixKeyInfo) ProtoReflect() protoreflect.Message { 261 | mi := &file_pixkey_proto_msgTypes[3] 262 | if protoimpl.UnsafeEnabled && x != nil { 263 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 264 | if ms.LoadMessageInfo() == nil { 265 | ms.StoreMessageInfo(mi) 266 | } 267 | return ms 268 | } 269 | return mi.MessageOf(x) 270 | } 271 | 272 | // Deprecated: Use PixKeyInfo.ProtoReflect.Descriptor instead. 273 | func (*PixKeyInfo) Descriptor() ([]byte, []int) { 274 | return file_pixkey_proto_rawDescGZIP(), []int{3} 275 | } 276 | 277 | func (x *PixKeyInfo) GetId() string { 278 | if x != nil { 279 | return x.Id 280 | } 281 | return "" 282 | } 283 | 284 | func (x *PixKeyInfo) GetKind() string { 285 | if x != nil { 286 | return x.Kind 287 | } 288 | return "" 289 | } 290 | 291 | func (x *PixKeyInfo) GetKey() string { 292 | if x != nil { 293 | return x.Key 294 | } 295 | return "" 296 | } 297 | 298 | func (x *PixKeyInfo) GetAccount() *Account { 299 | if x != nil { 300 | return x.Account 301 | } 302 | return nil 303 | } 304 | 305 | func (x *PixKeyInfo) GetCreatedAt() string { 306 | if x != nil { 307 | return x.CreatedAt 308 | } 309 | return "" 310 | } 311 | 312 | type PixKeyCreatedResult struct { 313 | state protoimpl.MessageState 314 | sizeCache protoimpl.SizeCache 315 | unknownFields protoimpl.UnknownFields 316 | 317 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` 318 | Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` 319 | Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` 320 | } 321 | 322 | func (x *PixKeyCreatedResult) Reset() { 323 | *x = PixKeyCreatedResult{} 324 | if protoimpl.UnsafeEnabled { 325 | mi := &file_pixkey_proto_msgTypes[4] 326 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 327 | ms.StoreMessageInfo(mi) 328 | } 329 | } 330 | 331 | func (x *PixKeyCreatedResult) String() string { 332 | return protoimpl.X.MessageStringOf(x) 333 | } 334 | 335 | func (*PixKeyCreatedResult) ProtoMessage() {} 336 | 337 | func (x *PixKeyCreatedResult) ProtoReflect() protoreflect.Message { 338 | mi := &file_pixkey_proto_msgTypes[4] 339 | if protoimpl.UnsafeEnabled && x != nil { 340 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 341 | if ms.LoadMessageInfo() == nil { 342 | ms.StoreMessageInfo(mi) 343 | } 344 | return ms 345 | } 346 | return mi.MessageOf(x) 347 | } 348 | 349 | // Deprecated: Use PixKeyCreatedResult.ProtoReflect.Descriptor instead. 350 | func (*PixKeyCreatedResult) Descriptor() ([]byte, []int) { 351 | return file_pixkey_proto_rawDescGZIP(), []int{4} 352 | } 353 | 354 | func (x *PixKeyCreatedResult) GetId() string { 355 | if x != nil { 356 | return x.Id 357 | } 358 | return "" 359 | } 360 | 361 | func (x *PixKeyCreatedResult) GetStatus() string { 362 | if x != nil { 363 | return x.Status 364 | } 365 | return "" 366 | } 367 | 368 | func (x *PixKeyCreatedResult) GetError() string { 369 | if x != nil { 370 | return x.Error 371 | } 372 | return "" 373 | } 374 | 375 | var File_pixkey_proto protoreflect.FileDescriptor 376 | 377 | var file_pixkey_proto_rawDesc = []byte{ 378 | 0x0a, 0x0c, 0x70, 0x69, 0x78, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 379 | 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x65, 380 | 0x64, 0x75, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x70, 0x69, 0x78, 0x22, 0x58, 0x0a, 0x12, 0x50, 0x69, 381 | 0x78, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 382 | 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 383 | 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 384 | 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 385 | 0x74, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 386 | 0x6e, 0x74, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x06, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 0x12, 0x12, 387 | 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 388 | 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 389 | 0x03, 0x6b, 0x65, 0x79, 0x22, 0xbd, 0x01, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 390 | 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x01, 0x20, 391 | 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x24, 392 | 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 393 | 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 394 | 0x6d, 0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x6e, 0x6b, 0x49, 0x64, 0x18, 0x03, 395 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x61, 0x6e, 0x6b, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 396 | 0x62, 0x61, 0x6e, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 397 | 0x62, 0x61, 0x6e, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x4f, 0x77, 0x6e, 0x65, 398 | 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4f, 0x77, 0x6e, 399 | 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 400 | 0x64, 0x41, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 401 | 0x65, 0x64, 0x41, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x0a, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 0x49, 402 | 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 403 | 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 404 | 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 405 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3d, 0x0a, 0x07, 0x61, 0x63, 0x63, 406 | 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x69, 0x74, 407 | 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x65, 0x64, 0x75, 0x2e, 408 | 0x63, 0x6f, 0x64, 0x65, 0x70, 0x69, 0x78, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 409 | 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 410 | 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 411 | 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x53, 0x0a, 0x13, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 412 | 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 413 | 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 414 | 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 415 | 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 416 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xd7, 0x01, 0x0a, 0x0a, 417 | 0x50, 0x69, 0x78, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0e, 0x52, 0x65, 418 | 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x2e, 0x67, 419 | 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x65, 0x64, 420 | 0x75, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x70, 0x69, 0x78, 0x2e, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 421 | 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x2f, 0x2e, 0x67, 422 | 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x65, 0x64, 423 | 0x75, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x70, 0x69, 0x78, 0x2e, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 424 | 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 425 | 0x54, 0x0a, 0x04, 0x46, 0x69, 0x6e, 0x64, 0x12, 0x22, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 426 | 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x65, 0x64, 0x75, 0x2e, 0x63, 0x6f, 0x64, 427 | 0x65, 0x70, 0x69, 0x78, 0x2e, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 0x1a, 0x26, 0x2e, 0x67, 0x69, 428 | 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x65, 0x64, 0x75, 429 | 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x70, 0x69, 0x78, 0x2e, 0x50, 0x69, 0x78, 0x4b, 0x65, 0x79, 0x49, 430 | 0x6e, 0x66, 0x6f, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x66, 0x69, 431 | 0x6c, 0x65, 0x73, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 432 | } 433 | 434 | var ( 435 | file_pixkey_proto_rawDescOnce sync.Once 436 | file_pixkey_proto_rawDescData = file_pixkey_proto_rawDesc 437 | ) 438 | 439 | func file_pixkey_proto_rawDescGZIP() []byte { 440 | file_pixkey_proto_rawDescOnce.Do(func() { 441 | file_pixkey_proto_rawDescData = protoimpl.X.CompressGZIP(file_pixkey_proto_rawDescData) 442 | }) 443 | return file_pixkey_proto_rawDescData 444 | } 445 | 446 | var file_pixkey_proto_msgTypes = make([]protoimpl.MessageInfo, 5) 447 | var file_pixkey_proto_goTypes = []interface{}{ 448 | (*PixKeyRegistration)(nil), // 0: github.com.codeedu.codepix.PixKeyRegistration 449 | (*PixKey)(nil), // 1: github.com.codeedu.codepix.PixKey 450 | (*Account)(nil), // 2: github.com.codeedu.codepix.Account 451 | (*PixKeyInfo)(nil), // 3: github.com.codeedu.codepix.PixKeyInfo 452 | (*PixKeyCreatedResult)(nil), // 4: github.com.codeedu.codepix.PixKeyCreatedResult 453 | } 454 | var file_pixkey_proto_depIdxs = []int32{ 455 | 2, // 0: github.com.codeedu.codepix.PixKeyInfo.account:type_name -> github.com.codeedu.codepix.Account 456 | 0, // 1: github.com.codeedu.codepix.PixService.RegisterPixKey:input_type -> github.com.codeedu.codepix.PixKeyRegistration 457 | 1, // 2: github.com.codeedu.codepix.PixService.Find:input_type -> github.com.codeedu.codepix.PixKey 458 | 4, // 3: github.com.codeedu.codepix.PixService.RegisterPixKey:output_type -> github.com.codeedu.codepix.PixKeyCreatedResult 459 | 3, // 4: github.com.codeedu.codepix.PixService.Find:output_type -> github.com.codeedu.codepix.PixKeyInfo 460 | 3, // [3:5] is the sub-list for method output_type 461 | 1, // [1:3] is the sub-list for method input_type 462 | 1, // [1:1] is the sub-list for extension type_name 463 | 1, // [1:1] is the sub-list for extension extendee 464 | 0, // [0:1] is the sub-list for field type_name 465 | } 466 | 467 | func init() { file_pixkey_proto_init() } 468 | func file_pixkey_proto_init() { 469 | if File_pixkey_proto != nil { 470 | return 471 | } 472 | if !protoimpl.UnsafeEnabled { 473 | file_pixkey_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 474 | switch v := v.(*PixKeyRegistration); i { 475 | case 0: 476 | return &v.state 477 | case 1: 478 | return &v.sizeCache 479 | case 2: 480 | return &v.unknownFields 481 | default: 482 | return nil 483 | } 484 | } 485 | file_pixkey_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 486 | switch v := v.(*PixKey); i { 487 | case 0: 488 | return &v.state 489 | case 1: 490 | return &v.sizeCache 491 | case 2: 492 | return &v.unknownFields 493 | default: 494 | return nil 495 | } 496 | } 497 | file_pixkey_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 498 | switch v := v.(*Account); i { 499 | case 0: 500 | return &v.state 501 | case 1: 502 | return &v.sizeCache 503 | case 2: 504 | return &v.unknownFields 505 | default: 506 | return nil 507 | } 508 | } 509 | file_pixkey_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 510 | switch v := v.(*PixKeyInfo); i { 511 | case 0: 512 | return &v.state 513 | case 1: 514 | return &v.sizeCache 515 | case 2: 516 | return &v.unknownFields 517 | default: 518 | return nil 519 | } 520 | } 521 | file_pixkey_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 522 | switch v := v.(*PixKeyCreatedResult); i { 523 | case 0: 524 | return &v.state 525 | case 1: 526 | return &v.sizeCache 527 | case 2: 528 | return &v.unknownFields 529 | default: 530 | return nil 531 | } 532 | } 533 | } 534 | type x struct{} 535 | out := protoimpl.TypeBuilder{ 536 | File: protoimpl.DescBuilder{ 537 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 538 | RawDescriptor: file_pixkey_proto_rawDesc, 539 | NumEnums: 0, 540 | NumMessages: 5, 541 | NumExtensions: 0, 542 | NumServices: 1, 543 | }, 544 | GoTypes: file_pixkey_proto_goTypes, 545 | DependencyIndexes: file_pixkey_proto_depIdxs, 546 | MessageInfos: file_pixkey_proto_msgTypes, 547 | }.Build() 548 | File_pixkey_proto = out.File 549 | file_pixkey_proto_rawDesc = nil 550 | file_pixkey_proto_goTypes = nil 551 | file_pixkey_proto_depIdxs = nil 552 | } 553 | -------------------------------------------------------------------------------- /codepix/application/grpc/pb/pixkey_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package pb 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // PixServiceClient is the client API for PixService service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type PixServiceClient interface { 21 | RegisterPixKey(ctx context.Context, in *PixKeyRegistration, opts ...grpc.CallOption) (*PixKeyCreatedResult, error) 22 | Find(ctx context.Context, in *PixKey, opts ...grpc.CallOption) (*PixKeyInfo, error) 23 | } 24 | 25 | type pixServiceClient struct { 26 | cc grpc.ClientConnInterface 27 | } 28 | 29 | func NewPixServiceClient(cc grpc.ClientConnInterface) PixServiceClient { 30 | return &pixServiceClient{cc} 31 | } 32 | 33 | func (c *pixServiceClient) RegisterPixKey(ctx context.Context, in *PixKeyRegistration, opts ...grpc.CallOption) (*PixKeyCreatedResult, error) { 34 | out := new(PixKeyCreatedResult) 35 | err := c.cc.Invoke(ctx, "/github.com.codeedu.codepix.PixService/RegisterPixKey", in, out, opts...) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return out, nil 40 | } 41 | 42 | func (c *pixServiceClient) Find(ctx context.Context, in *PixKey, opts ...grpc.CallOption) (*PixKeyInfo, error) { 43 | out := new(PixKeyInfo) 44 | err := c.cc.Invoke(ctx, "/github.com.codeedu.codepix.PixService/Find", in, out, opts...) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return out, nil 49 | } 50 | 51 | // PixServiceServer is the server API for PixService service. 52 | // All implementations must embed UnimplementedPixServiceServer 53 | // for forward compatibility 54 | type PixServiceServer interface { 55 | RegisterPixKey(context.Context, *PixKeyRegistration) (*PixKeyCreatedResult, error) 56 | Find(context.Context, *PixKey) (*PixKeyInfo, error) 57 | mustEmbedUnimplementedPixServiceServer() 58 | } 59 | 60 | // UnimplementedPixServiceServer must be embedded to have forward compatible implementations. 61 | type UnimplementedPixServiceServer struct { 62 | } 63 | 64 | func (UnimplementedPixServiceServer) RegisterPixKey(context.Context, *PixKeyRegistration) (*PixKeyCreatedResult, error) { 65 | return nil, status.Errorf(codes.Unimplemented, "method RegisterPixKey not implemented") 66 | } 67 | func (UnimplementedPixServiceServer) Find(context.Context, *PixKey) (*PixKeyInfo, error) { 68 | return nil, status.Errorf(codes.Unimplemented, "method Find not implemented") 69 | } 70 | func (UnimplementedPixServiceServer) mustEmbedUnimplementedPixServiceServer() {} 71 | 72 | // UnsafePixServiceServer may be embedded to opt out of forward compatibility for this service. 73 | // Use of this interface is not recommended, as added methods to PixServiceServer will 74 | // result in compilation errors. 75 | type UnsafePixServiceServer interface { 76 | mustEmbedUnimplementedPixServiceServer() 77 | } 78 | 79 | func RegisterPixServiceServer(s grpc.ServiceRegistrar, srv PixServiceServer) { 80 | s.RegisterService(&PixService_ServiceDesc, srv) 81 | } 82 | 83 | func _PixService_RegisterPixKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 84 | in := new(PixKeyRegistration) 85 | if err := dec(in); err != nil { 86 | return nil, err 87 | } 88 | if interceptor == nil { 89 | return srv.(PixServiceServer).RegisterPixKey(ctx, in) 90 | } 91 | info := &grpc.UnaryServerInfo{ 92 | Server: srv, 93 | FullMethod: "/github.com.codeedu.codepix.PixService/RegisterPixKey", 94 | } 95 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 96 | return srv.(PixServiceServer).RegisterPixKey(ctx, req.(*PixKeyRegistration)) 97 | } 98 | return interceptor(ctx, in, info, handler) 99 | } 100 | 101 | func _PixService_Find_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 102 | in := new(PixKey) 103 | if err := dec(in); err != nil { 104 | return nil, err 105 | } 106 | if interceptor == nil { 107 | return srv.(PixServiceServer).Find(ctx, in) 108 | } 109 | info := &grpc.UnaryServerInfo{ 110 | Server: srv, 111 | FullMethod: "/github.com.codeedu.codepix.PixService/Find", 112 | } 113 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 114 | return srv.(PixServiceServer).Find(ctx, req.(*PixKey)) 115 | } 116 | return interceptor(ctx, in, info, handler) 117 | } 118 | 119 | // PixService_ServiceDesc is the grpc.ServiceDesc for PixService service. 120 | // It's only intended for direct use with grpc.RegisterService, 121 | // and not to be introspected or modified (even as a copy) 122 | var PixService_ServiceDesc = grpc.ServiceDesc{ 123 | ServiceName: "github.com.codeedu.codepix.PixService", 124 | HandlerType: (*PixServiceServer)(nil), 125 | Methods: []grpc.MethodDesc{ 126 | { 127 | MethodName: "RegisterPixKey", 128 | Handler: _PixService_RegisterPixKey_Handler, 129 | }, 130 | { 131 | MethodName: "Find", 132 | Handler: _PixService_Find_Handler, 133 | }, 134 | }, 135 | Streams: []grpc.StreamDesc{}, 136 | Metadata: "pixkey.proto", 137 | } 138 | -------------------------------------------------------------------------------- /codepix/application/grpc/pix.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "github.com/codeedu/imersao/codepix-go/application/grpc/pb" 6 | "github.com/codeedu/imersao/codepix-go/application/usecase" 7 | ) 8 | 9 | type PixGrpcService struct { 10 | PixUseCase usecase.PixUseCase 11 | pb.UnimplementedPixServiceServer 12 | } 13 | 14 | func (p *PixGrpcService) RegisterPixKey(ctx context.Context, in *pb.PixKeyRegistration) (*pb.PixKeyCreatedResult, error) { 15 | key, err := p.PixUseCase.RegisterKey(in.Key, in.Kind, in.AccountId) 16 | if err != nil { 17 | return &pb.PixKeyCreatedResult{ 18 | Status: "not created", 19 | Error: err.Error(), 20 | }, err 21 | } 22 | 23 | return &pb.PixKeyCreatedResult{ 24 | Id: key.ID, 25 | Status: "created", 26 | }, nil 27 | } 28 | 29 | func (p *PixGrpcService) Find(ctx context.Context, in *pb.PixKey) (*pb.PixKeyInfo, error) { 30 | pixKey, err := p.PixUseCase.FindKey(in.Key, in.Kind) 31 | if err != nil { 32 | return &pb.PixKeyInfo{}, err 33 | } 34 | 35 | return &pb.PixKeyInfo{ 36 | Id: pixKey.ID, 37 | Kind: pixKey.Kind, 38 | Key: pixKey.Key, 39 | Account: &pb.Account{ 40 | AccountId: pixKey.AccountID, 41 | AccountNumber: pixKey.Account.Number, 42 | BankId: pixKey.Account.BankID, 43 | BankName: pixKey.Account.Bank.Name, 44 | OwnerName: pixKey.Account.OwnerName, 45 | CreatedAt: pixKey.Account.CreatedAt.String(), 46 | }, 47 | CreatedAt: pixKey.CreatedAt.String(), 48 | }, nil 49 | } 50 | 51 | func NewPixGrpcService(usecase usecase.PixUseCase) *PixGrpcService { 52 | return &PixGrpcService{ 53 | PixUseCase: usecase, 54 | } 55 | } -------------------------------------------------------------------------------- /codepix/application/grpc/protofiles/pixkey.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package github.com.codeedu.codepix; 4 | 5 | option go_package = "protofiles;pb"; 6 | 7 | message PixKeyRegistration { 8 | string kind = 1; 9 | string key = 2; 10 | string accountId = 3; 11 | } 12 | 13 | message PixKey { 14 | string kind = 1; 15 | string key = 2; 16 | } 17 | 18 | message Account { 19 | string accountId = 1; 20 | string accountNumber = 2; 21 | string bankId = 3; 22 | string bankName = 4; 23 | string OwnerName = 5; 24 | string createdAt = 6; 25 | } 26 | 27 | message PixKeyInfo { 28 | string id = 1; 29 | string kind = 2; 30 | string key = 3; 31 | Account account = 4; 32 | string createdAt = 5; 33 | } 34 | 35 | message PixKeyCreatedResult { 36 | string id = 1; 37 | string status = 2; 38 | string error = 3; 39 | } 40 | 41 | service PixService { 42 | rpc RegisterPixKey (PixKeyRegistration) returns (PixKeyCreatedResult) {}; 43 | rpc Find(PixKey) returns (PixKeyInfo) {}; 44 | } 45 | -------------------------------------------------------------------------------- /codepix/application/grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/codeedu/imersao/codepix-go/application/grpc/pb" 6 | "github.com/codeedu/imersao/codepix-go/application/usecase" 7 | "github.com/codeedu/imersao/codepix-go/infrastructure/repository" 8 | "github.com/jinzhu/gorm" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/reflection" 11 | "log" 12 | "net" 13 | ) 14 | 15 | func StartGrpcServer(database *gorm.DB, port int) { 16 | grpcServer := grpc.NewServer() 17 | reflection.Register(grpcServer) 18 | 19 | pixRepository := repository.PixKeyRepositoryDb{Db: database} 20 | pixUseCase := usecase.PixUseCase{PixKeyRepository: pixRepository} 21 | pixGrpcService := NewPixGrpcService(pixUseCase) 22 | pb.RegisterPixServiceServer(grpcServer, pixGrpcService) 23 | 24 | address := fmt.Sprintf("0.0.0.0:%d", port) 25 | listener, err := net.Listen("tcp", address) 26 | if err != nil { 27 | log.Fatal("cannot start grpc server", err) 28 | } 29 | 30 | log.Printf("gRPC server has been started on port %d", port) 31 | err = grpcServer.Serve(listener) 32 | if err != nil { 33 | log.Fatal("cannot start grpc server", err) 34 | } 35 | } -------------------------------------------------------------------------------- /codepix/application/kafka/process.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "fmt" 5 | "github.com/codeedu/imersao/codepix-go/application/factory" 6 | appmodel "github.com/codeedu/imersao/codepix-go/application/model" 7 | "github.com/codeedu/imersao/codepix-go/application/usecase" 8 | "github.com/codeedu/imersao/codepix-go/domain/model" 9 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 10 | "github.com/jinzhu/gorm" 11 | "os" 12 | ) 13 | 14 | type KafkaProcessor struct { 15 | Database *gorm.DB 16 | Producer *ckafka.Producer 17 | DeliveryChan chan ckafka.Event 18 | } 19 | 20 | func NewKafkaProcessor(database *gorm.DB, producer *ckafka.Producer, deliveryChan chan ckafka.Event) *KafkaProcessor { 21 | return &KafkaProcessor{ 22 | Database: database, 23 | Producer: producer, 24 | DeliveryChan: deliveryChan, 25 | } 26 | } 27 | 28 | func (k *KafkaProcessor) Consume() { 29 | configMap := &ckafka.ConfigMap{ 30 | "bootstrap.servers": os.Getenv("kafkaBootstrapServers"), 31 | "group.id": os.Getenv("kafkaConsumerGroupId"), 32 | "auto.offset.reset": "earliest", 33 | } 34 | c, err := ckafka.NewConsumer(configMap) 35 | 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | topics := []string{os.Getenv("kafkaTransactionTopic"), os.Getenv("kafkaTransactionConfirmationTopic")} 41 | c.SubscribeTopics(topics, nil) 42 | 43 | fmt.Println("kafka consumer has been started") 44 | for { 45 | msg, err := c.ReadMessage(-1) 46 | if err == nil { 47 | fmt.Println(string(msg.Value)) 48 | k.processMessage(msg) 49 | } 50 | } 51 | } 52 | 53 | func (k *KafkaProcessor) processMessage(msg *ckafka.Message) { 54 | transactionsTopic := "transactions" 55 | transactionConfirmationTopic := "transaction_confirmation" 56 | 57 | switch topic := *msg.TopicPartition.Topic; topic { 58 | case transactionsTopic: 59 | k.processTransaction(msg) 60 | case transactionConfirmationTopic: 61 | k.processTransactionConfirmation(msg) 62 | default: 63 | fmt.Println("not a valid topic", string(msg.Value)) 64 | } 65 | } 66 | 67 | func (k *KafkaProcessor) processTransaction(msg *ckafka.Message) error { 68 | transaction := appmodel.NewTransaction() 69 | err := transaction.ParseJson(msg.Value) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | transactionUseCase := factory.TransactionUseCaseFactory(k.Database) 75 | 76 | createdTransaction, err := transactionUseCase.Register( 77 | transaction.AccountID, 78 | transaction.Amount, 79 | transaction.PixKeyTo, 80 | transaction.PixKeyKindTo, 81 | transaction.Description, 82 | transaction.ID, 83 | ) 84 | if err != nil { 85 | fmt.Println("error registering transaction", err) 86 | return err 87 | } 88 | 89 | topic := "bank" + createdTransaction.PixKeyTo.Account.Bank.Code 90 | transaction.ID = createdTransaction.ID 91 | transaction.Status = model.TransactionPending 92 | transactionJson, err := transaction.ToJson() 93 | 94 | if err != nil { 95 | return err 96 | } 97 | 98 | err = Publish(string(transactionJson), topic, k.Producer, k.DeliveryChan) 99 | if err != nil { 100 | return err 101 | } 102 | return nil 103 | } 104 | 105 | func (k *KafkaProcessor) processTransactionConfirmation(msg *ckafka.Message) error { 106 | transaction := appmodel.NewTransaction() 107 | err := transaction.ParseJson(msg.Value) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | transactionUseCase := factory.TransactionUseCaseFactory(k.Database) 113 | 114 | if transaction.Status == model.TransactionConfirmed { 115 | err = k.confirmTransaction(transaction, transactionUseCase) 116 | if err != nil { 117 | return err 118 | } 119 | } else if transaction.Status == model.TransactionCompleted { 120 | _, err := transactionUseCase.Complete(transaction.ID) 121 | if err != nil { 122 | return err 123 | } 124 | return nil 125 | } 126 | return nil 127 | } 128 | 129 | func (k *KafkaProcessor) confirmTransaction(transaction *appmodel.Transaction, transactionUseCase usecase.TransactionUseCase) error { 130 | confirmedTransaction, err := transactionUseCase.Confirm(transaction.ID) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | topic := "bank" + confirmedTransaction.AccountFrom.Bank.Code 136 | transactionJson, err := transaction.ToJson() 137 | if err != nil { 138 | return err 139 | } 140 | 141 | err = Publish(string(transactionJson), topic, k.Producer, k.DeliveryChan) 142 | if err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | -------------------------------------------------------------------------------- /codepix/application/kafka/producer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "fmt" 5 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 6 | "os" 7 | ) 8 | 9 | func NewKafkaProducer() *ckafka.Producer { 10 | configMap := &ckafka.ConfigMap{ 11 | "bootstrap.servers": os.Getenv("kafkaBootstrapServers"), 12 | } 13 | p, err := ckafka.NewProducer(configMap) 14 | if err != nil { 15 | panic(err) 16 | } 17 | return p 18 | } 19 | 20 | func Publish(msg string, topic string, producer *ckafka.Producer, deliveryChan chan ckafka.Event) error { 21 | message := &ckafka.Message{ 22 | TopicPartition: ckafka.TopicPartition{Topic: &topic, Partition: ckafka.PartitionAny}, 23 | Value: []byte(msg), 24 | } 25 | err := producer.Produce(message, deliveryChan) 26 | if err != nil { 27 | return err 28 | } 29 | return nil 30 | } 31 | 32 | func DeliveryReport(deliveryChan chan ckafka.Event) { 33 | for e := range deliveryChan { 34 | switch ev := e.(type) { 35 | case *ckafka.Message: 36 | if ev.TopicPartition.Error != nil { 37 | fmt.Println("Delivery failed:", ev.TopicPartition) 38 | } else { 39 | fmt.Println("Delivered message to:", ev.TopicPartition) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /codepix/application/model/transaction.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | type Transaction struct { 10 | ID string `json:"id" validate:"required,uuid4"` 11 | AccountID string `json:"accountId" validate:"required,uuid4"` 12 | Amount float64 `json:"amount" validate:"required,numeric"` 13 | PixKeyTo string `json:"pixKeyTo" validate:"required"` 14 | PixKeyKindTo string `json:"pixKeyKindTo" validate:"required"` 15 | Description string `json:"description" validate:"required"` 16 | Status string `json:"status" validate:"-"` 17 | Error string `json:"error"` 18 | } 19 | 20 | func (t *Transaction) isValid() error { 21 | v := validator.New() 22 | err := v.Struct(t) 23 | if err != nil { 24 | fmt.Errorf("Error during Transaction validation: %s", err.Error()) 25 | return err 26 | } 27 | return nil 28 | } 29 | 30 | func (t *Transaction) ParseJson(data []byte) error { 31 | err := json.Unmarshal(data, t) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | err = t.isValid() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (t *Transaction) ToJson() ([]byte, error) { 45 | err := t.isValid() 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | result, err := json.Marshal(t) 51 | if err != nil { 52 | return nil, nil 53 | } 54 | 55 | return result, nil 56 | } 57 | 58 | func NewTransaction() *Transaction { 59 | return &Transaction{} 60 | } 61 | -------------------------------------------------------------------------------- /codepix/application/usecase/pix.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "github.com/codeedu/imersao/codepix-go/domain/model" 5 | ) 6 | 7 | type PixUseCase struct { 8 | PixKeyRepository model.PixKeyRepositoryInterface 9 | } 10 | 11 | func (p *PixUseCase) RegisterKey(key string, kind string, accountId string) (*model.PixKey, error) { 12 | account, err := p.PixKeyRepository.FindAccount(accountId) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | pixKey, err := model.NewPixKey(kind, account, key) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | p.PixKeyRepository.RegisterKey(pixKey) 23 | if pixKey.ID == "" { 24 | return nil, err 25 | } 26 | 27 | return pixKey, nil 28 | } 29 | 30 | func (p *PixUseCase) FindKey(key string, kind string) (*model.PixKey, error) { 31 | pixKey, err := p.PixKeyRepository.FindKeyByKind(key, kind) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return pixKey, nil 36 | } -------------------------------------------------------------------------------- /codepix/application/usecase/transaction.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "errors" 5 | "github.com/codeedu/imersao/codepix-go/domain/model" 6 | "log" 7 | ) 8 | 9 | type TransactionUseCase struct { 10 | TransactionRepository model.TransactionRepositoryInterface 11 | PixRepository model.PixKeyRepositoryInterface 12 | } 13 | 14 | func (t *TransactionUseCase) Register(accountId string, amount float64, pixKeyto string, pixKeyKindTo string, description string, id string) (*model.Transaction, error) { 15 | 16 | account, err := t.PixRepository.FindAccount(accountId) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | pixKey, err := t.PixRepository.FindKeyByKind(pixKeyto, pixKeyKindTo) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | transaction, err := model.NewTransaction(account, amount, pixKey, description, id) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | t.TransactionRepository.Save(transaction) 32 | if transaction.Base.ID != "" { 33 | return transaction, nil 34 | } 35 | 36 | return nil, errors.New("unable to process this transaction") 37 | 38 | } 39 | 40 | func (t *TransactionUseCase) Confirm(transactionId string) (*model.Transaction, error) { 41 | transaction, err := t.TransactionRepository.Find(transactionId) 42 | if err != nil { 43 | log.Println("Transaction not found", transactionId) 44 | return nil, err 45 | } 46 | 47 | transaction.Status = model.TransactionConfirmed 48 | err = t.TransactionRepository.Save(transaction) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | return transaction, nil 54 | } 55 | 56 | func (t *TransactionUseCase) Complete(transactionId string) (*model.Transaction, error) { 57 | transaction, err := t.TransactionRepository.Find(transactionId) 58 | if err != nil { 59 | log.Println("Transaction not found", transactionId) 60 | return nil, err 61 | } 62 | 63 | transaction.Status = model.TransactionCompleted 64 | err = t.TransactionRepository.Save(transaction) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return transaction, nil 70 | } 71 | 72 | func (t *TransactionUseCase) Error(transactionId string, reason string) (*model.Transaction, error) { 73 | transaction, err := t.TransactionRepository.Find(transactionId) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | transaction.Status = model.TransactionError 79 | transaction.CancelDescription = reason 80 | 81 | err = t.TransactionRepository.Save(transaction) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return transaction, nil 87 | 88 | } 89 | -------------------------------------------------------------------------------- /codepix/cmd/all.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/codeedu/imersao/codepix-go/application/grpc" 20 | "github.com/codeedu/imersao/codepix-go/application/kafka" 21 | "github.com/codeedu/imersao/codepix-go/infrastructure/db" 22 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 23 | "github.com/spf13/cobra" 24 | "os" 25 | ) 26 | 27 | var ( 28 | gRPCPortNumber int 29 | ) 30 | 31 | // allCmd represents the all command 32 | var allCmd = &cobra.Command{ 33 | Use: "all", 34 | Short: "Run gRPC and a Kafka Consumer", 35 | 36 | Run: func(cmd *cobra.Command, args []string) { 37 | database := db.ConnectDB(os.Getenv("env")) 38 | go grpc.StartGrpcServer(database, portNumber) 39 | 40 | deliveryChan := make(chan ckafka.Event) 41 | producer := kafka.NewKafkaProducer() 42 | go kafka.DeliveryReport(deliveryChan) 43 | kafkaProcessor := kafka.NewKafkaProcessor(database, producer, deliveryChan) 44 | kafkaProcessor.Consume() 45 | }, 46 | } 47 | 48 | func init() { 49 | rootCmd.AddCommand(allCmd) 50 | allCmd.Flags().IntVarP(&gRPCPortNumber, "grpc-port","p", 500051,"gRPC Port") 51 | // Here you will define your flags and configuration settings. 52 | 53 | // Cobra supports Persistent Flags which will work for this command 54 | // and all subcommands, e.g.: 55 | // allCmd.PersistentFlags().String("foo", "", "A help for foo") 56 | 57 | // Cobra supports local flags which will only run when this command 58 | // is called directly, e.g.: 59 | // allCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 60 | } 61 | -------------------------------------------------------------------------------- /codepix/cmd/fixtures.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/codeedu/imersao/codepix-go/domain/model" 20 | "github.com/codeedu/imersao/codepix-go/infrastructure/db" 21 | "github.com/codeedu/imersao/codepix-go/infrastructure/repository" 22 | "github.com/spf13/cobra" 23 | "os" 24 | ) 25 | 26 | // fixturesCmd represents the fixtures command 27 | var fixturesCmd = &cobra.Command{ 28 | Use: "fixtures", 29 | Short: "Run fixtures for fake data generation", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | database := db.ConnectDB(os.Getenv("env")) 32 | defer database.Close() 33 | pixRepository := repository.PixKeyRepositoryDb{Db: database} 34 | 35 | bankBBX, _ := model.NewBank("001", "BBX") 36 | bankCTER, _ := model.NewBank("002", "CTER") 37 | pixRepository.AddBank(bankBBX) 38 | pixRepository.AddBank(bankCTER) 39 | 40 | account1, _ := model.NewAccount(bankBBX, "1111", "User BBX 1") 41 | account1.ID = "6e4635ce-88d1-4e58-9597-d13fc446ee47" 42 | pixRepository.AddAccount(account1) 43 | 44 | account2, _ := model.NewAccount(bankBBX, "2222", "User BBX 2") 45 | account2.ID = "51a720b2-5144-4d7f-921d-57023b1e24c1" 46 | pixRepository.AddAccount(account2) 47 | 48 | account3, _ := model.NewAccount(bankCTER, "3333", "User CTER 1") 49 | account3.ID = "103cc632-78e7-4476-ab63-d5ad3a562d26" 50 | pixRepository.AddAccount(account3) 51 | 52 | account4, _ := model.NewAccount(bankCTER, "4444", "User CTER 2") 53 | account4.ID = "463b1b2a-b5fa-4b88-9c31-e5c894a20ae3" 54 | pixRepository.AddAccount(account4) 55 | }, 56 | } 57 | 58 | func init() { 59 | rootCmd.AddCommand(fixturesCmd) 60 | } 61 | -------------------------------------------------------------------------------- /codepix/cmd/grpc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/codeedu/imersao/codepix-go/application/grpc" 20 | "github.com/codeedu/imersao/codepix-go/infrastructure/db" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var portNumber int 27 | 28 | // grpcCmd represents the grpc command 29 | var grpcCmd = &cobra.Command{ 30 | Use: "grpc", 31 | Short: "Start gRPC server", 32 | Run: func(cmd *cobra.Command, args []string) { 33 | database := db.ConnectDB(os.Getenv("env")) 34 | grpc.StartGrpcServer(database, portNumber) 35 | }, 36 | } 37 | 38 | func init() { 39 | rootCmd.AddCommand(grpcCmd) 40 | grpcCmd.Flags().IntVarP(&portNumber, "port", "p",50051, "gRPC Server port") 41 | 42 | // Here you will define your flags and configuration settings. 43 | 44 | // Cobra supports Persistent Flags which will work for this command 45 | // and all subcommands, e.g.: 46 | // grpcCmd.PersistentFlags().String("foo", "", "A help for foo") 47 | 48 | // Cobra supports local flags which will only run when this command 49 | // is called directly, e.g.: 50 | // grpcCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 51 | } 52 | -------------------------------------------------------------------------------- /codepix/cmd/kafka.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/codeedu/imersao/codepix-go/application/kafka" 20 | "github.com/codeedu/imersao/codepix-go/infrastructure/db" 21 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 22 | "github.com/spf13/cobra" 23 | "os" 24 | ) 25 | 26 | // kafkaCmd represents the kafka command 27 | var kafkaCmd = &cobra.Command{ 28 | Use: "kafka", 29 | Short: "Start consuming transactions using Apache Kafka", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | deliveryChan := make(chan ckafka.Event) 32 | database := db.ConnectDB(os.Getenv("env")) 33 | producer := kafka.NewKafkaProducer() 34 | 35 | //kafka.Publish("Ola Cosumer", "teste", producer, deliveryChan) 36 | go kafka.DeliveryReport(deliveryChan) 37 | 38 | kafkaProcessor := kafka.NewKafkaProcessor(database, producer, deliveryChan) 39 | kafkaProcessor.Consume() 40 | }, 41 | } 42 | 43 | func init() { 44 | rootCmd.AddCommand(kafkaCmd) 45 | 46 | // Here you will define your flags and configuration settings. 47 | 48 | // Cobra supports Persistent Flags which will work for this command 49 | // and all subcommands, e.g.: 50 | // kafkaCmd.PersistentFlags().String("foo", "", "A help for foo") 51 | 52 | // Cobra supports local flags which will only run when this command 53 | // is called directly, e.g.: 54 | // kafkaCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 55 | } 56 | -------------------------------------------------------------------------------- /codepix/cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | "github.com/spf13/cobra" 21 | "os" 22 | 23 | homedir "github.com/mitchellh/go-homedir" 24 | "github.com/spf13/viper" 25 | ) 26 | 27 | var cfgFile string 28 | 29 | // rootCmd represents the base command when called without any subcommands 30 | var rootCmd = &cobra.Command{ 31 | Use: "codepix", 32 | Short: "Use codepix software to intermediate bank transaction with apache kafka and grpc", 33 | // Uncomment the following line if your bare application 34 | // has an action associated with it: 35 | // Run: func(cmd *cobra.Command, args []string) { }, 36 | } 37 | 38 | // Execute adds all child commands to the root command and sets flags appropriately. 39 | // This is called by main.main(). It only needs to happen once to the rootCmd. 40 | func Execute() { 41 | if err := rootCmd.Execute(); err != nil { 42 | fmt.Println(err) 43 | os.Exit(1) 44 | } 45 | } 46 | 47 | func init() { 48 | cobra.OnInitialize(initConfig) 49 | 50 | // Here you will define your flags and configuration settings. 51 | // Cobra supports persistent flags, which, if defined here, 52 | // will be global for your application. 53 | 54 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.codepix.yaml)") 55 | 56 | // Cobra also supports local flags, which will only run 57 | // when this action is called directly. 58 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 59 | } 60 | 61 | // initConfig reads in config file and ENV variables if set. 62 | func initConfig() { 63 | if cfgFile != "" { 64 | // Use config file from the flag. 65 | viper.SetConfigFile(cfgFile) 66 | } else { 67 | // Find home directory. 68 | home, err := homedir.Dir() 69 | if err != nil { 70 | fmt.Println(err) 71 | os.Exit(1) 72 | } 73 | 74 | // Search config in home directory with name ".codepix" (without extension). 75 | viper.AddConfigPath(home) 76 | viper.SetConfigName(".codepix") 77 | } 78 | 79 | viper.AutomaticEnv() // read in environment variables that match 80 | 81 | // If a config file is found, read it in. 82 | if err := viper.ReadInConfig(); err == nil { 83 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /codepix/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | build: . 6 | ports: 7 | - "50051:50051" 8 | volumes: 9 | - .:/go/src/ 10 | extra_hosts: 11 | - "host.docker.internal:172.17.0.1" 12 | 13 | db: 14 | image: postgres:9.4 15 | restart: always 16 | tty: true 17 | volumes: 18 | - .pgdata:/var/lib/postgresql/data 19 | environment: 20 | - POSTGRES_PASSWORD=root 21 | - POSTGRES_DB=codepix 22 | ports: 23 | - "5432:5432" 24 | 25 | pgadmin: 26 | image: dpage/pgadmin4 27 | tty: true 28 | environment: 29 | - PGADMIN_DEFAULT_EMAIL=admin@user.com 30 | - PGADMIN_DEFAULT_PASSWORD=123456 31 | ports: 32 | - "9000:80" 33 | depends_on: 34 | - db -------------------------------------------------------------------------------- /codepix/domain/model/account.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/asaskevich/govalidator" 7 | uuid "github.com/satori/go.uuid" 8 | ) 9 | 10 | func init() { 11 | govalidator.SetFieldsRequiredByDefault(true) 12 | } 13 | 14 | type Account struct { 15 | Base `valid:"required"` 16 | OwnerName string `gorm:"column:owner_name;type:varchar(255);not null" valid:"notnull"` 17 | Bank *Bank `valid:"-"` 18 | BankID string `gorm:"column:bank_id;type:uuid;not null" valid:"-"` 19 | Number string `json:"number" gorm:"type:varchar(20)" valid:"notnull"` 20 | PixKeys []*PixKey `gorm:"ForeignKey:AccountID" valid:"-"` 21 | } 22 | 23 | func (account *Account) isValid() error { 24 | _, err := govalidator.ValidateStruct(account) 25 | if err != nil { 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | func NewAccount(bank *Bank, number string, ownerName string) (*Account, error) { 32 | account := Account{ 33 | Bank: bank, 34 | BankID: bank.ID, 35 | Number: number, 36 | OwnerName: ownerName, 37 | } 38 | 39 | account.ID = uuid.NewV4().String() 40 | account.CreatedAt = time.Now() 41 | 42 | err := account.isValid() 43 | if err != nil { 44 | return nil, err 45 | } 46 | return &account, nil 47 | } 48 | -------------------------------------------------------------------------------- /codepix/domain/model/account_test.go: -------------------------------------------------------------------------------- 1 | package model_test 2 | 3 | import ( 4 | "testing" 5 | 6 | uuid "github.com/satori/go.uuid" 7 | 8 | "github.com/codeedu/imersao/codepix-go/domain/model" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestModel_NewAccount(t *testing.T) { 13 | code := "001" 14 | name := "Banco do Brasil" 15 | bank, err := model.NewBank(code, name) 16 | 17 | accountNumber := "abcnumber" 18 | ownerName := "Wesley" 19 | account, err := model.NewAccount(bank, accountNumber, ownerName) 20 | 21 | require.Nil(t, err) 22 | require.NotEmpty(t, uuid.FromStringOrNil(account.ID)) 23 | require.Equal(t, account.Number, accountNumber) 24 | require.Equal(t, account.BankID, bank.ID) 25 | 26 | _, err = model.NewAccount(bank, "", ownerName) 27 | require.NotNil(t, err) 28 | } 29 | -------------------------------------------------------------------------------- /codepix/domain/model/bank.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/asaskevich/govalidator" 7 | uuid "github.com/satori/go.uuid" 8 | ) 9 | 10 | func init() { 11 | govalidator.SetFieldsRequiredByDefault(true) 12 | } 13 | 14 | type Bank struct { 15 | Base `valid:"required"` 16 | Code string `json:"code" gorm:"type:varchar(20)" valid:"notnull"` 17 | Name string `json:"name" gorm:"type:varchar(255)" valid:"notnull"` 18 | Accounts []*Account `gorm:"ForeignKey:BankID" valid:"-"` 19 | } 20 | 21 | func (bank *Bank) isValid() error { 22 | _, err := govalidator.ValidateStruct(bank) 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | 29 | func NewBank(code string, name string) (*Bank, error) { 30 | bank := Bank{ 31 | Code: code, 32 | Name: name, 33 | } 34 | bank.ID = uuid.NewV4().String() 35 | bank.CreatedAt = time.Now() 36 | err := bank.isValid() 37 | if err != nil { 38 | return nil, err 39 | } 40 | return &bank, nil 41 | } 42 | -------------------------------------------------------------------------------- /codepix/domain/model/bank_test.go: -------------------------------------------------------------------------------- 1 | package model_test 2 | 3 | import ( 4 | "testing" 5 | 6 | uuid "github.com/satori/go.uuid" 7 | 8 | "github.com/codeedu/imersao/codepix-go/domain/model" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestModel_NewBank(t *testing.T) { 13 | 14 | code := "001" 15 | name := "Banco do Brasil" 16 | 17 | bank, err := model.NewBank(code, name) 18 | 19 | require.Nil(t, err) 20 | require.NotEmpty(t, uuid.FromStringOrNil(bank.ID)) 21 | require.Equal(t, bank.Code, code) 22 | require.Equal(t, bank.Name, name) 23 | 24 | _, err = model.NewBank("", "") 25 | require.NotNil(t, err) 26 | } 27 | -------------------------------------------------------------------------------- /codepix/domain/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/asaskevich/govalidator" 7 | ) 8 | 9 | func init() { 10 | govalidator.SetFieldsRequiredByDefault(true) 11 | } 12 | 13 | type Base struct { 14 | ID string `json:"id" gorm:"type:uuid;primary_key" valid:"uuid"` 15 | CreatedAt time.Time `json:"created_at" valid:"-"` 16 | UpdatedAt time.Time `json:"updated_at" valid:"-"` 17 | } 18 | -------------------------------------------------------------------------------- /codepix/domain/model/pixKey.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "github.com/asaskevich/govalidator" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | type PixKeyRepositoryInterface interface { 11 | RegisterKey(pixKey *PixKey) (*PixKey, error) 12 | FindKeyByKind(key string, kind string) (*PixKey, error) 13 | AddBank(bank *Bank) error 14 | AddAccount(account *Account) error 15 | FindAccount(id string) (*Account, error) 16 | } 17 | 18 | func init() { 19 | govalidator.SetFieldsRequiredByDefault(true) 20 | } 21 | 22 | type PixKey struct { 23 | Base `valid:"required"` 24 | Kind string `json:"kind" gorm:"type:varchar(20)" valid:"notnull"` 25 | Key string `json:"key" gorm:"type:varchar(255)" valid:"notnull"` 26 | AccountID string `gorm:"column:account_id;type:uuid;not null" valid:"-"` 27 | Account *Account `valid:"-"` 28 | Status string `json:"status" gorm:"type:varchar(20)" valid:"notnull"` 29 | } 30 | 31 | func (p *PixKey) isValid() error { 32 | _, err := govalidator.ValidateStruct(p) 33 | 34 | if p.Kind != "email" && p.Kind != "cpf" { 35 | return errors.New("invalid type of key") 36 | } 37 | 38 | if p.Status != "active" && p.Status != "inactive" { 39 | return errors.New("invalid status") 40 | } 41 | 42 | if err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | func NewPixKey(kind string, account *Account, key string) (*PixKey, error) { 49 | pixKey := PixKey{ 50 | Kind: kind, 51 | Key: key, 52 | Account: account, 53 | AccountID: account.ID, 54 | Status: "active", 55 | } 56 | pixKey.ID = uuid.NewV4().String() 57 | pixKey.CreatedAt = time.Now() 58 | err := pixKey.isValid() 59 | if err != nil { 60 | return nil, err 61 | } 62 | return &pixKey, nil 63 | } 64 | -------------------------------------------------------------------------------- /codepix/domain/model/pixKey_test.go: -------------------------------------------------------------------------------- 1 | package model_test 2 | 3 | import ( 4 | "testing" 5 | 6 | uuid "github.com/satori/go.uuid" 7 | 8 | "github.com/codeedu/imersao/codepix-go/domain/model" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestModel_NewPixKey(t *testing.T) { 13 | code := "001" 14 | name := "Banco do Brasil" 15 | bank, err := model.NewBank(code, name) 16 | 17 | accountNumber := "abcnumber" 18 | ownerName := "Wesley" 19 | account, err := model.NewAccount(bank, accountNumber, ownerName) 20 | 21 | kind := "email" 22 | key := "j@j.com" 23 | pixKey, err := model.NewPixKey(kind, account, key) 24 | 25 | require.NotEmpty(t, uuid.FromStringOrNil(pixKey.ID)) 26 | require.Equal(t, pixKey.Kind, kind) 27 | require.Equal(t, pixKey.Status, "active") 28 | 29 | kind = "cpf" 30 | _, err = model.NewPixKey(kind, account, key) 31 | require.Nil(t, err) 32 | 33 | _, err = model.NewPixKey("nome", account, key) 34 | require.NotNil(t, err) 35 | } 36 | -------------------------------------------------------------------------------- /codepix/domain/model/transaction.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "github.com/asaskevich/govalidator" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | const ( 11 | TransactionPending string = "pending" 12 | TransactionCompleted string = "completed" 13 | TransactionError string = "error" 14 | TransactionConfirmed string = "confirmed" 15 | ) 16 | 17 | type TransactionRepositoryInterface interface { 18 | Register(transaction *Transaction) error 19 | Save(transaction *Transaction) error 20 | Find(id string) (*Transaction, error) 21 | } 22 | 23 | type Transactions struct { 24 | Transaction []Transaction 25 | } 26 | 27 | type Transaction struct { 28 | Base `valid:"required"` 29 | AccountFrom *Account `valid:"-"` 30 | AccountFromID string `gorm:"column:account_from_id;type:uuid;" valid:"notnull"` 31 | Amount float64 `json:"amount" gorm:"type:float" valid:"notnull"` 32 | PixKeyTo *PixKey `valid:"-"` 33 | PixKeyIdTo string `gorm:"column:pix_key_id_to;type:uuid;" valid:"notnull"` 34 | Status string `json:"status" gorm:"type:varchar(20)" valid:"notnull"` 35 | Description string `json:"description" gorm:"type:varchar(255)" valid:"-"` 36 | CancelDescription string `json:"cancel_description" gorm:"type:varchar(255)" valid:"-"` 37 | } 38 | 39 | func init() { 40 | govalidator.SetFieldsRequiredByDefault(true) 41 | } 42 | 43 | func (t *Transaction) isValid() error { 44 | _, err := govalidator.ValidateStruct(t) 45 | 46 | if t.Amount <= 0 { 47 | return errors.New("the amount must be greater than 0") 48 | } 49 | 50 | if t.Status != TransactionPending && t.Status != TransactionCompleted && t.Status != TransactionError { 51 | return errors.New("invalid status for the transaction") 52 | } 53 | 54 | if t.PixKeyTo.AccountID == t.AccountFromID { 55 | return errors.New("the source and destination account cannot be the same") 56 | } 57 | 58 | if err != nil { 59 | return err 60 | } 61 | return nil 62 | } 63 | 64 | func (t *Transaction) Complete() error { 65 | t.Status = TransactionCompleted 66 | t.UpdatedAt = time.Now() 67 | err := t.isValid() 68 | return err 69 | } 70 | 71 | func (t *Transaction) Cancel(description string) error { 72 | t.Status = TransactionError 73 | t.CancelDescription = description 74 | t.UpdatedAt = time.Now() 75 | err := t.isValid() 76 | return err 77 | } 78 | 79 | func NewTransaction(accountFrom *Account, amount float64, pixKeyTo *PixKey, description string, id string) (*Transaction, error) { 80 | transaction := Transaction{ 81 | AccountFrom: accountFrom, 82 | AccountFromID: accountFrom.ID, 83 | Amount: amount, 84 | PixKeyTo: pixKeyTo, 85 | PixKeyIdTo: pixKeyTo.ID, 86 | Status: TransactionPending, 87 | Description: description, 88 | } 89 | if id == "" { 90 | transaction.ID = uuid.NewV4().String() 91 | } else { 92 | transaction.ID = id 93 | } 94 | transaction.CreatedAt = time.Now() 95 | err := transaction.isValid() 96 | if err != nil { 97 | return nil, err 98 | } 99 | return &transaction, nil 100 | } 101 | -------------------------------------------------------------------------------- /codepix/domain/model/transaction_test.go: -------------------------------------------------------------------------------- 1 | package model_test 2 | 3 | import ( 4 | "testing" 5 | 6 | uuid "github.com/satori/go.uuid" 7 | 8 | "github.com/codeedu/imersao/codepix-go/domain/model" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestNewTransaction(t *testing.T) { 13 | code := "001" 14 | name := "Banco do Brasil" 15 | bank, _ := model.NewBank(code, name) 16 | 17 | accountNumber := "abcnumber" 18 | ownerName := "Wesley" 19 | account, _ := model.NewAccount(bank, accountNumber, ownerName) 20 | 21 | accountNumberDestination := "abcdestination" 22 | ownerName = "Mariana" 23 | accountDestination, _ := model.NewAccount(bank, accountNumberDestination, ownerName) 24 | 25 | kind := "email" 26 | key := "j@j.com" 27 | pixKey, _ := model.NewPixKey(kind, accountDestination, key) 28 | 29 | require.NotEqual(t, account.ID, accountDestination.ID) 30 | 31 | amount := 3.10 32 | statusTransaction := "pending" 33 | transaction, err := model.NewTransaction(account, amount, pixKey, "My description", "") 34 | // 35 | require.Nil(t, err) 36 | require.NotNil(t, uuid.FromStringOrNil(transaction.ID)) 37 | require.Equal(t, transaction.Amount, amount) 38 | require.Equal(t, transaction.Status, statusTransaction) 39 | require.Equal(t, transaction.Description, "My description") 40 | require.Empty(t, transaction.CancelDescription) 41 | 42 | pixKeySameAccount, err := model.NewPixKey(kind, account, key) 43 | 44 | _, err = model.NewTransaction(account, amount, pixKeySameAccount, "My description", "") 45 | require.NotNil(t, err) 46 | 47 | _, err = model.NewTransaction(account, 0, pixKey, "My description", "") 48 | require.NotNil(t, err) 49 | 50 | } 51 | 52 | func TestModel_ChangeStatusOfATransaction(t *testing.T) { 53 | code := "001" 54 | name := "Banco do Brasil" 55 | bank, _ := model.NewBank(code, name) 56 | 57 | accountNumber := "abcnumber" 58 | ownerName := "Wesley" 59 | account, _ := model.NewAccount(bank, accountNumber, ownerName) 60 | 61 | accountNumberDestination := "abcdestination" 62 | ownerName = "Mariana" 63 | accountDestination, _ := model.NewAccount(bank, accountNumberDestination, ownerName) 64 | 65 | kind := "email" 66 | key := "j@j.com" 67 | pixKey, _ := model.NewPixKey(kind, accountDestination, key) 68 | 69 | amount := 3.10 70 | transaction, _ := model.NewTransaction(account, amount, pixKey, "My description", "") 71 | 72 | transaction.Complete() 73 | require.Equal(t, transaction.Status, model.TransactionCompleted) 74 | 75 | transaction.Cancel("Error") 76 | require.Equal(t, transaction.Status, model.TransactionError) 77 | require.Equal(t, transaction.CancelDescription, "Error") 78 | 79 | } 80 | -------------------------------------------------------------------------------- /codepix/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codeedu/imersao/codepix-go 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef 7 | github.com/confluentinc/confluent-kafka-go v1.5.2 8 | github.com/go-playground/validator/v10 v10.4.1 9 | github.com/golang/protobuf v1.4.3 10 | github.com/jinzhu/gorm v1.9.16 11 | github.com/joho/godotenv v1.3.0 12 | github.com/lib/pq v1.9.0 13 | github.com/mitchellh/go-homedir v1.1.0 14 | github.com/satori/go.uuid v1.2.0 15 | github.com/spf13/cobra v1.1.1 16 | github.com/spf13/viper v1.7.1 17 | github.com/stretchr/testify v1.7.0 18 | google.golang.org/grpc v1.35.0 19 | google.golang.org/protobuf v1.25.0 20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 21 | gorm.io/driver/sqlite v1.1.4 22 | gorm.io/gorm v1.20.12 23 | ) 24 | -------------------------------------------------------------------------------- /codepix/infrastructure/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/codeedu/imersao/codepix-go/domain/model" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | 10 | "github.com/jinzhu/gorm" 11 | "github.com/joho/godotenv" 12 | _ "github.com/lib/pq" 13 | _ "gorm.io/driver/sqlite" 14 | ) 15 | 16 | func init() { 17 | _, b, _, _ := runtime.Caller(0) 18 | basepath := filepath.Dir(b) 19 | 20 | err := godotenv.Load(basepath + "/../../.env") 21 | 22 | if err != nil { 23 | log.Fatalf("Error loading .env files") 24 | } 25 | } 26 | 27 | func ConnectDB(env string) *gorm.DB { 28 | var dsn string 29 | var db *gorm.DB 30 | var err error 31 | 32 | if env != "test" { 33 | dsn = os.Getenv("dsn") 34 | db, err = gorm.Open(os.Getenv("dbType"), dsn) 35 | } else { 36 | dsn = os.Getenv("dsnTest") 37 | db, err = gorm.Open(os.Getenv("dbTypeTest"), dsn) 38 | } 39 | 40 | if err != nil { 41 | log.Fatalf("Error connecting to database: %v", err) 42 | panic(err) 43 | } 44 | 45 | if os.Getenv("debug") == "true" { 46 | db.LogMode(true) 47 | } 48 | 49 | if os.Getenv("AutoMigrateDb") == "true" { 50 | db.AutoMigrate(&model.Bank{}, &model.Account{}, &model.PixKey{}, &model.Transaction{}) 51 | } 52 | 53 | return db 54 | } 55 | -------------------------------------------------------------------------------- /codepix/infrastructure/repository/pix.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | "github.com/codeedu/imersao/codepix-go/domain/model" 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type PixKeyRepositoryDb struct { 10 | Db *gorm.DB 11 | } 12 | 13 | func (r PixKeyRepositoryDb) AddBank(bank *model.Bank) error { 14 | err := r.Db.Create(bank).Error 15 | if err != nil { 16 | return err 17 | } 18 | return nil 19 | } 20 | 21 | func (r PixKeyRepositoryDb) AddAccount(account *model.Account) error { 22 | err := r.Db.Create(account).Error 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | 29 | func (r PixKeyRepositoryDb) RegisterKey(pixKey *model.PixKey) (*model.PixKey, error) { 30 | err := r.Db.Create(pixKey).Error 31 | if err != nil { 32 | return nil, err 33 | } 34 | return pixKey, nil 35 | } 36 | 37 | func (r PixKeyRepositoryDb) FindKeyByKind(key string, kind string) (*model.PixKey, error) { 38 | var pixKey model.PixKey 39 | r.Db.Preload("Account.Bank").First(&pixKey, "kind = ? and key = ?", kind, key) 40 | 41 | if pixKey.ID == "" { 42 | return nil, fmt.Errorf("no key was found") 43 | } 44 | return &pixKey, nil 45 | } 46 | 47 | func (r PixKeyRepositoryDb) FindAccount(id string) (*model.Account, error) { 48 | var account model.Account 49 | r.Db.Preload("Bank").First(&account, "id = ?", id) 50 | 51 | if account.ID == "" { 52 | return nil, fmt.Errorf("no account found") 53 | } 54 | return &account, nil 55 | } 56 | 57 | func (r PixKeyRepositoryDb) FindBank(id string) (*model.Bank, error) { 58 | var bank model.Bank 59 | r.Db.First(&bank, "id = ?", id) 60 | 61 | if bank.ID == "" { 62 | return nil, fmt.Errorf("no bank found") 63 | } 64 | return &bank, nil 65 | } 66 | -------------------------------------------------------------------------------- /codepix/infrastructure/repository/transaction.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "fmt" 5 | "github.com/codeedu/imersao/codepix-go/domain/model" 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type TransactionRepositoryDb struct { 10 | Db *gorm.DB 11 | } 12 | 13 | func (t *TransactionRepositoryDb) Register(transaction *model.Transaction) error { 14 | err := t.Db.Create(transaction).Error 15 | if err != nil { 16 | return err 17 | } 18 | return nil 19 | } 20 | 21 | func (t *TransactionRepositoryDb) Save(transaction *model.Transaction) error { 22 | err := t.Db.Save(transaction).Error 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | 29 | func (t *TransactionRepositoryDb) Find(id string) (*model.Transaction, error) { 30 | var transaction model.Transaction 31 | t.Db.Preload("AccountFrom.Bank").First(&transaction, "id = ?", id) 32 | 33 | if transaction.ID == "" { 34 | return nil, fmt.Errorf("no key was found") 35 | } 36 | return &transaction, nil 37 | } 38 | -------------------------------------------------------------------------------- /codepix/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import "github.com/codeedu/imersao/codepix-go/cmd" 19 | 20 | func main() { 21 | cmd.Execute() 22 | } 23 | -------------------------------------------------------------------------------- /k8s/bankapi/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: bankapi-conf 5 | data: 6 | env: | 7 | TYPEORM_CONNECTION=postgres 8 | TYPEORM_HOST=postgres-postgresql 9 | TYPEORM_USERNAME=postgres 10 | TYPEORM_DATABASE=bank001 11 | TYPEORM_PORT=5432 12 | TYPEORM_ENTITIES=src/**/*.model.ts 13 | TYPEORM_ENTITIES_DIR=src/models 14 | TYPEORM_MIGRATIONS=src/migrations/**/*.ts 15 | TYPEORM_MIGRATIONS_DIR=src/migrations 16 | GRPC_URL=codepix-service:50051 17 | KAFKA_CLIENT_ID=codepix 18 | KAFKA_BROKER=kafka:9092 19 | KAFKA_CONSUMER_GROUP_ID=bank001 20 | BANK_CODE=001 -------------------------------------------------------------------------------- /k8s/bankapi/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: bankapi 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: bankapi 10 | template: 11 | metadata: 12 | labels: 13 | app: bankapi 14 | spec: 15 | containers: 16 | - name: codepix 17 | image: wesleywillians/imersao-bankapi:latest 18 | ports: 19 | - containerPort: 3000 20 | envFrom: 21 | - secretRef: 22 | name: bankapi-secret 23 | 24 | volumeMounts: 25 | - name: bankapi-volume 26 | mountPath: /home/node/app/.env 27 | subPath: .env 28 | 29 | 30 | volumes: 31 | - name: bankapi-volume 32 | configMap: 33 | name: bankapi-conf 34 | items: 35 | - key: env 36 | path: .env 37 | 38 | -------------------------------------------------------------------------------- /k8s/bankapi/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: bankapi-secret 5 | type: Opaque 6 | data: 7 | TYPEORM_PASSWORD: c296eW1ydWpPMg== 8 | -------------------------------------------------------------------------------- /k8s/bankapi/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: bankapi-service 5 | spec: 6 | type: ClusterIP 7 | selector: 8 | app: bankapi 9 | ports: 10 | - port: 3000 11 | 12 | -------------------------------------------------------------------------------- /k8s/bankapi002/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: bankapi002-conf 5 | data: 6 | env: | 7 | TYPEORM_CONNECTION=postgres 8 | TYPEORM_HOST=postgres-postgresql 9 | TYPEORM_USERNAME=postgres 10 | TYPEORM_DATABASE=bank002 11 | TYPEORM_PORT=5432 12 | TYPEORM_ENTITIES=src/**/*.model.ts 13 | TYPEORM_ENTITIES_DIR=src/models 14 | TYPEORM_MIGRATIONS=src/migrations/**/*.ts 15 | TYPEORM_MIGRATIONS_DIR=src/migrations 16 | GRPC_URL=codepix-service:50051 17 | KAFKA_CLIENT_ID=codepix 18 | KAFKA_BROKER=kafka:9092 19 | KAFKA_CONSUMER_GROUP_ID=bank002 20 | BANK_CODE=002 -------------------------------------------------------------------------------- /k8s/bankapi002/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: bankapi002 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: bankapi002 10 | template: 11 | metadata: 12 | labels: 13 | app: bankapi002 14 | spec: 15 | containers: 16 | - name: bankapi002 17 | image: wesleywillians/imersao-bankapi:latest 18 | ports: 19 | - containerPort: 3000 20 | envFrom: 21 | - secretRef: 22 | name: bankapi002-secret 23 | 24 | volumeMounts: 25 | - name: bankapi002-volume 26 | mountPath: /home/node/app/.env 27 | subPath: .env 28 | 29 | 30 | volumes: 31 | - name: bankapi002-volume 32 | configMap: 33 | name: bankapi002-conf 34 | items: 35 | - key: env 36 | path: .env 37 | 38 | -------------------------------------------------------------------------------- /k8s/bankapi002/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: bankapi002-secret 5 | type: Opaque 6 | data: 7 | TYPEORM_PASSWORD: c296eW1ydWpPMg== 8 | -------------------------------------------------------------------------------- /k8s/bankapi002/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: bankapi002-service 5 | spec: 6 | type: ClusterIP 7 | selector: 8 | app: bankapi002 9 | ports: 10 | - port: 3000 11 | 12 | -------------------------------------------------------------------------------- /k8s/bankfrontend/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: bankfrontend-conf 5 | data: 6 | env: | 7 | NEXT_PUBLIC_NEST_API_URL=http://bankapi-service:3000/api 8 | NEXT_PUBLIC_BANK_NAME=BBX 9 | NEXT_PUBLIC_BANK_CODE=001 -------------------------------------------------------------------------------- /k8s/bankfrontend/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: bankfrontend 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: bankfrontend 10 | template: 11 | metadata: 12 | labels: 13 | app: bankfrontend 14 | spec: 15 | containers: 16 | - name: bankfrontend 17 | image: wesleywillians/imersao-bankfrontend:latest 18 | ports: 19 | - containerPort: 3000 20 | 21 | volumeMounts: 22 | - name: bankfrontend-volume 23 | mountPath: /app/.env 24 | subPath: .env 25 | 26 | volumes: 27 | - name: bankfrontend-volume 28 | configMap: 29 | name: bankfrontend-conf 30 | items: 31 | - key: env 32 | path: .env 33 | 34 | -------------------------------------------------------------------------------- /k8s/bankfrontend/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: bankfrontend-service 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: bankfrontend 9 | ports: 10 | - port: 3000 11 | 12 | -------------------------------------------------------------------------------- /k8s/bankfrontend002/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: bankfrontend002-conf 5 | data: 6 | env: | 7 | NEXT_PUBLIC_NEST_API_URL=http://bankapi002-service:3000/api 8 | NEXT_PUBLIC_BANK_NAME=CTER 9 | NEXT_PUBLIC_BANK_CODE=002 -------------------------------------------------------------------------------- /k8s/bankfrontend002/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: bankfrontend002 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: bankfrontend002 10 | template: 11 | metadata: 12 | labels: 13 | app: bankfrontend002 14 | spec: 15 | containers: 16 | - name: bankfrontend002 17 | image: wesleywillians/imersao-bankfrontend:latest 18 | ports: 19 | - containerPort: 3000 20 | 21 | volumeMounts: 22 | - name: bankfrontend-volume 23 | mountPath: /app/.env 24 | subPath: .env 25 | 26 | volumes: 27 | - name: bankfrontend-volume 28 | configMap: 29 | name: bankfrontend002-conf 30 | items: 31 | - key: env 32 | path: .env 33 | 34 | -------------------------------------------------------------------------------- /k8s/bankfrontend002/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: bankfrontend002-service 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: bankfrontend002 9 | ports: 10 | - port: 3000 11 | 12 | -------------------------------------------------------------------------------- /k8s/codepix/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: codepix-conf 5 | data: 6 | env: | 7 | dbType="postgres" 8 | dbTypeTest="sqlite3" 9 | dsnTest=":memory" 10 | env="dev" 11 | debug="true" 12 | AutoMigrateDb="true" 13 | kafkaBootstrapServers="kafka:9092" 14 | kafkaConsumerGroupId="codepix" 15 | kafkaTransactionTopic="transactions" 16 | kafkaTransactionConfirmationTopic="transaction_confirmation" -------------------------------------------------------------------------------- /k8s/codepix/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: codepix 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: codepix 10 | template: 11 | metadata: 12 | labels: 13 | app: codepix 14 | spec: 15 | containers: 16 | - name: codepix 17 | image: wesleywillians/codepix-go 18 | command: ["./codepix","all"] 19 | ports: 20 | - containerPort: 50051 21 | envFrom: 22 | - secretRef: 23 | name: codepix-secret 24 | 25 | volumeMounts: 26 | - name: codepix-volume 27 | mountPath: /go/src/.env 28 | subPath: .env 29 | 30 | 31 | volumes: 32 | - name: codepix-volume 33 | configMap: 34 | name: codepix-conf 35 | items: 36 | - key: env 37 | path: .env 38 | 39 | -------------------------------------------------------------------------------- /k8s/codepix/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: codepix-secret 5 | type: Opaque 6 | data: 7 | dsn: ZGJuYW1lPWNvZGVwaXggc3NsbW9kZT1kaXNhYmxlIHVzZXI9cG9zdGdyZXMgcGFzc3dvcmQ9c296eW1ydWpPMiBob3N0PXBvc3RncmVzLXBvc3RncmVzcWw= 8 | -------------------------------------------------------------------------------- /k8s/codepix/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: codepix-service 5 | spec: 6 | type: ClusterIP 7 | selector: 8 | app: codepix 9 | ports: 10 | - port: 50051 11 | 12 | --------------------------------------------------------------------------------