Repository: devfullcycle/imersao12 Branch: main Commit: 09811339d58f Files: 83 Total size: 66.1 KB Directory structure: gitextract__u_u31gc/ ├── .gitignore ├── README.md ├── apache-kafka/ │ ├── README.md │ ├── connectors/ │ │ └── elasticsearch.properties │ └── docker-compose.yaml ├── k8s/ │ ├── backend/ │ │ ├── configmap.yaml │ │ ├── deploy.yaml │ │ └── service.yaml │ ├── frontend/ │ │ ├── deploy.yaml │ │ └── service.yaml │ └── simulator/ │ ├── configmap.yaml │ └── deploy.yaml ├── nest-api/ │ ├── .docker/ │ │ ├── entrypoint.sh │ │ └── mongo/ │ │ └── init.js │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── settings.json │ ├── Dockerfile │ ├── Dockerfile.prod │ ├── README.md │ ├── api.http │ ├── docker-compose.yaml │ ├── nest-cli.json │ ├── package.json │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── main.ts │ │ └── routes/ │ │ ├── dto/ │ │ │ ├── create-route.dto.ts │ │ │ └── update-route.dto.ts │ │ ├── entities/ │ │ │ └── route.entity.ts │ │ ├── routes.controller.spec.ts │ │ ├── routes.controller.ts │ │ ├── routes.gateway.spec.ts │ │ ├── routes.gateway.ts │ │ ├── routes.module.ts │ │ ├── routes.service.spec.ts │ │ └── routes.service.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── react-frontend/ │ ├── .docker/ │ │ └── entrypoint.sh │ ├── .gitignore │ ├── .vscode/ │ │ └── settings.json │ ├── Dockerfile │ ├── Dockerfile.prod │ ├── README.md │ ├── docker-compose.yaml │ ├── package.json │ ├── public/ │ │ ├── index.html │ │ ├── manifest.json │ │ └── robots.txt │ ├── src/ │ │ ├── App.tsx │ │ ├── components/ │ │ │ ├── Mapping.tsx │ │ │ └── Navbar.tsx │ │ ├── errors/ │ │ │ └── route-exists.error.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.ts │ │ ├── setupTests.ts │ │ ├── theme.ts │ │ └── util/ │ │ ├── geolocation.ts │ │ ├── map.ts │ │ └── models.ts │ └── tsconfig.json └── simulator/ ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── application/ │ ├── kafka/ │ │ └── produce.go │ └── route/ │ └── route.go ├── destinations/ │ ├── 1.txt │ ├── 2.txt │ └── 3.txt ├── docker-compose.yaml ├── go.mod ├── go.sum ├── infra/ │ └── kafka/ │ ├── consumer.go │ └── producer.go └── main.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ simulator/.idea simulator/.vscode simulator/.env .history/ ================================================ FILE: README.md ================================================ # Imersão Fullcycle 12 - Codelivery ![Imersão Full Stack && Full Cycle](https://events-fullcycle.s3.amazonaws.com/events-fullcycle/static/site/img/grupo_4417.png) Participe gratuitamente: https://imersao.fullcycle.com.br/ ## Sobre o repositório Esse repositório contém todo código utilizado durante as aulas para referência. Faça seu fork e também nos dê uma estrelinha para nos ajudar a divulgar o projeto. As instruções de instalações estão no README.md de cada projeto. ## Se tiver dificuldades de como conectar o Kafka nos microsserviços Nesta aula, o professor Luiz, explicou como conectar o Kafka nos microsserviços, então, criamos um vídeo explicando como fazer isso. [https://www.youtube.com/watch?v=XsngzcsdnXQ](https://www.youtube.com/watch?v=XsngzcsdnXQ) ================================================ FILE: apache-kafka/README.md ================================================ # Imersão Full Stack & FullCycle - Codelivery ## Descrição Repositório do Apache Kafka (Backend) ## Configurar /etc/hosts A comunicação entre as aplicações se dá de forma direta através da rede da máquina. Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): ``` 127.0.0.1 host.docker.internal ``` Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. ## Rodar a aplicação Execute os comandos: ``` docker-compose up ``` Quando parar os containers do Kafka, lembre-se antes de rodar o **docker-compose up**, rodar o **docker-compose down** para limpar o armazenamento, senão lançará erro ao subir novamente. ### Para Windows Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=usF0rYCcj-E](https://www.youtube.com/watch?v=usF0rYCcj-E) Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) ## Se tiver dificuldades de como conectar o Kafka nos microsserviços Nesta aula, o professor Luiz, explicou como conectar o Kafka nos microsserviços, então, criamos um vídeo explicando como fazer isso. [https://www.youtube.com/watch?v=XsngzcsdnXQ](https://www.youtube.com/watch?v=XsngzcsdnXQ) ================================================ FILE: apache-kafka/connectors/elasticsearch.properties ================================================ name=elasticsearch-sink connector.class=io.confluent.connect.elasticsearch.ElasticsearchSinkConnector topics=route.new-direction,route.new-position connection.url=http://es01:9200 type.name=_doc value.converter=org.apache.kafka.connect.json.JsonConverter value.converter.schemas.enable=false schema.ignore=true key.ignore=true transforms=InsertField transforms.InsertField.type=org.apache.kafka.connect.transforms.InsertField$Value transforms.InsertField.timestamp.field=timestamp ================================================ FILE: apache-kafka/docker-compose.yaml ================================================ version: "3" services: zookeeper: image: confluentinc/cp-zookeeper:latest environment: ZOOKEEPER_CLIENT_PORT: 2181 extra_hosts: - "host.docker.internal:172.17.0.1" kafka: image: confluentinc/cp-kafka:latest depends_on: - zookeeper ports: - "9092:9092" - "9094:9094" environment: KAFKA_BROKER_ID: 1 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL KAFKA_LISTENERS: INTERNAL://:9092,OUTSIDE://:9094 KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,OUTSIDE://host.docker.internal:9094 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,OUTSIDE:PLAINTEXT extra_hosts: - "host.docker.internal:172.17.0.1" kafka-topics-generator: image: confluentinc/cp-kafka:latest depends_on: - kafka command: > bash -c "sleep 5s && kafka-topics --create --topic=route.new-direction --if-not-exists --bootstrap-server=kafka:9092 && kafka-topics --create --topic=route.new-position --if-not-exists --bootstrap-server=kafka:9092" control-center: image: confluentinc/cp-enterprise-control-center:6.0.1 hostname: control-center depends_on: - kafka ports: - "9021:9021" environment: CONTROL_CENTER_BOOTSTRAP_SERVERS: 'kafka:9092' CONTROL_CENTER_REPLICATION_FACTOR: 1 CONTROL_CENTER_CONNECT_CLUSTER: http://kafka-connect:8083 PORT: 9021 extra_hosts: - "host.docker.internal:172.17.0.1" kafka-connect: image: confluentinc/cp-kafka-connect-base:6.0.0 container_name: kafka-connect depends_on: - zookeeper - kafka ports: - 8083:8083 environment: CONNECT_BOOTSTRAP_SERVERS: "kafka:9092" CONNECT_REST_PORT: 8083 CONNECT_GROUP_ID: kafka-connect CONNECT_CONFIG_STORAGE_TOPIC: _connect-configs CONNECT_OFFSET_STORAGE_TOPIC: _connect-offsets CONNECT_STATUS_STORAGE_TOPIC: _connect-status CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter CONNECT_INTERNAL_KEY_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" CONNECT_INTERNAL_VALUE_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" CONNECT_REST_ADVERTISED_HOST_NAME: "kafka-connect" CONNECT_LOG4J_ROOT_LOGLEVEL: "INFO" CONNECT_LOG4J_LOGGERS: "org.apache.kafka.connect.runtime.rest=WARN,org.reflections=ERROR" CONNECT_LOG4J_APPENDER_STDOUT_LAYOUT_CONVERSIONPATTERN: "[%d] %p %X{connector.context}%m (%c:%L)%n" CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: "1" CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: "1" CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: "1" # # Optional settings to include to support Confluent Control Center # CONNECT_PRODUCER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor" # CONNECT_CONSUMER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor" # --------------- CONNECT_PLUGIN_PATH: /usr/share/java,/usr/share/confluent-hub-components,/data/connect-jars # If you want to use the Confluent Hub installer to d/l component, but make them available # when running this offline, spin up the stack once and then run : # docker cp kafka-connect:/usr/share/confluent-hub-components ./data/connect-jars volumes: - $PWD/data:/data # In the command section, $ are replaced with $$ to avoid the error 'Invalid interpolation format for "command" option' command: - bash - -c - | echo "Installing Connector" confluent-hub install --no-prompt confluentinc/kafka-connect-elasticsearch:10.0.1 # echo "Launching Kafka Connect worker" /etc/confluent/docker/run & # sleep infinity extra_hosts: - "host.docker.internal:172.17.0.1" es01: image: docker.elastic.co/elasticsearch/elasticsearch:7.11.2 container_name: es01 environment: - node.name=es01 - cluster.name=es-docker-cluster - cluster.initial_master_nodes=es01 - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 volumes: - ./es01:/usr/share/elasticsearch/data ports: - 9200:9200 extra_hosts: - "host.docker.internal:172.17.0.1" kibana: image: docker.elastic.co/kibana/kibana:7.11.2 container_name: kib01 ports: - 5601:5601 environment: ELASTICSEARCH_URL: http://es01:9200 ELASTICSEARCH_HOSTS: '["http://es01:9200"]' extra_hosts: - "host.docker.internal:172.17.0.1" ================================================ FILE: k8s/backend/configmap.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: backend-conf data: env: | MONGO_DSN=mongodb://root:root@mongodb/nest?authSource=admin KAFKA_CLIENT_ID=code-delivery KAFKA_BROKER=pkc-lzvrd.us-west4.gcp.confluent.cloud:9092 KAFKA_CONSUMER_GROUP_ID=code-delivery KAFKA_SASL_USERNAME=EBNIKUAMEB2PJ2TZ KAFKA_SASL_PASSWORD=59VVz1gr7l4tl4ikK4Lzlk/vLZB++7ek5vOC73cxtNsKLW7oUtbfxj/PicpbZ9rq ================================================ FILE: k8s/backend/deploy.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: backend spec: replicas: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: wesleywillians/imersao2-backend ports: - containerPort: 3000 volumeMounts: - name: backend-volume mountPath: /home/node/app/.env subPath: .env volumes: - name: backend-volume configMap: name: backend-conf items: - key: env path: .env ================================================ FILE: k8s/backend/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: backend-service spec: type: LoadBalancer selector: app: backend ports: - port: 3000 ================================================ FILE: k8s/frontend/deploy.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: wesleywillians/imersao2-frontend:latest ports: - containerPort: 80 ================================================ FILE: k8s/frontend/service.yaml ================================================ apiVersion: v1 kind: Service metadata: name: frontend-service spec: type: LoadBalancer selector: app: frontend ports: - port: 80 ================================================ FILE: k8s/simulator/configmap.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: simulator-conf data: env: | KafkaReadTopic=route.new-direction KafkaProduceTopic=route.new-position KafkaBootstrapServers=pkc-lzvrd.us-west4.gcp.confluent.cloud:9092 KafkaConsumerGroupId=simulator security.protocol="SASL_SSL" sasl.mechanisms="PLAIN" sasl.username="EBNIKUAMEB2PJ2TZ" sasl.password="59VVz1gr7l4tl4ikK4Lzlk/vLZB++7ek5vOC73cxtNsKLW7oUtbfxj/PicpbZ9rq" ================================================ FILE: k8s/simulator/deploy.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: simulator spec: replicas: 1 selector: matchLabels: app: simulator template: metadata: labels: app: simulator spec: containers: - name: simulator image: wesleywillians/imersao2-simulator:latest volumeMounts: - name: simulator-volume mountPath: /go/src/.env subPath: .env volumes: - name: simulator-volume configMap: name: simulator-conf items: - key: env path: .env ================================================ FILE: nest-api/.docker/entrypoint.sh ================================================ #!/bin/bash if [ ! -f ".env" ]; then cp .env.example .env fi npm install npm run start:dev ================================================ FILE: nest-api/.docker/mongo/init.js ================================================ db.routes.insertMany([ { _id: '1', title: 'Primeiro', startPosition: { lat: -15.82594, lng: -47.92923 }, endPosition: { lat: -15.82942, lng: -47.92765 }, }, { _id: '2', title: 'Segundo', startPosition: { lat: -15.82449, lng: -47.92756 }, endPosition: { lat: -15.8276, lng: -47.92621 }, }, { _id: '3', title: 'Terceiro', startPosition: { lat: -15.82331, lng: -47.92588 }, endPosition: { lat: -15.82758, lng: -47.92532 }, }, ]); ================================================ FILE: nest-api/.eslintrc.js ================================================ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { project: 'tsconfig.json', sourceType: 'module', }, plugins: ['@typescript-eslint/eslint-plugin'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], root: true, env: { node: true, jest: true, }, ignorePatterns: ['.eslintrc.js'], rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, }; ================================================ FILE: nest-api/.gitignore ================================================ # compiled output /dist /node_modules # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # OS .DS_Store # Tests /coverage /.nyc_output # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json .docker/dbdata/ .history/ .env ================================================ FILE: nest-api/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: nest-api/.vscode/settings.json ================================================ { "workbench.colorCustomizations": { "activityBar.activeBackground": "#b61a3d", "activityBar.activeBorder": "#133808", "activityBar.background": "#b61a3d", "activityBar.foreground": "#e7e7e7", "activityBar.inactiveForeground": "#e7e7e799", "activityBarBadge.background": "#133808", "activityBarBadge.foreground": "#e7e7e7", "statusBar.background": "#b61a3d", "statusBar.foreground": "#e7e7e7", "statusBarItem.hoverBackground": "#e0234e" }, "peacock.remoteColor": "#e0234e" } ================================================ FILE: nest-api/Dockerfile ================================================ FROM node:12.14.0-alpine3.11 RUN apk add --no-cache bash RUN npm install -g @nestjs/cli@7.5.6 USER node WORKDIR /home/node/app ================================================ FILE: nest-api/Dockerfile.prod ================================================ FROM node:14.15.4-slim USER node RUN mkdir -p /home/node/app WORKDIR /home/node/app COPY --chown=node package*.json ./ RUN npm install COPY --chown=node ./ . RUN npm run build EXPOSE 3000 CMD ["npm", "run", "start:prod"] ================================================ FILE: nest-api/README.md ================================================ # Imersão Full Stack & FullCycle - Codelivery ## Descrição Repositório do front-end feito com Nest.js (Backend) **Importante**: A aplicação do Apache Kafka, Golang deve estar rodando primeiro. ## Configurar /etc/hosts A comunicação entre as aplicações se dá de forma direta através da rede da máquina. Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): ``` 127.0.0.1 host.docker.internal ``` Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. ## Rodar a aplicação Execute os comandos: ``` docker-compose up ``` Acessar http://localhost:3000/routes. ### Para Windows Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=usF0rYCcj-E](https://www.youtube.com/watch?v=usF0rYCcj-E) Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) ================================================ FILE: nest-api/api.http ================================================ GET http://localhost:3000/routes ### GET http://localhost:3000/routes/1/start ================================================ FILE: nest-api/docker-compose.yaml ================================================ version: '3' services: app: build: . entrypoint: ./.docker/entrypoint.sh ports: - 3000:3000 volumes: - .:/home/node/app extra_hosts: - "host.docker.internal:172.17.0.1" depends_on: - db db: image: mongo:4.4.4 restart: always volumes: - ./.docker/dbdata:/data/db - ./.docker/mongo:/docker-entrypoint-initdb.d environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=root - MONGO_INITDB_DATABASE=nest mongo-express: image: mongo-express restart: always ports: - 8081:8081 environment: - ME_CONFIG_MONGODB_SERVER=db - ME_CONFIG_MONGODB_AUTH_USERNAME=root - ME_CONFIG_MONGODB_AUTH_PASSWORD=root - ME_CONFIG_MONGODB_ADMINUSERNAME=root - ME_CONFIG_MONGODB_ADMINPASSWORD=root depends_on: - db ================================================ FILE: nest-api/nest-cli.json ================================================ { "collection": "@nestjs/schematics", "sourceRoot": "src" } ================================================ FILE: nest-api/package.json ================================================ { "name": "nest-api", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^7.6.15", "@nestjs/config": "^0.6.3", "@nestjs/core": "^7.6.15", "@nestjs/mapped-types": "*", "@nestjs/microservices": "^7.6.15", "@nestjs/mongoose": "^7.2.4", "@nestjs/platform-express": "^7.6.15", "@nestjs/platform-socket.io": "^7.6.15", "@nestjs/websockets": "^7.6.15", "kafkajs": "^1.15.0", "mongoose": "^5.12.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.6.6" }, "devDependencies": { "@nestjs/cli": "^7.6.0", "@nestjs/schematics": "^7.3.0", "@nestjs/testing": "^7.6.15", "@types/express": "^4.17.11", "@types/jest": "^26.0.22", "@types/node": "^14.14.36", "@types/socket.io": "^2.1.13", "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/parser": "^4.19.0", "eslint": "^7.22.0", "eslint-config-prettier": "^8.1.0", "eslint-plugin-prettier": "^3.3.1", "jest": "^26.6.3", "prettier": "^2.2.1", "supertest": "^6.1.3", "ts-jest": "^26.5.4", "ts-loader": "^8.0.18", "ts-node": "^9.1.1", "tsconfig-paths": "^3.9.0", "typescript": "^4.2.3" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: nest-api/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let appController: AppController; beforeEach(async () => { const app: TestingModule = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); appController = app.get(AppController); }); describe('root', () => { it('should return "Hello World!"', () => { expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: nest-api/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller('/prefixo') export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } //HTTP - Get, Post, Put, Patch, Delete e ================================================ FILE: nest-api/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { MongooseModule } from '@nestjs/mongoose'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { RoutesModule } from './routes/routes.module'; //ES7 decorators @Module({ imports: [ ConfigModule.forRoot(), RoutesModule, MongooseModule.forRoot(process.env.MONGO_DSN, { useNewUrlParser: true, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: nest-api/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World222!'; } } ================================================ FILE: nest-api/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { Transport } from '@nestjs/microservices'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true }); app.connectMicroservice({ transport: Transport.KAFKA, options: { client: { clientId: process.env.KAFKA_CLIENT_ID, brokers: [process.env.KAFKA_BROKER], // ssl: true, // sasl: { // mechanism: 'plain', // scram-sha-256 or scram-sha-512 // username: process.env.KAFKA_SASL_USERNAME, // password: process.env.KAFKA_SASL_PASSWORD, // }, }, consumer: { groupId: !process.env.KAFKA_CONSUMER_GROUP_ID || process.env.KAFKA_CONSUMER_GROUP_ID === '' ? 'my-consumer-' + Math.random() : process.env.KAFKA_CONSUMER_GROUP_ID, }, }, }); await app.startAllMicroservicesAsync(); await app.listen(3000); } bootstrap(); ================================================ FILE: nest-api/src/routes/dto/create-route.dto.ts ================================================ export class CreateRouteDto {} ================================================ FILE: nest-api/src/routes/dto/update-route.dto.ts ================================================ import { PartialType } from '@nestjs/mapped-types'; import { CreateRouteDto } from './create-route.dto'; export class UpdateRouteDto extends PartialType(CreateRouteDto) {} ================================================ FILE: nest-api/src/routes/entities/route.entity.ts ================================================ import { Prop, Schema, raw, SchemaFactory } from '@nestjs/mongoose'; import { Document } from 'mongoose'; export type RouteDocument = Route & Document; @Schema() export class Route { @Prop() _id: string; @Prop() title: string; @Prop( raw({ lat: { type: Number }, lng: { type: Number }, }), ) startPosition: { lat: number; lng: number }; @Prop( raw({ lat: { type: Number }, lng: { type: Number }, }), ) endPosition: { lat: number; lng: number }; } export const RouteSchema = SchemaFactory.createForClass(Route); ================================================ FILE: nest-api/src/routes/routes.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { RoutesController } from './routes.controller'; import { RoutesService } from './routes.service'; describe('RoutesController', () => { let controller: RoutesController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [RoutesController], providers: [RoutesService], }).compile(); controller = module.get(RoutesController); }); it('should be defined', () => { expect(controller).toBeDefined(); }); }); ================================================ FILE: nest-api/src/routes/routes.controller.ts ================================================ import { Controller, Get, Post, Body, Patch, Param, Delete, Inject, OnModuleInit, } from '@nestjs/common'; import { RoutesService } from './routes.service'; import { CreateRouteDto } from './dto/create-route.dto'; import { UpdateRouteDto } from './dto/update-route.dto'; import { ClientKafka, MessagePattern, Payload } from '@nestjs/microservices'; import { Producer } from '@nestjs/microservices/external/kafka.interface'; import { RoutesGateway } from './routes.gateway'; @Controller('routes') export class RoutesController implements OnModuleInit { private kafkaProducer: Producer; constructor( private readonly routesService: RoutesService, @Inject('KAFKA_SERVICE') private kafkaClient: ClientKafka, private routeGateway: RoutesGateway, ) {} @Post() create(@Body() createRouteDto: CreateRouteDto) { return this.routesService.create(createRouteDto); } @Get() findAll() { return this.routesService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.routesService.findOne(+id); } @Patch(':id') update(@Param('id') id: string, @Body() updateRouteDto: UpdateRouteDto) { return this.routesService.update(+id, updateRouteDto); } @Delete(':id') remove(@Param('id') id: string) { return this.routesService.remove(+id); } async onModuleInit() { this.kafkaProducer = await this.kafkaClient.connect(); } @Get(':id/start') startRoute(@Param('id') id: string) { this.kafkaProducer.send({ topic: 'route.new-direction', messages: [ { key: 'route.new-direction', value: JSON.stringify({ routeId: id, clientId: '' }), }, ], }); } @MessagePattern('route.new-position') consumeNewPosition( @Payload() message: { value: { routeId: string; clientId: string; position: [number, number]; finished: boolean; }; }, ) { this.routeGateway.sendPosition({ ...message.value, position: [message.value.position[1], message.value.position[0]], }); } } ================================================ FILE: nest-api/src/routes/routes.gateway.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { RoutesGateway } from './routes.gateway'; describe('RoutesGateway', () => { let gateway: RoutesGateway; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [RoutesGateway], }).compile(); gateway = module.get(RoutesGateway); }); it('should be defined', () => { expect(gateway).toBeDefined(); }); }); ================================================ FILE: nest-api/src/routes/routes.gateway.ts ================================================ import { Inject, OnModuleInit } from '@nestjs/common'; import { ClientKafka } from '@nestjs/microservices'; import { Producer } from '@nestjs/microservices/external/kafka.interface'; import { SubscribeMessage, WebSocketGateway, WebSocketServer, } from '@nestjs/websockets'; import { Socket, Server } from 'socket.io'; @WebSocketGateway() export class RoutesGateway implements OnModuleInit { private kafkaProducer: Producer; @WebSocketServer() server: Server; constructor( @Inject('KAFKA_SERVICE') private kafkaClient: ClientKafka, ) {} async onModuleInit() { this.kafkaProducer = await this.kafkaClient.connect(); } @SubscribeMessage('new-direction') handleMessage(client: Socket, payload: { routeId: string }) { this.kafkaProducer.send({ topic: 'route.new-direction', messages: [ { key: 'route.new-direction', value: JSON.stringify({ routeId: payload.routeId, clientId: client.id, }), }, ], }); console.log(payload); } sendPosition(data: { clientId: string; routeId: string; position: [number, number]; finished: boolean; }) { const { clientId, ...rest } = data; const clients = this.server.sockets.connected; if (!(clientId in clients)) { console.error( 'Client not exists, refresh React Application and resend new direction again.', ); return; } clients[clientId].emit('new-position', rest); } } ================================================ FILE: nest-api/src/routes/routes.module.ts ================================================ import { Module } from '@nestjs/common'; import { RoutesService } from './routes.service'; import { RoutesController } from './routes.controller'; import { MongooseModule } from '@nestjs/mongoose'; import { Route, RouteSchema } from './entities/route.entity'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { RoutesGateway } from './routes.gateway'; @Module({ imports: [ MongooseModule.forFeature([{ name: Route.name, schema: RouteSchema }]), ClientsModule.registerAsync([ { name: 'KAFKA_SERVICE', useFactory: (): any => ({ transport: Transport.KAFKA, options: { client: { clientId: process.env.KAFKA_CLIENT_ID, brokers: [process.env.KAFKA_BROKER], // ssl: true, // sasl: { // mechanism: 'plain', // scram-sha-256 or scram-sha-512 // username: process.env.KAFKA_SASL_USERNAME, // password: process.env.KAFKA_SASL_PASSWORD, // }, }, consumer: { groupId: !process.env.KAFKA_CONSUMER_GROUP_ID || process.env.KAFKA_CONSUMER_GROUP_ID === '' ? 'my-consumer-' + Math.random() : process.env.KAFKA_CONSUMER_GROUP_ID, }, }, }), }, ]), ], controllers: [RoutesController], providers: [RoutesService, RoutesGateway], }) export class RoutesModule {} ================================================ FILE: nest-api/src/routes/routes.service.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { RoutesService } from './routes.service'; describe('RoutesService', () => { let service: RoutesService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [RoutesService], }).compile(); service = module.get(RoutesService); }); it('should be defined', () => { expect(service).toBeDefined(); }); }); ================================================ FILE: nest-api/src/routes/routes.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { CreateRouteDto } from './dto/create-route.dto'; import { UpdateRouteDto } from './dto/update-route.dto'; import { Route, RouteDocument } from './entities/route.entity'; @Injectable() export class RoutesService { constructor( @InjectModel(Route.name) private routeModel: Model, ) {} create(createRouteDto: CreateRouteDto) { return 'This action adds a new route'; } findAll(): Promise { return this.routeModel.find().exec(); } findOne(id: number) { return `This action returns a #${id} route`; } update(id: number, updateRouteDto: UpdateRouteDto) { return `This action updates a #${id} route`; } remove(id: number) { return `This action removes a #${id} route`; } } ================================================ FILE: nest-api/test/app.e2e-spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: nest-api/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: nest-api/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] } ================================================ FILE: nest-api/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true }, "include": [ "src" ], "exclude": [ "node_modules", "dist", ".docker" ] } ================================================ FILE: react-frontend/.docker/entrypoint.sh ================================================ #!/bin/bash if [ ! -f ".env" ]; then cp .env.example .env fi npm install npm start ================================================ FILE: react-frontend/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* .env .history/ ================================================ FILE: react-frontend/.vscode/settings.json ================================================ { "workbench.colorCustomizations": { "activityBar.activeBackground": "#2fcefa", "activityBar.activeBorder": "#da05ac", "activityBar.background": "#2fcefa", "activityBar.foreground": "#15202b", "activityBar.inactiveForeground": "#15202b99", "activityBarBadge.background": "#da05ac", "activityBarBadge.foreground": "#e7e7e7", "statusBar.background": "#2fcefa", "statusBar.foreground": "#15202b", "statusBarItem.hoverBackground": "#06bdf0" }, "peacock.remoteColor": "#61dafb" } ================================================ FILE: react-frontend/Dockerfile ================================================ FROM node:12.14.0-alpine3.11 RUN apk add --no-cache bash USER node WORKDIR /home/node/app ================================================ FILE: react-frontend/Dockerfile.prod ================================================ #build FROM node:14.15.4-slim as build WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build #production FROM nginx:1.15 COPY --from=build /app/build /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ================================================ FILE: react-frontend/README.md ================================================ # Imersão Full Stack & FullCycle - Codelivery ## Descrição Repositório do front-end feito com React.js (Front-end) **Importante**: A aplicação do Apache Kafka, Golang e Nest.js deve estar rodando primeiro. ## Configurar /etc/hosts A comunicação entre as aplicações se dá de forma direta através da rede da máquina. Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): ``` 127.0.0.1 host.docker.internal ``` Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. ## Rodar a aplicação Execute os comandos: ``` docker-compose up ``` Acessar http://localhost:3001. ### Para Windows Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=usF0rYCcj-E](https://www.youtube.com/watch?v=usF0rYCcj-E) Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) ================================================ FILE: react-frontend/docker-compose.yaml ================================================ version: '3' services: app: build: . entrypoint: ./.docker/entrypoint.sh ports: - 3001:3000 volumes: - .:/home/node/app ================================================ FILE: react-frontend/package.json ================================================ { "name": "react-frontend", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", "@types/jest": "^26.0.22", "@types/lodash": "^4.14.168", "@types/node": "^12.20.7", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", "@types/socket.io-client": "^1.4.36", "google-maps": "^4.3.3", "notistack": "^1.0.5", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", "socket.io-client": "^2.4.0", "typescript": "^4.2.3", "web-vitals": "^1.1.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: react-frontend/public/index.html ================================================ Codelivery - Imersão Fullcycle
================================================ FILE: react-frontend/public/manifest.json ================================================ { "short_name": "Codelivery - Imersão Fullcycle", "name": "Codelivery - Imersão Fullcycle", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: react-frontend/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: react-frontend/src/App.tsx ================================================ import { CssBaseline, MuiThemeProvider } from "@material-ui/core"; import { SnackbarProvider } from "notistack"; import { Mapping } from "./components/Mapping"; import theme from "./theme"; function App() { return ( ); } export default App; //TS + JSX ================================================ FILE: react-frontend/src/components/Mapping.tsx ================================================ import { Button, Grid, makeStyles, MenuItem, Select } from "@material-ui/core"; import { Loader } from "google-maps"; import { FormEvent, FunctionComponent, useCallback, useEffect, useRef, useState, } from "react"; import { getCurrentPosition } from "../util/geolocation"; import { makeCarIcon, makeMarkerIcon, Map } from "../util/map"; import { Route } from "../util/models"; import { sample, shuffle } from "lodash"; import { RouteExistsError } from "../errors/route-exists.error"; import { useSnackbar } from "notistack"; import { Navbar } from "./Navbar"; import io from "socket.io-client"; const API_URL = process.env.REACT_APP_API_URL as string; const googleMapsLoader = new Loader(process.env.REACT_APP_GOOGLE_API_KEY); const colors = [ "#b71c1c", "#4a148c", "#2e7d32", "#e65100", "#2962ff", "#c2185b", "#FFCD00", "#3e2723", "#03a9f4", "#827717", ]; const useStyles = makeStyles({ root: { width: "100%", height: "100%", }, form: { margin: "16px", }, btnSubmitWrapper: { textAlign: "center", marginTop: "8px", }, map: { width: "100%", height: "100%", }, }); export const Mapping: FunctionComponent = () => { const classes = useStyles(); const [routes, setRoutes] = useState([]); const [routeIdSelected, setRouteIdSelected] = useState(""); const mapRef = useRef(); const socketIORef = useRef(); const { enqueueSnackbar } = useSnackbar(); const finishRoute = useCallback( (route: Route) => { enqueueSnackbar(`${route.title} finalizou!`, { variant: "success", }); mapRef.current?.removeRoute(route._id); }, [enqueueSnackbar] ); useEffect(() => { if (!socketIORef.current?.connected) { socketIORef.current = io.connect(API_URL); socketIORef.current.on("connect", () => console.log("conectou")); } const handler = (data: { routeId: string; position: [number, number]; finished: boolean; }) => { console.log(data); mapRef.current?.moveCurrentMarker(data.routeId, { lat: data.position[0], lng: data.position[1], }); const route = routes.find((route) => route._id === data.routeId) as Route; if (data.finished) { finishRoute(route); } }; socketIORef.current?.on("new-position", handler); return () => { socketIORef.current?.off("new-position", handler); }; }, [finishRoute, routes, routeIdSelected]); useEffect(() => { fetch(`${API_URL}/routes`) .then((data) => data.json()) .then((data) => setRoutes(data)); }, []); useEffect(() => { (async () => { const [, position] = await Promise.all([ googleMapsLoader.load(), getCurrentPosition({ enableHighAccuracy: true }), ]); const divMap = document.getElementById("map") as HTMLElement; mapRef.current = new Map(divMap, { zoom: 15, center: position, }); })(); }, []); const startRoute = useCallback( (event: FormEvent) => { event.preventDefault(); const route = routes.find((route) => route._id === routeIdSelected); const color = sample(shuffle(colors)) as string; try { mapRef.current?.addRoute(routeIdSelected, { currentMarkerOptions: { position: route?.startPosition, icon: makeCarIcon(color), }, endMarkerOptions: { position: route?.endPosition, icon: makeMarkerIcon(color), }, }); socketIORef.current?.emit("new-direction", { routeId: routeIdSelected, }); } catch (error) { if (error instanceof RouteExistsError) { enqueueSnackbar(`${route?.title} já adicionado, espere finalizar.`, { variant: "error", }); return; } throw error; } }, [routeIdSelected, routes, enqueueSnackbar] ); return (
); }; ================================================ FILE: react-frontend/src/components/Navbar.tsx ================================================ import { AppBar, IconButton, Toolbar, Typography } from "@material-ui/core"; import { FunctionComponent } from "react"; import DriverIcon from "@material-ui/icons/DriveEta"; export const Navbar: FunctionComponent = () => { return ( Code Delivery ); }; ================================================ FILE: react-frontend/src/errors/route-exists.error.ts ================================================ export class RouteExistsError extends Error {} ================================================ FILE: react-frontend/src/index.css ================================================ html, body, #root{ height: 100%; margin: 0; } ================================================ FILE: react-frontend/src/index.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; //ES6 Modules import reportWebVitals from './reportWebVitals'; ReactDOM.render( , document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); ================================================ FILE: react-frontend/src/react-app-env.d.ts ================================================ /// ================================================ FILE: react-frontend/src/reportWebVitals.ts ================================================ import { ReportHandler } from 'web-vitals'; const reportWebVitals = (onPerfEntry?: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); getLCP(onPerfEntry); getTTFB(onPerfEntry); }); } }; export default reportWebVitals; ================================================ FILE: react-frontend/src/setupTests.ts ================================================ // jest-dom adds custom jest matchers for asserting on DOM nodes. // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; ================================================ FILE: react-frontend/src/theme.ts ================================================ import { createMuiTheme } from "@material-ui/core"; import { PaletteOptions } from "@material-ui/core/styles/createPalette"; const palette: PaletteOptions = { type: "dark", primary: { main: "#FFCD00", contrastText: "#242526", }, background: { default: "#242526", }, }; const theme = createMuiTheme({ palette, }); export default theme; ================================================ FILE: react-frontend/src/util/geolocation.ts ================================================ export function getCurrentPosition( options?: PositionOptions ): Promise<{ lat: number; lng: number }> { return new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition( (position) => resolve({ lat: position.coords.latitude, lng: position.coords.longitude, }), (error) => reject(error), options ); }); } ================================================ FILE: react-frontend/src/util/map.ts ================================================ import { RouteExistsError } from "../errors/route-exists.error"; export class Route { public currentMarker: google.maps.Marker; public endMarker: google.maps.Marker; private directionsRenderer: google.maps.DirectionsRenderer; constructor(options: { currentMarkerOptions: google.maps.ReadonlyMarkerOptions; endMarkerOptions: google.maps.ReadonlyMarkerOptions; }) { const { currentMarkerOptions, endMarkerOptions } = options; this.currentMarker = new google.maps.Marker(currentMarkerOptions); this.endMarker = new google.maps.Marker(endMarkerOptions); const strokeColor = (this.currentMarker.getIcon() as google.maps.ReadonlySymbol) .strokeColor; this.directionsRenderer = new google.maps.DirectionsRenderer({ suppressMarkers: true, polylineOptions: { strokeColor, strokeOpacity: 0.5, strokeWeight: 5, }, }); this.directionsRenderer.setMap( this.currentMarker.getMap() as google.maps.Map ); this.calculateRoute(); } private calculateRoute() { const currentPosition = this.currentMarker.getPosition() as google.maps.LatLng; const endPosition = this.endMarker.getPosition() as google.maps.LatLng; new google.maps.DirectionsService().route( { origin: currentPosition, destination: endPosition, travelMode: google.maps.TravelMode.DRIVING, }, (result, status) => { if (status === "OK") { this.directionsRenderer.setDirections(result); return; } throw new Error(status); } ); } delete() { this.currentMarker.setMap(null); this.endMarker.setMap(null); this.directionsRenderer.setMap(null); } } export class Map { public map: google.maps.Map; private routes: { [id: string]: Route } = {}; constructor(element: Element, options: google.maps.MapOptions) { this.map = new google.maps.Map(element, options); } moveCurrentMarker(id: string, position: google.maps.LatLngLiteral) { this.routes[id].currentMarker.setPosition(position); } removeRoute(id: string) { const route = this.routes[id]; route.delete(); delete this.routes[id]; } addRoute( id: string, routeOptions: { currentMarkerOptions: google.maps.ReadonlyMarkerOptions; endMarkerOptions: google.maps.ReadonlyMarkerOptions; } ) { if (id in this.routes) { throw new RouteExistsError(); } const { currentMarkerOptions, endMarkerOptions } = routeOptions; this.routes[id] = new Route({ currentMarkerOptions: { ...currentMarkerOptions, map: this.map }, endMarkerOptions: { ...endMarkerOptions, map: this.map }, }); this.fitBounds(); } private fitBounds() { const bounds = new google.maps.LatLngBounds(); Object.keys(this.routes).forEach((id: string) => { const route = this.routes[id]; bounds.extend(route.currentMarker.getPosition() as any); bounds.extend(route.endMarker.getPosition() as any); }); this.map.fitBounds(bounds); } } export const makeCarIcon = (color: string) => ({ path: "M23.5 7c.276 0 .5.224.5.5v.511c0 .793-.926.989-1.616.989l-1.086-2h2.202zm-1.441 3.506c.639 1.186.946 2.252.946 3.666 0 1.37-.397 2.533-1.005 3.981v1.847c0 .552-.448 1-1 1h-1.5c-.552 0-1-.448-1-1v-1h-13v1c0 .552-.448 1-1 1h-1.5c-.552 0-1-.448-1-1v-1.847c-.608-1.448-1.005-2.611-1.005-3.981 0-1.414.307-2.48.946-3.666.829-1.537 1.851-3.453 2.93-5.252.828-1.382 1.262-1.707 2.278-1.889 1.532-.275 2.918-.365 4.851-.365s3.319.09 4.851.365c1.016.182 1.45.507 2.278 1.889 1.079 1.799 2.101 3.715 2.93 5.252zm-16.059 2.994c0-.828-.672-1.5-1.5-1.5s-1.5.672-1.5 1.5.672 1.5 1.5 1.5 1.5-.672 1.5-1.5zm10 1c0-.276-.224-.5-.5-.5h-7c-.276 0-.5.224-.5.5s.224.5.5.5h7c.276 0 .5-.224.5-.5zm2.941-5.527s-.74-1.826-1.631-3.142c-.202-.298-.515-.502-.869-.566-1.511-.272-2.835-.359-4.441-.359s-2.93.087-4.441.359c-.354.063-.667.267-.869.566-.891 1.315-1.631 3.142-1.631 3.142 1.64.313 4.309.497 6.941.497s5.301-.184 6.941-.497zm2.059 4.527c0-.828-.672-1.5-1.5-1.5s-1.5.672-1.5 1.5.672 1.5 1.5 1.5 1.5-.672 1.5-1.5zm-18.298-6.5h-2.202c-.276 0-.5.224-.5.5v.511c0 .793.926.989 1.616.989l1.086-2z", fillColor: color, strokeColor: color, strokeWeight: 1, fillOpacity: 1, anchor: new google.maps.Point(26, 20), }); export const makeMarkerIcon = (color: string) => ({ path: "M66.9,41.8c0-11.3-9.1-20.4-20.4-20.4c-11.3,0-20.4,9.1-20.4,20.4c0,11.3,20.4,32.4,20.4,32.4S66.9,53.1,66.9,41.8z M37,41.4c0-5.2,4.3-9.5,9.5-9.5c5.2,0,9.5,4.2,9.5,9.5c0,5.2-4.2,9.5-9.5,9.5C41.3,50.9,37,46.6,37,41.4z", strokeColor: color, fillColor: color, strokeOpacity: 1, strokeWeight: 1, fillOpacity: 1, anchor: new google.maps.Point(46, 70), }); ================================================ FILE: react-frontend/src/util/models.ts ================================================ export interface Position { lat: number; lng: number; } export interface Route { _id: string; title: string; startPosition: Position; endPosition: Position; } ================================================ FILE: react-frontend/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": [ "src" ] } ================================================ FILE: simulator/Dockerfile ================================================ FROM golang:1.16 WORKDIR /go/src ENV PATH="/go/bin:${PATH}" RUN apt-get update && \ apt-get install build-essential librdkafka-dev -y CMD ["tail", "-f", "/dev/null"] ================================================ FILE: simulator/Dockerfile.prod ================================================ FROM golang:1.16 WORKDIR /go/src ENV PATH="/go/bin:${PATH}" RUN apt-get update && \ apt-get install build-essential librdkafka-dev -y COPY . . RUN GOOS=linux go build -ldflags="-s -w" -o simulator ENTRYPOINT ["./simulator"] ================================================ FILE: simulator/README.md ================================================ # Imersão Full Stack & FullCycle - Codelivery ## Descrição Repositório do front-end feito com Golang (Backend) **Importante**: A aplicação do Apache Kafka deve estar rodando primeiro. ## Configurar /etc/hosts A comunicação entre as aplicações se dá de forma direta através da rede da máquina. Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): ``` 127.0.0.1 host.docker.internal ``` Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. ## Rodar a aplicação Execute os comandos: ``` docker-compose up -d # Entrar no container docker-compose exec app bash # Rodar a aplicação Golang go run main.go ``` ### Para Windows Lembrar de instalar o WSL2 e Docker. Vejo o vídeo: [https://www.youtube.com/watch?v=usF0rYCcj-E](https://www.youtube.com/watch?v=usF0rYCcj-E) Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) ================================================ FILE: simulator/application/kafka/produce.go ================================================ package kafka import ( "encoding/json" route2 "github.com/codeedu/imersaofsfc2-simulator/application/route" "github.com/codeedu/imersaofsfc2-simulator/infra/kafka" ckafka "github.com/confluentinc/confluent-kafka-go/kafka" "log" "os" "time" ) // Produce is responsible to publish the positions of each request // Example of a json request: //{"clientId":"1","routeId":"1"} //{"clientId":"2","routeId":"2"} //{"clientId":"3","routeId":"3"} func Produce(msg *ckafka.Message) { producer := kafka.NewKafkaProducer() route := route2.NewRoute() json.Unmarshal(msg.Value, &route) route.LoadPositions() positions, err := route.ExportJsonPositions() if err != nil { log.Println(err.Error()) } for _, p := range positions { kafka.Publish(p, os.Getenv("KafkaProduceTopic"), producer) time.Sleep(time.Millisecond * 500) } } ================================================ FILE: simulator/application/route/route.go ================================================ package route import ( "bufio" "encoding/json" "errors" "os" "strconv" "strings" ) // Route represents a request of new delivery request type Route struct { ID string `json:"routeId"` ClientID string `json:"clientId"` Positions []Position `json:"position"` } // Position is a type which contains the lat and long type Position struct { Lat float64 `json:"lat"` Long float64 `json:"long"` } // PartialRoutePosition is the actual response which the system will return type PartialRoutePosition struct { ID string `json:"routeId"` ClientID string `json:"clientId"` Position []float64 `json:"position"` Finished bool `json:"finished"` } // NewRoute creates a *Route struct func NewRoute() *Route { return &Route{} } // LoadPositions loads from a .txt file all positions (lat and long) to the Position attribute of the struct func (r *Route) LoadPositions() error { if r.ID == "" { return errors.New("route id not informed") } f, err := os.Open("destinations/" + r.ID + ".txt") if err != nil { return err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { data := strings.Split(scanner.Text(), ",") lat, err := strconv.ParseFloat(data[1], 64) if err != nil { return nil } long, err := strconv.ParseFloat(data[0], 64) if err != nil { return nil } r.Positions = append(r.Positions, Position{ Lat: lat, Long: long, }) } return nil } // ExportJsonPositions generates a slice of string in Json using PartialRoutePosition struct func (r *Route) ExportJsonPositions() ([]string, error) { var route PartialRoutePosition var result []string total := len(r.Positions) for k, v := range r.Positions { route.ID = r.ID route.ClientID = r.ClientID route.Position = []float64{v.Lat, v.Long} route.Finished = false if total-1 == k { route.Finished = true } jsonRoute, err := json.Marshal(route) if err != nil { return nil, err } result = append(result, string(jsonRoute)) } return result, nil } ================================================ FILE: simulator/destinations/1.txt ================================================ -15.82594,-47.92923 -15.8261,-47.92911 -15.82615,-47.92907 -15.82637,-47.92889 -15.82651,-47.92878 -15.82655,-47.92875 -15.82665,-47.92867 -15.82636,-47.92827 -15.82615,-47.92798 -15.82651,-47.9277 -15.82658,-47.92765 -15.82701,-47.92732 -15.82733,-47.92706 -15.82749,-47.92694 -15.8272,-47.92653 -15.82717,-47.9265 -15.82686,-47.92607 -15.82683,-47.92603 -15.82678,-47.92596 -15.82676,-47.92594 -15.82671,-47.92586 -15.82666,-47.92578 -15.82659,-47.92569 -15.82644,-47.92548 -15.8262,-47.92515 -15.82614,-47.92506 -15.82596,-47.92482 -15.8259,-47.92474 -15.82587,-47.9247 -15.82579,-47.92457 -15.82568,-47.92442 -15.8261,-47.92412 -15.82642,-47.92387 -15.82643,-47.92386 -15.82644,-47.92385 -15.82645,-47.92384 -15.82647,-47.92382 -15.82648,-47.9238 -15.82648,-47.92378 -15.82649,-47.92376 -15.82649,-47.92375 -15.8265,-47.92373 -15.8265,-47.92372 -15.8265,-47.9237 -15.8265,-47.92369 -15.82649,-47.92363 -15.8265,-47.92352 -15.8265,-47.92351 -15.8265,-47.92349 -15.82651,-47.92348 -15.82651,-47.92346 -15.82652,-47.92345 -15.82652,-47.92344 -15.82653,-47.92342 -15.82653,-47.92341 -15.82654,-47.9234 -15.82655,-47.92339 -15.82656,-47.92338 -15.82663,-47.92334 -15.8267,-47.92329 -15.82685,-47.92317 -15.827,-47.92305 -15.82711,-47.92298 -15.82721,-47.92291 -15.82731,-47.92283 -15.82741,-47.92274 -15.8275,-47.92267 -15.82758,-47.9226 -15.82761,-47.92257 -15.82764,-47.92255 -15.82775,-47.92246 -15.82787,-47.92238 -15.8281,-47.92222 -15.8281,-47.92223 -15.82811,-47.92223 -15.82812,-47.92224 -15.82812,-47.92225 -15.82813,-47.92225 -15.82814,-47.92226 -15.82815,-47.92226 -15.82816,-47.92227 -15.82817,-47.92227 -15.82818,-47.92228 -15.82819,-47.92228 -15.8282,-47.92228 -15.82821,-47.92228 -15.82822,-47.92228 -15.82823,-47.92228 -15.82824,-47.92227 -15.82825,-47.92227 -15.82826,-47.92227 -15.82826,-47.92226 -15.82827,-47.92226 -15.82828,-47.92225 -15.82847,-47.92251 -15.82853,-47.9226 -15.82923,-47.92354 -15.82935,-47.92373 -15.82947,-47.92391 -15.82947,-47.92392 -15.82946,-47.92393 -15.82946,-47.92394 -15.82946,-47.92395 -15.82945,-47.92396 -15.82945,-47.92397 -15.82945,-47.92398 -15.82946,-47.92399 -15.82946,-47.924 -15.82946,-47.92401 -15.82946,-47.92402 -15.82947,-47.92402 -15.82947,-47.92403 -15.82948,-47.92404 -15.82949,-47.92405 -15.8295,-47.92405 -15.8295,-47.92406 -15.82951,-47.92406 -15.82952,-47.92407 -15.82953,-47.92407 -15.82954,-47.92407 -15.82955,-47.92407 -15.82956,-47.92407 -15.82964,-47.92423 -15.82974,-47.92444 -15.82979,-47.9245 -15.82998,-47.92475 -15.82997,-47.92477 -15.82997,-47.92478 -15.82996,-47.92479 -15.82996,-47.9248 -15.82996,-47.92482 -15.82996,-47.92483 -15.82996,-47.92484 -15.82996,-47.92486 -15.82996,-47.92487 -15.82997,-47.92488 -15.82997,-47.92489 -15.82998,-47.9249 -15.82999,-47.92492 -15.82999,-47.92493 -15.83,-47.92494 -15.83001,-47.92495 -15.83002,-47.92496 -15.83003,-47.92497 -15.83004,-47.92497 -15.83005,-47.92498 -15.83006,-47.92499 -15.83008,-47.92499 -15.83009,-47.92499 -15.8301,-47.925 -15.83011,-47.925 -15.83013,-47.925 -15.83015,-47.925 -15.83022,-47.92511 -15.83034,-47.92529 -15.83041,-47.92536 -15.83047,-47.92543 -15.83063,-47.92564 -15.83063,-47.92565 -15.83062,-47.92565 -15.83062,-47.92566 -15.83061,-47.92566 -15.83061,-47.92567 -15.83061,-47.92568 -15.8306,-47.92568 -15.8306,-47.92569 -15.8306,-47.9257 -15.8306,-47.92571 -15.8306,-47.92572 -15.8306,-47.92573 -15.83061,-47.92573 -15.83061,-47.92574 -15.83061,-47.92575 -15.83062,-47.92575 -15.83062,-47.92576 -15.83063,-47.92576 -15.83063,-47.92577 -15.83064,-47.92577 -15.83068,-47.92593 -15.83068,-47.92596 -15.83068,-47.92599 -15.83065,-47.92606 -15.83044,-47.92624 -15.83041,-47.92626 -15.83038,-47.92628 -15.83035,-47.92631 -15.83033,-47.92634 -15.8303,-47.92636 -15.83029,-47.92637 -15.83028,-47.9264 -15.83027,-47.92642 -15.83025,-47.92644 -15.83024,-47.92646 -15.83024,-47.92648 -15.83023,-47.9265 -15.83022,-47.92651 -15.8302,-47.92656 -15.83018,-47.92661 -15.83015,-47.92665 -15.83012,-47.9267 -15.8301,-47.92674 -15.83006,-47.9268 -15.83003,-47.92684 -15.83001,-47.92686 -15.83,-47.92688 -15.82998,-47.9269 -15.82995,-47.92692 -15.82993,-47.92694 -15.82991,-47.92696 -15.82988,-47.92698 -15.82985,-47.92701 -15.82983,-47.92703 -15.8298,-47.92705 -15.82979,-47.92706 -15.82966,-47.92716 -15.82972,-47.92723 -15.8298,-47.92735 -15.82966,-47.92746 -15.82942,-47.92765 ================================================ FILE: simulator/destinations/2.txt ================================================ -15.82449,-47.92756 -15.82404,-47.9269 -15.82397,-47.9268 -15.82386,-47.92661 -15.82418,-47.92636 -15.8243,-47.92627 -15.82436,-47.92623 -15.82441,-47.92619 -15.82452,-47.92611 -15.82465,-47.92601 -15.82478,-47.9259 -15.825,-47.9257 -15.82501,-47.92573 -15.82527,-47.92552 -15.82537,-47.92546 -15.82546,-47.92538 -15.82554,-47.92532 -15.82568,-47.92522 -15.8257,-47.9252 -15.82588,-47.92501 -15.82601,-47.9252 -15.82636,-47.92569 -15.82648,-47.92584 -15.82651,-47.92589 -15.82656,-47.92596 -15.8267,-47.92615 -15.82733,-47.92706 -15.82759,-47.9274 -15.82807,-47.92805 -15.82836,-47.92845 -15.82843,-47.92839 -15.82851,-47.92833 -15.82825,-47.92797 -15.82796,-47.92759 -15.82773,-47.92727 -15.82795,-47.92712 -15.82804,-47.92703 -15.82814,-47.92695 -15.82812,-47.92692 -15.8276,-47.92621 ================================================ FILE: simulator/destinations/3.txt ================================================ -15.82331,-47.92588 -15.82327,-47.92584 -15.82306,-47.92553 -15.82284,-47.92522 -15.82281,-47.92519 -15.82277,-47.92513 -15.82271,-47.92504 -15.82262,-47.92492 -15.8225,-47.92476 -15.82235,-47.92454 -15.8219,-47.92394 -15.82185,-47.92387 -15.82174,-47.92372 -15.82164,-47.92357 -15.82152,-47.92342 -15.82105,-47.92377 -15.8211,-47.92384 -15.82119,-47.92395 -15.82127,-47.92406 -15.82133,-47.92413 -15.82136,-47.92419 -15.82159,-47.92451 -15.82186,-47.92488 -15.82188,-47.92491 -15.82196,-47.92502 -15.82208,-47.92519 -15.82218,-47.92533 -15.82229,-47.92548 -15.82234,-47.92555 -15.82237,-47.9256 -15.82241,-47.92564 -15.82261,-47.92592 -15.82276,-47.92612 -15.82278,-47.92615 -15.82303,-47.9265 -15.82311,-47.92663 -15.82314,-47.92668 -15.8232,-47.92687 -15.8232,-47.92688 -15.8232,-47.92689 -15.8232,-47.9269 -15.8232,-47.92691 -15.8232,-47.92692 -15.8232,-47.92693 -15.8232,-47.92694 -15.8232,-47.92695 -15.82321,-47.92696 -15.82321,-47.92697 -15.82322,-47.92698 -15.82322,-47.92699 -15.82323,-47.927 -15.82323,-47.92701 -15.82324,-47.92701 -15.82325,-47.92702 -15.82325,-47.92703 -15.82326,-47.92703 -15.82327,-47.92704 -15.82328,-47.92704 -15.82329,-47.92705 -15.8233,-47.92705 -15.82331,-47.92705 -15.82333,-47.92706 -15.82335,-47.92706 -15.82336,-47.92705 -15.82337,-47.92705 -15.82338,-47.92705 -15.82339,-47.92705 -15.8234,-47.92704 -15.82341,-47.92703 -15.82342,-47.92703 -15.82343,-47.92702 -15.82343,-47.92701 -15.82344,-47.927 -15.82345,-47.92699 -15.82345,-47.92698 -15.82346,-47.92697 -15.82346,-47.92696 -15.82346,-47.92695 -15.82346,-47.92694 -15.82347,-47.92694 -15.82347,-47.92693 -15.82375,-47.92669 -15.8238,-47.92665 -15.82386,-47.92661 -15.82418,-47.92636 -15.8243,-47.92627 -15.82436,-47.92623 -15.82441,-47.92619 -15.82452,-47.92611 -15.82465,-47.92601 -15.82478,-47.9259 -15.825,-47.9257 -15.82501,-47.92573 -15.82527,-47.92552 -15.82537,-47.92546 -15.82546,-47.92538 -15.82554,-47.92532 -15.82568,-47.92522 -15.8257,-47.9252 -15.82588,-47.92501 -15.82596,-47.92482 -15.8259,-47.92474 -15.82587,-47.9247 -15.82579,-47.92457 -15.82568,-47.92442 -15.8261,-47.92412 -15.82642,-47.92387 -15.82643,-47.92386 -15.82644,-47.92385 -15.82645,-47.92384 -15.82647,-47.92382 -15.82648,-47.9238 -15.82648,-47.92378 -15.82649,-47.92376 -15.82649,-47.92375 -15.8265,-47.92373 -15.8265,-47.92372 -15.8265,-47.9237 -15.8265,-47.92369 -15.82649,-47.92363 -15.8265,-47.92352 -15.8265,-47.92351 -15.8265,-47.92349 -15.82651,-47.92348 -15.82651,-47.92346 -15.82652,-47.92345 -15.82652,-47.92344 -15.82653,-47.92342 -15.82653,-47.92341 -15.82654,-47.9234 -15.82655,-47.92339 -15.82656,-47.92338 -15.82663,-47.92334 -15.8267,-47.92329 -15.82685,-47.92317 -15.827,-47.923 -15.82711,-47.92298 -15.82721,-47.92291 -15.82731,-47.92283 -15.82741,-47.92274 -15.8275,-47.92267 -15.82758,-47.9226 -15.82761,-47.92257 -15.82764,-47.92255 -15.82775,-47.92246 -15.82787,-47.92238 -15.8281,-47.92222 -15.8281,-47.92223 -15.82811,-47.92223 -15.82812,-47.92224 -15.82812,-47.92225 -15.82813,-47.92225 -15.82814,-47.92226 -15.82815,-47.92226 -15.82816,-47.92227 -15.82817,-47.92227 -15.82818,-47.92228 -15.82819,-47.92228 -15.8282,-47.92228 -15.82821,-47.92228 -15.82822,-47.92228 -15.82823,-47.92228 -15.82824,-47.92227 -15.82825,-47.92227 -15.82826,-47.92227 -15.82826,-47.92226 -15.82827,-47.92226 -15.82828,-47.92225 -15.82847,-47.92251 -15.82853,-47.9226 -15.82923,-47.92354 -15.82935,-47.92373 -15.82947,-47.92391 -15.82947,-47.92392 -15.82946,-47.92393 -15.82946,-47.92394 -15.82946,-47.92395 -15.82945,-47.92396 -15.82945,-47.92397 -15.82938,-47.924 -15.82931,-47.92406 -15.82925,-47.92412 -15.82921,-47.92417 -15.82917,-47.92423 -15.82905,-47.92434 -15.82897,-47.9244 -15.82888,-47.92443 -15.82879,-47.92445 -15.8286,-47.92446 -15.8286,-47.92445 -15.8286,-47.92444 -15.8286,-47.92443 -15.82859,-47.92443 -15.82859,-47.92442 -15.82859,-47.92441 -15.82858,-47.92441 -15.82858,-47.9244 -15.82857,-47.9244 -15.82856,-47.9244 -15.82856,-47.92439 -15.82855,-47.92439 -15.82854,-47.92439 -15.82853,-47.92439 -15.82853,-47.9244 -15.82852,-47.9244 -15.82851,-47.9244 -15.82841,-47.92424 -15.82826,-47.92405 -15.82821,-47.92406 -15.82815,-47.92407 -15.8281,-47.92407 -15.828,-47.924 -15.82797,-47.92409 -15.82794,-47.92409 -15.8279,-47.9241 -15.82787,-47.92411 -15.82786,-47.92412 -15.82784,-47.92412 -15.82782,-47.92414 -15.82781,-47.92415 -15.82779,-47.92416 -15.82777,-47.92418 -15.82775,-47.92419 -15.82773,-47.92422 -15.82771,-47.92423 -15.8277,-47.92425 -15.82767,-47.92429 -15.82766,-47.9243 -15.82765,-47.92432 -15.82764,-47.92434 -15.82762,-47.92436 -15.82761,-47.92438 -15.8276,-47.9244 -15.82759,-47.92442 -15.82758,-47.92445 -15.82757,-47.92447 -15.82757,-47.92449 -15.82756,-47.92452 -15.82756,-47.92455 -15.82755,-47.92457 -15.82755,-47.92461 -15.82754,-47.92466 -15.82753,-47.92471 -15.82749,-47.92478 -15.82747,-47.92481 -15.82747,-47.92483 -15.82746,-47.92485 -15.82746,-47.92486 -15.82746,-47.92489 -15.82745,-47.92491 -15.82745,-47.92494 -15.82745,-47.92495 -15.82745,-47.92498 -15.82745,-47.92501 -15.82745,-47.92504 -15.82746,-47.92505 -15.82746,-47.92507 -15.82746,-47.9251 -15.82747,-47.92512 -15.82747,-47.92514 -15.82748,-47.92516 -15.8275,-47.92519 -15.82751,-47.92521 -15.82752,-47.92523 -15.82758,-47.92532 ================================================ FILE: simulator/docker-compose.yaml ================================================ version: "3" services: app: build: . container_name: simulator volumes: - .:/go/src/ extra_hosts: - "host.docker.internal:172.17.0.1" ================================================ FILE: simulator/go.mod ================================================ module github.com/codeedu/imersaofsfc2-simulator go 1.16 require ( github.com/confluentinc/confluent-kafka-go v1.6.1 github.com/joho/godotenv v1.3.0 ) ================================================ FILE: simulator/go.sum ================================================ github.com/confluentinc/confluent-kafka-go v1.6.1 h1:YxM/UtMQ2vgJX2gIgeJFUD0ANQYTEvfo4Cs4qKUlmGE= github.com/confluentinc/confluent-kafka-go v1.6.1/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= ================================================ FILE: simulator/infra/kafka/consumer.go ================================================ package kafka import ( "fmt" "log" "os" ckafka "github.com/confluentinc/confluent-kafka-go/kafka" ) // KafkaConsumer holds all consumer logic and settings of Apache Kafka connections/ // Also has a Message channel which is a channel where the messages are going to be pushed type KafkaConsumer struct { MsgChan chan *ckafka.Message } // NewKafkaConsumer creates a new KafkaConsumer struct with its message channel as dependency func NewKafkaConsumer(msgChan chan *ckafka.Message) *KafkaConsumer { return &KafkaConsumer{ MsgChan: msgChan, } } // Consume consumes all message pulled from apache kafka and sent it to message channel func (k *KafkaConsumer) Consume() { configMap := &ckafka.ConfigMap{ "bootstrap.servers": os.Getenv("KafkaBootstrapServers"), "group.id": os.Getenv("KafkaConsumerGroupId"), // "security.protocol": os.Getenv("security.protocol"), // "sasl.mechanisms": os.Getenv("sasl.mechanisms"), // "sasl.username": os.Getenv("sasl.username"), // "sasl.password": os.Getenv("sasl.password"), } c, err := ckafka.NewConsumer(configMap) if err != nil { log.Fatalf("error consuming kafka message:" + err.Error()) } topics := []string{os.Getenv("KafkaReadTopic")} c.SubscribeTopics(topics, nil) fmt.Println("Kafka consumer has been started") for { msg, err := c.ReadMessage(-1) if err == nil { k.MsgChan <- msg } } } ================================================ FILE: simulator/infra/kafka/producer.go ================================================ package kafka import ( "log" "os" ckafka "github.com/confluentinc/confluent-kafka-go/kafka" ) // NewKafkaProducer creates a ready to go kafka.Producer instance func NewKafkaProducer() *ckafka.Producer { configMap := &ckafka.ConfigMap{ "bootstrap.servers": os.Getenv("KafkaBootstrapServers"), // "security.protocol": os.Getenv("security.protocol"), // "sasl.mechanisms": os.Getenv("sasl.mechanisms"), // "sasl.username": os.Getenv("sasl.username"), // "sasl.password": os.Getenv("sasl.password"), } p, err := ckafka.NewProducer(configMap) if err != nil { log.Println(err.Error()) } return p } // Publish is simple function created to publish new message to kafka func Publish(msg string, topic string, producer *ckafka.Producer) error { message := &ckafka.Message{ TopicPartition: ckafka.TopicPartition{Topic: &topic, Partition: ckafka.PartitionAny}, Value: []byte(msg), } err := producer.Produce(message, nil) if err != nil { return err } return nil } ================================================ FILE: simulator/main.go ================================================ package main import ( "fmt" kafka2 "github.com/codeedu/imersaofsfc2-simulator/application/kafka" "github.com/codeedu/imersaofsfc2-simulator/infra/kafka" ckafka "github.com/confluentinc/confluent-kafka-go/kafka" "github.com/joho/godotenv" "log" ) func init() { err := godotenv.Load() if err != nil { log.Fatal("error loading .env file") } } func main() { msgChan := make(chan *ckafka.Message) consumer := kafka.NewKafkaConsumer(msgChan) go consumer.Consume() for msg := range msgChan { fmt.Println(string(msg.Value)) go kafka2.Produce(msg) } }