├── .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 ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode │ └── settings.json ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── api.http ├── docker-compose.yaml ├── nest-cli.json ├── package-lock.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 ├── .env.example ├── .gitignore ├── .vscode │ └── settings.json ├── Dockerfile ├── Dockerfile.prod ├── README.md ├── docker-compose.yaml ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── 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 ├── .env.example ├── 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 /.gitignore: -------------------------------------------------------------------------------- 1 | simulator/.idea 2 | simulator/.vscode 3 | simulator/.env 4 | .history/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Imersão Fullcycle 10 - Codelivery 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/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - Codelivery 2 | 3 | ## Descrição 4 | 5 | Repositório do Apache Kafka (Backend) 6 | 7 | ## Configurar /etc/hosts 8 | 9 | A comunicação entre as aplicações se dá de forma direta através da rede da máquina. 10 | Para isto é necessário configurar um endereços que todos os containers Docker consigam acessar. 11 | 12 | Acrescente no seu /etc/hosts (para Windows o caminho é C:\Windows\system32\drivers\etc\hosts): 13 | ``` 14 | 127.0.0.1 host.docker.internal 15 | ``` 16 | Em todos os sistemas operacionais é necessário abrir o programa para editar o *hosts* como Administrator da máquina ou root. 17 | 18 | ## Rodar a aplicação 19 | 20 | Execute os comandos: 21 | 22 | ``` 23 | docker-compose up 24 | ``` 25 | 26 | 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. 27 | 28 | ### Para Windows 29 | 30 | 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) 31 | 32 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 33 | -------------------------------------------------------------------------------- /apache-kafka/connectors/elasticsearch.properties: -------------------------------------------------------------------------------- 1 | name=elasticsearch-sink 2 | connector.class=io.confluent.connect.elasticsearch.ElasticsearchSinkConnector 3 | topics=route.new-direction,route.new-position 4 | connection.url=http://es01:9200 5 | type.name=_doc 6 | value.converter=org.apache.kafka.connect.json.JsonConverter 7 | value.converter.schemas.enable=false 8 | schema.ignore=true 9 | key.ignore=true 10 | transforms=InsertField 11 | transforms.InsertField.type=org.apache.kafka.connect.transforms.InsertField$Value 12 | transforms.InsertField.timestamp.field=timestamp -------------------------------------------------------------------------------- /apache-kafka/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | zookeeper: 6 | image: confluentinc/cp-zookeeper:latest 7 | environment: 8 | ZOOKEEPER_CLIENT_PORT: 2181 9 | extra_hosts: 10 | - "host.docker.internal:172.17.0.1" 11 | 12 | kafka: 13 | image: confluentinc/cp-kafka:latest 14 | depends_on: 15 | - zookeeper 16 | ports: 17 | - "9092:9092" 18 | - "9094:9094" 19 | environment: 20 | KAFKA_BROKER_ID: 1 21 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 22 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 23 | KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL 24 | KAFKA_LISTENERS: INTERNAL://:9092,OUTSIDE://:9094 25 | KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,OUTSIDE://host.docker.internal:9094 26 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,OUTSIDE:PLAINTEXT 27 | extra_hosts: 28 | - "host.docker.internal:172.17.0.1" 29 | 30 | kafka-topics-generator: 31 | image: confluentinc/cp-kafka:latest 32 | depends_on: 33 | - kafka 34 | command: > 35 | bash -c 36 | "sleep 5s && 37 | kafka-topics --create --topic=route.new-direction --if-not-exists --bootstrap-server=kafka:9092 && 38 | kafka-topics --create --topic=route.new-position --if-not-exists --bootstrap-server=kafka:9092" 39 | 40 | control-center: 41 | image: confluentinc/cp-enterprise-control-center:6.0.1 42 | hostname: control-center 43 | depends_on: 44 | - kafka 45 | ports: 46 | - "9021:9021" 47 | environment: 48 | CONTROL_CENTER_BOOTSTRAP_SERVERS: 'kafka:9092' 49 | CONTROL_CENTER_REPLICATION_FACTOR: 1 50 | CONTROL_CENTER_CONNECT_CLUSTER: http://kafka-connect:8083 51 | PORT: 9021 52 | extra_hosts: 53 | - "host.docker.internal:172.17.0.1" 54 | 55 | kafka-connect: 56 | image: confluentinc/cp-kafka-connect-base:6.0.0 57 | container_name: kafka-connect 58 | depends_on: 59 | - zookeeper 60 | - kafka 61 | ports: 62 | - 8083:8083 63 | environment: 64 | CONNECT_BOOTSTRAP_SERVERS: "kafka:9092" 65 | CONNECT_REST_PORT: 8083 66 | CONNECT_GROUP_ID: kafka-connect 67 | CONNECT_CONFIG_STORAGE_TOPIC: _connect-configs 68 | CONNECT_OFFSET_STORAGE_TOPIC: _connect-offsets 69 | CONNECT_STATUS_STORAGE_TOPIC: _connect-status 70 | CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter 71 | CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter 72 | CONNECT_INTERNAL_KEY_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" 73 | CONNECT_INTERNAL_VALUE_CONVERTER: "org.apache.kafka.connect.json.JsonConverter" 74 | CONNECT_REST_ADVERTISED_HOST_NAME: "kafka-connect" 75 | CONNECT_LOG4J_ROOT_LOGLEVEL: "INFO" 76 | CONNECT_LOG4J_LOGGERS: "org.apache.kafka.connect.runtime.rest=WARN,org.reflections=ERROR" 77 | CONNECT_LOG4J_APPENDER_STDOUT_LAYOUT_CONVERSIONPATTERN: "[%d] %p %X{connector.context}%m (%c:%L)%n" 78 | CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: "1" 79 | CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: "1" 80 | CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: "1" 81 | # # Optional settings to include to support Confluent Control Center 82 | # CONNECT_PRODUCER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor" 83 | # CONNECT_CONSUMER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor" 84 | # --------------- 85 | CONNECT_PLUGIN_PATH: /usr/share/java,/usr/share/confluent-hub-components,/data/connect-jars 86 | # If you want to use the Confluent Hub installer to d/l component, but make them available 87 | # when running this offline, spin up the stack once and then run : 88 | # docker cp kafka-connect:/usr/share/confluent-hub-components ./data/connect-jars 89 | volumes: 90 | - $PWD/data:/data 91 | # In the command section, $ are replaced with $$ to avoid the error 'Invalid interpolation format for "command" option' 92 | command: 93 | - bash 94 | - -c 95 | - | 96 | echo "Installing Connector" 97 | confluent-hub install --no-prompt confluentinc/kafka-connect-elasticsearch:10.0.1 98 | # 99 | echo "Launching Kafka Connect worker" 100 | /etc/confluent/docker/run & 101 | # 102 | sleep infinity 103 | extra_hosts: 104 | - "host.docker.internal:172.17.0.1" 105 | 106 | es01: 107 | image: docker.elastic.co/elasticsearch/elasticsearch:7.11.2 108 | container_name: es01 109 | environment: 110 | - node.name=es01 111 | - cluster.name=es-docker-cluster 112 | - cluster.initial_master_nodes=es01 113 | - bootstrap.memory_lock=true 114 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 115 | ulimits: 116 | memlock: 117 | soft: -1 118 | hard: -1 119 | volumes: 120 | - ./es01:/usr/share/elasticsearch/data 121 | ports: 122 | - 9200:9200 123 | extra_hosts: 124 | - "host.docker.internal:172.17.0.1" 125 | 126 | kibana: 127 | image: docker.elastic.co/kibana/kibana:7.11.2 128 | container_name: kib01 129 | ports: 130 | - 5601:5601 131 | environment: 132 | ELASTICSEARCH_URL: http://es01:9200 133 | ELASTICSEARCH_HOSTS: '["http://es01:9200"]' 134 | extra_hosts: 135 | - "host.docker.internal:172.17.0.1" -------------------------------------------------------------------------------- /k8s/backend/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: backend-conf 5 | data: 6 | env: | 7 | MONGO_DSN=mongodb://root:root@mongodb/nest?authSource=admin 8 | KAFKA_CLIENT_ID=code-delivery 9 | KAFKA_BROKER=pkc-lzvrd.us-west4.gcp.confluent.cloud:9092 10 | KAFKA_CONSUMER_GROUP_ID=code-delivery 11 | KAFKA_SASL_USERNAME=EBNIKUAMEB2PJ2TZ 12 | KAFKA_SASL_PASSWORD=59VVz1gr7l4tl4ikK4Lzlk/vLZB++7ek5vOC73cxtNsKLW7oUtbfxj/PicpbZ9rq -------------------------------------------------------------------------------- /k8s/backend/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backend 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: backend 10 | template: 11 | metadata: 12 | labels: 13 | app: backend 14 | spec: 15 | containers: 16 | - name: backend 17 | image: wesleywillians/imersao2-backend 18 | ports: 19 | - containerPort: 3000 20 | volumeMounts: 21 | - name: backend-volume 22 | mountPath: /home/node/app/.env 23 | subPath: .env 24 | volumes: 25 | - name: backend-volume 26 | configMap: 27 | name: backend-conf 28 | items: 29 | - key: env 30 | path: .env 31 | 32 | -------------------------------------------------------------------------------- /k8s/backend/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: backend-service 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: backend 9 | ports: 10 | - port: 3000 11 | 12 | -------------------------------------------------------------------------------- /k8s/frontend/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frontend 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: frontend 10 | template: 11 | metadata: 12 | labels: 13 | app: frontend 14 | spec: 15 | containers: 16 | - name: frontend 17 | image: wesleywillians/imersao2-frontend:latest 18 | ports: 19 | - containerPort: 80 20 | -------------------------------------------------------------------------------- /k8s/frontend/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend-service 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: frontend 9 | ports: 10 | - port: 80 11 | 12 | 13 | -------------------------------------------------------------------------------- /k8s/simulator/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: simulator-conf 5 | data: 6 | env: | 7 | KafkaReadTopic=route.new-direction 8 | KafkaProduceTopic=route.new-position 9 | KafkaBootstrapServers=pkc-lzvrd.us-west4.gcp.confluent.cloud:9092 10 | KafkaConsumerGroupId=simulator 11 | security.protocol="SASL_SSL" 12 | sasl.mechanisms="PLAIN" 13 | sasl.username="EBNIKUAMEB2PJ2TZ" 14 | sasl.password="59VVz1gr7l4tl4ikK4Lzlk/vLZB++7ek5vOC73cxtNsKLW7oUtbfxj/PicpbZ9rq" -------------------------------------------------------------------------------- /k8s/simulator/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: simulator 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: simulator 10 | template: 11 | metadata: 12 | labels: 13 | app: simulator 14 | spec: 15 | containers: 16 | - name: simulator 17 | image: wesleywillians/imersao2-simulator:latest 18 | volumeMounts: 19 | - name: simulator-volume 20 | mountPath: /go/src/.env 21 | subPath: .env 22 | 23 | 24 | volumes: 25 | - name: simulator-volume 26 | configMap: 27 | name: simulator-conf 28 | items: 29 | - key: env 30 | path: .env 31 | 32 | -------------------------------------------------------------------------------- /nest-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 | 9 | npm run start:dev -------------------------------------------------------------------------------- /nest-api/.docker/mongo/init.js: -------------------------------------------------------------------------------- 1 | db.routes.insertMany([ 2 | { 3 | _id: '1', 4 | title: 'Primeiro', 5 | startPosition: { lat: -15.82594, lng: -47.92923 }, 6 | endPosition: { lat: -15.82942, lng: -47.92765 }, 7 | }, 8 | { 9 | _id: '2', 10 | title: 'Segundo', 11 | startPosition: { lat: -15.82449, lng: -47.92756 }, 12 | endPosition: { lat: -15.8276, lng: -47.92621 }, 13 | }, 14 | { 15 | _id: '3', 16 | title: 'Terceiro', 17 | startPosition: { lat: -15.82331, lng: -47.92588 }, 18 | endPosition: { lat: -15.82758, lng: -47.92532 }, 19 | }, 20 | ]); 21 | -------------------------------------------------------------------------------- /nest-api/.env.example: -------------------------------------------------------------------------------- 1 | MONGO_DSN=mongodb://root:root@db/nest?authSource=admin 2 | KAFKA_CLIENT_ID=code-delivery 3 | KAFKA_BROKER=host.docker.internal:9094 4 | KAFKA_CONSUMER_GROUP_ID= 5 | 6 | KAFKA_SASL_USERNAME= 7 | KAFKA_SASL_PASSWORD= 8 | -------------------------------------------------------------------------------- /nest-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 | 'plugin:prettier/recommended', 11 | ], 12 | root: true, 13 | env: { 14 | node: true, 15 | jest: true, 16 | }, 17 | ignorePatterns: ['.eslintrc.js'], 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /nest-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 | .docker/dbdata/ 37 | 38 | .history/ 39 | 40 | .env -------------------------------------------------------------------------------- /nest-api/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /nest-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 | } -------------------------------------------------------------------------------- /nest-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.14.0-alpine3.11 2 | 3 | RUN apk add --no-cache bash 4 | 5 | RUN npm install -g @nestjs/cli@7.5.6 6 | 7 | USER node 8 | 9 | WORKDIR /home/node/app -------------------------------------------------------------------------------- /nest-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"] -------------------------------------------------------------------------------- /nest-api/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - Codelivery 2 | 3 | ## Descrição 4 | 5 | Repositório do front-end feito com Nest.js (Backend) 6 | 7 | **Importante**: A aplicação do Apache Kafka, 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 | ``` 25 | docker-compose up 26 | ``` 27 | 28 | Acessar http://localhost:3000/routes. 29 | 30 | ### Para Windows 31 | 32 | 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) 33 | 34 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 35 | -------------------------------------------------------------------------------- /nest-api/api.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/routes 2 | 3 | ### 4 | GET http://localhost:3000/routes/1/start -------------------------------------------------------------------------------- /nest-api/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | entrypoint: ./.docker/entrypoint.sh 8 | ports: 9 | - 3000:3000 10 | volumes: 11 | - .:/home/node/app 12 | extra_hosts: 13 | - "host.docker.internal:172.17.0.1" 14 | depends_on: 15 | - db 16 | 17 | db: 18 | image: mongo:4.4.4 19 | restart: always 20 | volumes: 21 | - ./.docker/dbdata:/data/db 22 | - ./.docker/mongo:/docker-entrypoint-initdb.d 23 | environment: 24 | - MONGO_INITDB_ROOT_USERNAME=root 25 | - MONGO_INITDB_ROOT_PASSWORD=root 26 | - MONGO_INITDB_DATABASE=nest 27 | 28 | mongo-express: 29 | image: mongo-express 30 | restart: always 31 | ports: 32 | - 8081:8081 33 | environment: 34 | - ME_CONFIG_MONGODB_SERVER=db 35 | - ME_CONFIG_MONGODB_AUTH_USERNAME=root 36 | - ME_CONFIG_MONGODB_AUTH_PASSWORD=root 37 | - ME_CONFIG_MONGODB_ADMINUSERNAME=root 38 | - ME_CONFIG_MONGODB_ADMINPASSWORD=root 39 | depends_on: 40 | - db 41 | -------------------------------------------------------------------------------- /nest-api/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /nest-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-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 | }, 23 | "dependencies": { 24 | "@nestjs/common": "^7.6.15", 25 | "@nestjs/config": "^0.6.3", 26 | "@nestjs/core": "^7.6.15", 27 | "@nestjs/mapped-types": "*", 28 | "@nestjs/microservices": "^7.6.15", 29 | "@nestjs/mongoose": "^7.2.4", 30 | "@nestjs/platform-express": "^7.6.15", 31 | "@nestjs/platform-socket.io": "^7.6.15", 32 | "@nestjs/websockets": "^7.6.15", 33 | "kafkajs": "^1.15.0", 34 | "mongoose": "^5.12.3", 35 | "reflect-metadata": "^0.1.13", 36 | "rimraf": "^3.0.2", 37 | "rxjs": "^6.6.6" 38 | }, 39 | "devDependencies": { 40 | "@nestjs/cli": "^7.6.0", 41 | "@nestjs/schematics": "^7.3.0", 42 | "@nestjs/testing": "^7.6.15", 43 | "@types/express": "^4.17.11", 44 | "@types/jest": "^26.0.22", 45 | "@types/node": "^14.14.36", 46 | "@types/socket.io": "^2.1.13", 47 | "@types/supertest": "^2.0.10", 48 | "@typescript-eslint/eslint-plugin": "^4.19.0", 49 | "@typescript-eslint/parser": "^4.19.0", 50 | "eslint": "^7.22.0", 51 | "eslint-config-prettier": "^8.1.0", 52 | "eslint-plugin-prettier": "^3.3.1", 53 | "jest": "^26.6.3", 54 | "prettier": "^2.2.1", 55 | "supertest": "^6.1.3", 56 | "ts-jest": "^26.5.4", 57 | "ts-loader": "^8.0.18", 58 | "ts-node": "^9.1.1", 59 | "tsconfig-paths": "^3.9.0", 60 | "typescript": "^4.2.3" 61 | }, 62 | "jest": { 63 | "moduleFileExtensions": [ 64 | "js", 65 | "json", 66 | "ts" 67 | ], 68 | "rootDir": "src", 69 | "testRegex": ".*\\.spec\\.ts$", 70 | "transform": { 71 | "^.+\\.(t|j)s$": "ts-jest" 72 | }, 73 | "collectCoverageFrom": [ 74 | "**/*.(t|j)s" 75 | ], 76 | "coverageDirectory": "../coverage", 77 | "testEnvironment": "node" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /nest-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 | -------------------------------------------------------------------------------- /nest-api/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller('/prefixo') 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | 14 | //HTTP - Get, Post, Put, Patch, Delete e -------------------------------------------------------------------------------- /nest-api/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule } from '@nestjs/config'; 3 | import { MongooseModule } from '@nestjs/mongoose'; 4 | import { AppController } from './app.controller'; 5 | import { AppService } from './app.service'; 6 | import { RoutesModule } from './routes/routes.module'; 7 | //ES7 decorators 8 | @Module({ 9 | imports: [ 10 | ConfigModule.forRoot(), 11 | RoutesModule, 12 | MongooseModule.forRoot(process.env.MONGO_DSN, { 13 | useNewUrlParser: true, 14 | }), 15 | ], 16 | controllers: [AppController], 17 | providers: [AppService], 18 | }) 19 | export class AppModule {} 20 | -------------------------------------------------------------------------------- /nest-api/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World222!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /nest-api/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { Transport } from '@nestjs/microservices'; 3 | import { AppModule } from './app.module'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule, { cors: true }); 7 | 8 | app.connectMicroservice({ 9 | transport: Transport.KAFKA, 10 | options: { 11 | client: { 12 | clientId: process.env.KAFKA_CLIENT_ID, 13 | brokers: [process.env.KAFKA_BROKER], 14 | // ssl: true, 15 | // sasl: { 16 | // mechanism: 'plain', // scram-sha-256 or scram-sha-512 17 | // username: process.env.KAFKA_SASL_USERNAME, 18 | // password: process.env.KAFKA_SASL_PASSWORD, 19 | // }, 20 | }, 21 | consumer: { 22 | groupId: 23 | !process.env.KAFKA_CONSUMER_GROUP_ID || 24 | process.env.KAFKA_CONSUMER_GROUP_ID === '' 25 | ? 'my-consumer-' + Math.random() 26 | : process.env.KAFKA_CONSUMER_GROUP_ID, 27 | }, 28 | }, 29 | }); 30 | 31 | await app.startAllMicroservicesAsync(); 32 | await app.listen(3000); 33 | } 34 | bootstrap(); 35 | -------------------------------------------------------------------------------- /nest-api/src/routes/dto/create-route.dto.ts: -------------------------------------------------------------------------------- 1 | export class CreateRouteDto {} 2 | -------------------------------------------------------------------------------- /nest-api/src/routes/dto/update-route.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/mapped-types'; 2 | import { CreateRouteDto } from './create-route.dto'; 3 | 4 | export class UpdateRouteDto extends PartialType(CreateRouteDto) {} 5 | -------------------------------------------------------------------------------- /nest-api/src/routes/entities/route.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, Schema, raw, SchemaFactory } from '@nestjs/mongoose'; 2 | import { Document } from 'mongoose'; 3 | 4 | export type RouteDocument = Route & Document; 5 | 6 | @Schema() 7 | export class Route { 8 | @Prop() 9 | _id: string; 10 | 11 | @Prop() 12 | title: string; 13 | 14 | @Prop( 15 | raw({ 16 | lat: { type: Number }, 17 | lng: { type: Number }, 18 | }), 19 | ) 20 | startPosition: { lat: number; lng: number }; 21 | 22 | @Prop( 23 | raw({ 24 | lat: { type: Number }, 25 | lng: { type: Number }, 26 | }), 27 | ) 28 | endPosition: { lat: number; lng: number }; 29 | } 30 | 31 | export const RouteSchema = SchemaFactory.createForClass(Route); 32 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RoutesController } from './routes.controller'; 3 | import { RoutesService } from './routes.service'; 4 | 5 | describe('RoutesController', () => { 6 | let controller: RoutesController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [RoutesController], 11 | providers: [RoutesService], 12 | }).compile(); 13 | 14 | controller = module.get(RoutesController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Patch, 7 | Param, 8 | Delete, 9 | Inject, 10 | OnModuleInit, 11 | } from '@nestjs/common'; 12 | import { RoutesService } from './routes.service'; 13 | import { CreateRouteDto } from './dto/create-route.dto'; 14 | import { UpdateRouteDto } from './dto/update-route.dto'; 15 | import { ClientKafka, MessagePattern, Payload } from '@nestjs/microservices'; 16 | import { Producer } from '@nestjs/microservices/external/kafka.interface'; 17 | import { RoutesGateway } from './routes.gateway'; 18 | 19 | @Controller('routes') 20 | export class RoutesController implements OnModuleInit { 21 | private kafkaProducer: Producer; 22 | 23 | constructor( 24 | private readonly routesService: RoutesService, 25 | @Inject('KAFKA_SERVICE') 26 | private kafkaClient: ClientKafka, 27 | private routeGateway: RoutesGateway, 28 | ) {} 29 | 30 | @Post() 31 | create(@Body() createRouteDto: CreateRouteDto) { 32 | return this.routesService.create(createRouteDto); 33 | } 34 | 35 | @Get() 36 | findAll() { 37 | return this.routesService.findAll(); 38 | } 39 | 40 | @Get(':id') 41 | findOne(@Param('id') id: string) { 42 | return this.routesService.findOne(+id); 43 | } 44 | 45 | @Patch(':id') 46 | update(@Param('id') id: string, @Body() updateRouteDto: UpdateRouteDto) { 47 | return this.routesService.update(+id, updateRouteDto); 48 | } 49 | 50 | @Delete(':id') 51 | remove(@Param('id') id: string) { 52 | return this.routesService.remove(+id); 53 | } 54 | 55 | async onModuleInit() { 56 | this.kafkaProducer = await this.kafkaClient.connect(); 57 | } 58 | 59 | @Get(':id/start') 60 | startRoute(@Param('id') id: string) { 61 | this.kafkaProducer.send({ 62 | topic: 'route.new-direction', 63 | messages: [ 64 | { 65 | key: 'route.new-direction', 66 | value: JSON.stringify({ routeId: id, clientId: '' }), 67 | }, 68 | ], 69 | }); 70 | } 71 | 72 | @MessagePattern('route.new-position') 73 | consumeNewPosition( 74 | @Payload() 75 | message: { 76 | value: { 77 | routeId: string; 78 | clientId: string; 79 | position: [number, number]; 80 | finished: boolean; 81 | }; 82 | }, 83 | ) { 84 | 85 | this.routeGateway.sendPosition({ 86 | ...message.value, 87 | position: [message.value.position[1], message.value.position[0]], 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.gateway.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RoutesGateway } from './routes.gateway'; 3 | 4 | describe('RoutesGateway', () => { 5 | let gateway: RoutesGateway; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [RoutesGateway], 10 | }).compile(); 11 | 12 | gateway = module.get(RoutesGateway); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(gateway).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.gateway.ts: -------------------------------------------------------------------------------- 1 | import { Inject, OnModuleInit } from '@nestjs/common'; 2 | import { ClientKafka } from '@nestjs/microservices'; 3 | import { Producer } from '@nestjs/microservices/external/kafka.interface'; 4 | import { 5 | SubscribeMessage, 6 | WebSocketGateway, 7 | WebSocketServer, 8 | } from '@nestjs/websockets'; 9 | import { Socket, Server } from 'socket.io'; 10 | 11 | @WebSocketGateway() 12 | export class RoutesGateway implements OnModuleInit { 13 | private kafkaProducer: Producer; 14 | 15 | @WebSocketServer() 16 | server: Server; 17 | 18 | constructor( 19 | @Inject('KAFKA_SERVICE') 20 | private kafkaClient: ClientKafka, 21 | ) {} 22 | 23 | async onModuleInit() { 24 | this.kafkaProducer = await this.kafkaClient.connect(); 25 | } 26 | 27 | @SubscribeMessage('new-direction') 28 | handleMessage(client: Socket, payload: { routeId: string }) { 29 | this.kafkaProducer.send({ 30 | topic: 'route.new-direction', 31 | messages: [ 32 | { 33 | key: 'route.new-direction', 34 | value: JSON.stringify({ 35 | routeId: payload.routeId, 36 | clientId: client.id, 37 | }), 38 | }, 39 | ], 40 | }); 41 | console.log(payload); 42 | } 43 | 44 | sendPosition(data: { 45 | clientId: string; 46 | routeId: string; 47 | position: [number, number]; 48 | finished: boolean; 49 | }) { 50 | const { clientId, ...rest } = data; 51 | const clients = this.server.sockets.connected; 52 | if (!(clientId in clients)) { 53 | console.error( 54 | 'Client not exists, refresh React Application and resend new direction again.', 55 | ); 56 | return; 57 | } 58 | clients[clientId].emit('new-position', rest); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RoutesService } from './routes.service'; 3 | import { RoutesController } from './routes.controller'; 4 | import { MongooseModule } from '@nestjs/mongoose'; 5 | import { Route, RouteSchema } from './entities/route.entity'; 6 | import { ClientsModule, Transport } from '@nestjs/microservices'; 7 | import { RoutesGateway } from './routes.gateway'; 8 | 9 | @Module({ 10 | imports: [ 11 | MongooseModule.forFeature([{ name: Route.name, schema: RouteSchema }]), 12 | ClientsModule.registerAsync([ 13 | { 14 | name: 'KAFKA_SERVICE', 15 | useFactory: (): any => ({ 16 | transport: Transport.KAFKA, 17 | options: { 18 | client: { 19 | clientId: process.env.KAFKA_CLIENT_ID, 20 | brokers: [process.env.KAFKA_BROKER], 21 | // ssl: true, 22 | // sasl: { 23 | // mechanism: 'plain', // scram-sha-256 or scram-sha-512 24 | // username: process.env.KAFKA_SASL_USERNAME, 25 | // password: process.env.KAFKA_SASL_PASSWORD, 26 | // }, 27 | }, 28 | consumer: { 29 | groupId: 30 | !process.env.KAFKA_CONSUMER_GROUP_ID || 31 | process.env.KAFKA_CONSUMER_GROUP_ID === '' 32 | ? 'my-consumer-' + Math.random() 33 | : process.env.KAFKA_CONSUMER_GROUP_ID, 34 | }, 35 | }, 36 | }), 37 | }, 38 | ]), 39 | ], 40 | controllers: [RoutesController], 41 | providers: [RoutesService, RoutesGateway], 42 | }) 43 | export class RoutesModule {} 44 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RoutesService } from './routes.service'; 3 | 4 | describe('RoutesService', () => { 5 | let service: RoutesService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [RoutesService], 10 | }).compile(); 11 | 12 | service = module.get(RoutesService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /nest-api/src/routes/routes.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectModel } from '@nestjs/mongoose'; 3 | import { Model } from 'mongoose'; 4 | import { CreateRouteDto } from './dto/create-route.dto'; 5 | import { UpdateRouteDto } from './dto/update-route.dto'; 6 | import { Route, RouteDocument } from './entities/route.entity'; 7 | 8 | @Injectable() 9 | export class RoutesService { 10 | constructor( 11 | @InjectModel(Route.name) private routeModel: Model, 12 | ) {} 13 | 14 | create(createRouteDto: CreateRouteDto) { 15 | return 'This action adds a new route'; 16 | } 17 | 18 | findAll(): Promise { 19 | return this.routeModel.find().exec(); 20 | } 21 | 22 | findOne(id: number) { 23 | return `This action returns a #${id} route`; 24 | } 25 | 26 | update(id: number, updateRouteDto: UpdateRouteDto) { 27 | return `This action updates a #${id} route`; 28 | } 29 | 30 | remove(id: number) { 31 | return `This action removes a #${id} route`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /nest-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 | -------------------------------------------------------------------------------- /nest-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 | -------------------------------------------------------------------------------- /nest-api/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /nest-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": [ 16 | "src" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | "dist", 21 | ".docker" 22 | ] 23 | } -------------------------------------------------------------------------------- /react-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 | npm start -------------------------------------------------------------------------------- /react-frontend/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=http://localhost:3000 2 | REACT_APP_GOOGLE_API_KEY= -------------------------------------------------------------------------------- /react-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env 26 | 27 | .history/ -------------------------------------------------------------------------------- /react-frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#2fcefa", 4 | "activityBar.activeBorder": "#da05ac", 5 | "activityBar.background": "#2fcefa", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#da05ac", 9 | "activityBarBadge.foreground": "#e7e7e7", 10 | "statusBar.background": "#2fcefa", 11 | "statusBar.foreground": "#15202b", 12 | "statusBarItem.hoverBackground": "#06bdf0" 13 | }, 14 | "peacock.remoteColor": "#61dafb" 15 | } -------------------------------------------------------------------------------- /react-frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.14.0-alpine3.11 2 | 3 | RUN apk add --no-cache bash 4 | 5 | USER node 6 | 7 | WORKDIR /home/node/app -------------------------------------------------------------------------------- /react-frontend/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | #build 2 | FROM node:14.15.4-slim as build 3 | 4 | WORKDIR /app 5 | 6 | COPY package*.json ./ 7 | 8 | RUN npm install 9 | 10 | COPY . . 11 | 12 | RUN npm run build 13 | 14 | #production 15 | FROM nginx:1.15 16 | 17 | COPY --from=build /app/build /usr/share/nginx/html 18 | 19 | EXPOSE 80 20 | 21 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /react-frontend/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - Codelivery 2 | 3 | ## Descrição 4 | 5 | Repositório do front-end feito com React.js (Front-end) 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 | ``` 25 | docker-compose up 26 | ``` 27 | 28 | Acessar http://localhost:3001. 29 | 30 | ### Para Windows 31 | 32 | 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) 33 | 34 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 35 | -------------------------------------------------------------------------------- /react-frontend/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | app: 6 | build: . 7 | entrypoint: ./.docker/entrypoint.sh 8 | ports: 9 | - 3001:3000 10 | volumes: 11 | - .:/home/node/app -------------------------------------------------------------------------------- /react-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.3", 7 | "@material-ui/icons": "^4.11.2", 8 | "@testing-library/jest-dom": "^5.11.10", 9 | "@testing-library/react": "^11.2.6", 10 | "@testing-library/user-event": "^12.8.3", 11 | "@types/jest": "^26.0.22", 12 | "@types/lodash": "^4.14.168", 13 | "@types/node": "^12.20.7", 14 | "@types/react": "^17.0.3", 15 | "@types/react-dom": "^17.0.3", 16 | "@types/socket.io-client": "^1.4.36", 17 | "google-maps": "^4.3.3", 18 | "notistack": "^1.0.5", 19 | "react": "^17.0.2", 20 | "react-dom": "^17.0.2", 21 | "react-scripts": "4.0.3", 22 | "socket.io-client": "^2.4.0", 23 | "typescript": "^4.2.3", 24 | "web-vitals": "^1.1.1" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /react-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao10/fcfcdd83292bb92b3d33a5c62b69e31cf4c874cb/react-frontend/public/favicon.ico -------------------------------------------------------------------------------- /react-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | 29 | Codelivery - Imersão Fullcycle 30 | 31 | 32 | 33 |
34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /react-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao10/fcfcdd83292bb92b3d33a5c62b69e31cf4c874cb/react-frontend/public/logo192.png -------------------------------------------------------------------------------- /react-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeedu/imersao10/fcfcdd83292bb92b3d33a5c62b69e31cf4c874cb/react-frontend/public/logo512.png -------------------------------------------------------------------------------- /react-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Codelivery - Imersão Fullcycle", 3 | "name": "Codelivery - Imersão Fullcycle", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { CssBaseline, MuiThemeProvider } from "@material-ui/core"; 2 | import { SnackbarProvider } from "notistack"; 3 | import { Mapping } from "./components/Mapping"; 4 | import theme from "./theme"; 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export default App; 18 | //TS + JSX 19 | -------------------------------------------------------------------------------- /react-frontend/src/components/Mapping.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Grid, makeStyles, MenuItem, Select } from "@material-ui/core"; 2 | import { Loader } from "google-maps"; 3 | import { 4 | FormEvent, 5 | FunctionComponent, 6 | useCallback, 7 | useEffect, 8 | useRef, 9 | useState, 10 | } from "react"; 11 | import { getCurrentPosition } from "../util/geolocation"; 12 | import { makeCarIcon, makeMarkerIcon, Map } from "../util/map"; 13 | import { Route } from "../util/models"; 14 | import { sample, shuffle } from "lodash"; 15 | import { RouteExistsError } from "../errors/route-exists.error"; 16 | import { useSnackbar } from "notistack"; 17 | import { Navbar } from "./Navbar"; 18 | import io from "socket.io-client"; 19 | 20 | const API_URL = process.env.REACT_APP_API_URL as string; 21 | 22 | const googleMapsLoader = new Loader(process.env.REACT_APP_GOOGLE_API_KEY); 23 | 24 | const colors = [ 25 | "#b71c1c", 26 | "#4a148c", 27 | "#2e7d32", 28 | "#e65100", 29 | "#2962ff", 30 | "#c2185b", 31 | "#FFCD00", 32 | "#3e2723", 33 | "#03a9f4", 34 | "#827717", 35 | ]; 36 | 37 | const useStyles = makeStyles({ 38 | root: { 39 | width: "100%", 40 | height: "100%", 41 | }, 42 | form: { 43 | margin: "16px", 44 | }, 45 | btnSubmitWrapper: { 46 | textAlign: "center", 47 | marginTop: "8px", 48 | }, 49 | map: { 50 | width: "100%", 51 | height: "100%", 52 | }, 53 | }); 54 | 55 | export const Mapping: FunctionComponent = () => { 56 | const classes = useStyles(); 57 | const [routes, setRoutes] = useState([]); 58 | const [routeIdSelected, setRouteIdSelected] = useState(""); 59 | const mapRef = useRef(); 60 | const socketIORef = useRef(); 61 | const { enqueueSnackbar } = useSnackbar(); 62 | 63 | const finishRoute = useCallback( 64 | (route: Route) => { 65 | enqueueSnackbar(`${route.title} finalizou!`, { 66 | variant: "success", 67 | }); 68 | mapRef.current?.removeRoute(route._id); 69 | }, 70 | [enqueueSnackbar] 71 | ); 72 | 73 | useEffect(() => { 74 | if (!socketIORef.current?.connected) { 75 | socketIORef.current = io.connect(API_URL); 76 | socketIORef.current.on("connect", () => console.log("conectou")); 77 | } 78 | 79 | const handler = (data: { 80 | routeId: string; 81 | position: [number, number]; 82 | finished: boolean; 83 | }) => { 84 | console.log(data); 85 | mapRef.current?.moveCurrentMarker(data.routeId, { 86 | lat: data.position[0], 87 | lng: data.position[1], 88 | }); 89 | const route = routes.find((route) => route._id === data.routeId) as Route; 90 | if (data.finished) { 91 | finishRoute(route); 92 | } 93 | }; 94 | socketIORef.current?.on("new-position", handler); 95 | return () => { 96 | socketIORef.current?.off("new-position", handler); 97 | }; 98 | }, [finishRoute, routes, routeIdSelected]); 99 | 100 | useEffect(() => { 101 | fetch(`${API_URL}/routes`) 102 | .then((data) => data.json()) 103 | .then((data) => setRoutes(data)); 104 | }, []); 105 | 106 | useEffect(() => { 107 | (async () => { 108 | const [, position] = await Promise.all([ 109 | googleMapsLoader.load(), 110 | getCurrentPosition({ enableHighAccuracy: true }), 111 | ]); 112 | const divMap = document.getElementById("map") as HTMLElement; 113 | mapRef.current = new Map(divMap, { 114 | zoom: 15, 115 | center: position, 116 | }); 117 | })(); 118 | }, []); 119 | 120 | const startRoute = useCallback( 121 | (event: FormEvent) => { 122 | event.preventDefault(); 123 | const route = routes.find((route) => route._id === routeIdSelected); 124 | const color = sample(shuffle(colors)) as string; 125 | try { 126 | mapRef.current?.addRoute(routeIdSelected, { 127 | currentMarkerOptions: { 128 | position: route?.startPosition, 129 | icon: makeCarIcon(color), 130 | }, 131 | endMarkerOptions: { 132 | position: route?.endPosition, 133 | icon: makeMarkerIcon(color), 134 | }, 135 | }); 136 | socketIORef.current?.emit("new-direction", { 137 | routeId: routeIdSelected, 138 | }); 139 | } catch (error) { 140 | if (error instanceof RouteExistsError) { 141 | enqueueSnackbar(`${route?.title} já adicionado, espere finalizar.`, { 142 | variant: "error", 143 | }); 144 | return; 145 | } 146 | throw error; 147 | } 148 | }, 149 | [routeIdSelected, routes, enqueueSnackbar] 150 | ); 151 | 152 | return ( 153 | 154 | 155 | 156 |
157 | 172 |
173 | 176 |
177 |
178 |
179 | 180 |
181 | 182 | 183 | ); 184 | }; 185 | -------------------------------------------------------------------------------- /react-frontend/src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { AppBar, IconButton, Toolbar, Typography } from "@material-ui/core"; 2 | import { FunctionComponent } from "react"; 3 | import DriverIcon from "@material-ui/icons/DriveEta"; 4 | 5 | export const Navbar: FunctionComponent = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | Code Delivery 13 | 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /react-frontend/src/errors/route-exists.error.ts: -------------------------------------------------------------------------------- 1 | export class RouteExistsError extends Error {} 2 | -------------------------------------------------------------------------------- /react-frontend/src/index.css: -------------------------------------------------------------------------------- 1 | html, body, #root{ 2 | height: 100%; 3 | margin: 0; 4 | } -------------------------------------------------------------------------------- /react-frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; //ES6 Modules 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /react-frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /react-frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /react-frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /react-frontend/src/theme.ts: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from "@material-ui/core"; 2 | import { PaletteOptions } from "@material-ui/core/styles/createPalette"; 3 | 4 | const palette: PaletteOptions = { 5 | type: "dark", 6 | primary: { 7 | main: "#FFCD00", 8 | contrastText: "#242526", 9 | }, 10 | background: { 11 | default: "#242526", 12 | }, 13 | }; 14 | 15 | const theme = createMuiTheme({ 16 | palette, 17 | }); 18 | 19 | export default theme; 20 | -------------------------------------------------------------------------------- /react-frontend/src/util/geolocation.ts: -------------------------------------------------------------------------------- 1 | export function getCurrentPosition( 2 | options?: PositionOptions 3 | ): Promise<{ lat: number; lng: number }> { 4 | return new Promise((resolve, reject) => { 5 | navigator.geolocation.getCurrentPosition( 6 | (position) => 7 | resolve({ 8 | lat: position.coords.latitude, 9 | lng: position.coords.longitude, 10 | }), 11 | (error) => reject(error), 12 | options 13 | ); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /react-frontend/src/util/map.ts: -------------------------------------------------------------------------------- 1 | import { RouteExistsError } from "../errors/route-exists.error"; 2 | 3 | export class Route { 4 | public currentMarker: google.maps.Marker; 5 | public endMarker: google.maps.Marker; 6 | private directionsRenderer: google.maps.DirectionsRenderer; 7 | 8 | constructor(options: { 9 | currentMarkerOptions: google.maps.ReadonlyMarkerOptions; 10 | endMarkerOptions: google.maps.ReadonlyMarkerOptions; 11 | }) { 12 | const { currentMarkerOptions, endMarkerOptions } = options; 13 | this.currentMarker = new google.maps.Marker(currentMarkerOptions); 14 | this.endMarker = new google.maps.Marker(endMarkerOptions); 15 | 16 | const strokeColor = (this.currentMarker.getIcon() as google.maps.ReadonlySymbol) 17 | .strokeColor; 18 | this.directionsRenderer = new google.maps.DirectionsRenderer({ 19 | suppressMarkers: true, 20 | polylineOptions: { 21 | strokeColor, 22 | strokeOpacity: 0.5, 23 | strokeWeight: 5, 24 | }, 25 | }); 26 | this.directionsRenderer.setMap( 27 | this.currentMarker.getMap() as google.maps.Map 28 | ); 29 | 30 | this.calculateRoute(); 31 | } 32 | 33 | private calculateRoute() { 34 | const currentPosition = this.currentMarker.getPosition() as google.maps.LatLng; 35 | const endPosition = this.endMarker.getPosition() as google.maps.LatLng; 36 | 37 | new google.maps.DirectionsService().route( 38 | { 39 | origin: currentPosition, 40 | destination: endPosition, 41 | travelMode: google.maps.TravelMode.DRIVING, 42 | }, 43 | (result, status) => { 44 | if (status === "OK") { 45 | this.directionsRenderer.setDirections(result); 46 | return; 47 | } 48 | 49 | throw new Error(status); 50 | } 51 | ); 52 | } 53 | 54 | delete() { 55 | this.currentMarker.setMap(null); 56 | this.endMarker.setMap(null); 57 | this.directionsRenderer.setMap(null); 58 | } 59 | } 60 | 61 | export class Map { 62 | public map: google.maps.Map; 63 | private routes: { [id: string]: Route } = {}; 64 | constructor(element: Element, options: google.maps.MapOptions) { 65 | this.map = new google.maps.Map(element, options); 66 | } 67 | 68 | moveCurrentMarker(id: string, position: google.maps.LatLngLiteral) { 69 | this.routes[id].currentMarker.setPosition(position); 70 | } 71 | 72 | removeRoute(id: string) { 73 | const route = this.routes[id]; 74 | route.delete(); 75 | delete this.routes[id]; 76 | } 77 | 78 | addRoute( 79 | id: string, 80 | routeOptions: { 81 | currentMarkerOptions: google.maps.ReadonlyMarkerOptions; 82 | endMarkerOptions: google.maps.ReadonlyMarkerOptions; 83 | } 84 | ) { 85 | if (id in this.routes) { 86 | throw new RouteExistsError(); 87 | } 88 | 89 | const { currentMarkerOptions, endMarkerOptions } = routeOptions; 90 | this.routes[id] = new Route({ 91 | currentMarkerOptions: { ...currentMarkerOptions, map: this.map }, 92 | endMarkerOptions: { ...endMarkerOptions, map: this.map }, 93 | }); 94 | 95 | this.fitBounds(); 96 | } 97 | 98 | private fitBounds() { 99 | const bounds = new google.maps.LatLngBounds(); 100 | 101 | Object.keys(this.routes).forEach((id: string) => { 102 | const route = this.routes[id]; 103 | bounds.extend(route.currentMarker.getPosition() as any); 104 | bounds.extend(route.endMarker.getPosition() as any); 105 | }); 106 | 107 | this.map.fitBounds(bounds); 108 | } 109 | } 110 | 111 | export const makeCarIcon = (color: string) => ({ 112 | path: 113 | "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", 114 | fillColor: color, 115 | strokeColor: color, 116 | strokeWeight: 1, 117 | fillOpacity: 1, 118 | anchor: new google.maps.Point(26, 20), 119 | }); 120 | 121 | export const makeMarkerIcon = (color: string) => ({ 122 | path: 123 | "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", 124 | strokeColor: color, 125 | fillColor: color, 126 | strokeOpacity: 1, 127 | strokeWeight: 1, 128 | fillOpacity: 1, 129 | anchor: new google.maps.Point(46, 70), 130 | }); 131 | -------------------------------------------------------------------------------- /react-frontend/src/util/models.ts: -------------------------------------------------------------------------------- 1 | export interface Position { 2 | lat: number; 3 | lng: number; 4 | } 5 | export interface Route { 6 | _id: string; 7 | title: string; 8 | startPosition: Position; 9 | endPosition: Position; 10 | } 11 | -------------------------------------------------------------------------------- /react-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /simulator/.env.example: -------------------------------------------------------------------------------- 1 | KafkaReadTopic=route.new-direction 2 | KafkaProduceTopic=route.new-position 3 | KafkaBootstrapServers=host.docker.internal:9094 4 | KafkaConsumerGroupId=simulator -------------------------------------------------------------------------------- /simulator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16 2 | 3 | WORKDIR /go/src 4 | ENV PATH="/go/bin:${PATH}" 5 | 6 | RUN apt-get update && \ 7 | apt-get install build-essential librdkafka-dev -y 8 | 9 | CMD ["tail", "-f", "/dev/null"] -------------------------------------------------------------------------------- /simulator/Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM golang:1.16 2 | 3 | WORKDIR /go/src 4 | ENV PATH="/go/bin:${PATH}" 5 | 6 | RUN apt-get update && \ 7 | apt-get install build-essential librdkafka-dev -y 8 | 9 | COPY . . 10 | RUN GOOS=linux go build -ldflags="-s -w" -o simulator 11 | ENTRYPOINT ["./simulator"] -------------------------------------------------------------------------------- /simulator/README.md: -------------------------------------------------------------------------------- 1 | # Imersão Full Stack & FullCycle - Codelivery 2 | 3 | ## Descrição 4 | 5 | Repositório do front-end feito com Golang (Backend) 6 | 7 | **Importante**: A aplicação do Apache Kafka 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 | ``` 25 | docker-compose up -d 26 | # Entrar no container 27 | docker-compose exec app bash 28 | # Rodar a aplicação Golang 29 | go run main.go 30 | ``` 31 | 32 | ### Para Windows 33 | 34 | 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) 35 | 36 | Siga o guia rápido de instalação: [https://github.com/codeedu/wsl2-docker-quickstart](https://github.com/codeedu/wsl2-docker-quickstart) 37 | -------------------------------------------------------------------------------- /simulator/application/kafka/produce.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "encoding/json" 5 | route2 "github.com/codeedu/imersaofsfc2-simulator/application/route" 6 | "github.com/codeedu/imersaofsfc2-simulator/infra/kafka" 7 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 8 | "log" 9 | "os" 10 | "time" 11 | ) 12 | 13 | // Produce is responsible to publish the positions of each request 14 | // Example of a json request: 15 | //{"clientId":"1","routeId":"1"} 16 | //{"clientId":"2","routeId":"2"} 17 | //{"clientId":"3","routeId":"3"} 18 | func Produce(msg *ckafka.Message) { 19 | producer := kafka.NewKafkaProducer() 20 | route := route2.NewRoute() 21 | json.Unmarshal(msg.Value, &route) 22 | route.LoadPositions() 23 | positions, err := route.ExportJsonPositions() 24 | if err != nil { 25 | log.Println(err.Error()) 26 | } 27 | for _, p := range positions { 28 | kafka.Publish(p, os.Getenv("KafkaProduceTopic"), producer) 29 | time.Sleep(time.Millisecond * 500) 30 | } 31 | } -------------------------------------------------------------------------------- /simulator/application/route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "errors" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // Route represents a request of new delivery request 13 | type Route struct { 14 | ID string `json:"routeId"` 15 | ClientID string `json:"clientId"` 16 | Positions []Position `json:"position"` 17 | } 18 | 19 | // Position is a type which contains the lat and long 20 | type Position struct { 21 | Lat float64 `json:"lat"` 22 | Long float64 `json:"long"` 23 | } 24 | 25 | // PartialRoutePosition is the actual response which the system will return 26 | type PartialRoutePosition struct { 27 | ID string `json:"routeId"` 28 | ClientID string `json:"clientId"` 29 | Position []float64 `json:"position"` 30 | Finished bool `json:"finished"` 31 | } 32 | 33 | // NewRoute creates a *Route struct 34 | func NewRoute() *Route { 35 | return &Route{} 36 | } 37 | 38 | // LoadPositions loads from a .txt file all positions (lat and long) to the Position attribute of the struct 39 | func (r *Route) LoadPositions() error { 40 | if r.ID == "" { 41 | return errors.New("route id not informed") 42 | } 43 | f, err := os.Open("destinations/" + r.ID + ".txt") 44 | if err != nil { 45 | return err 46 | } 47 | defer f.Close() 48 | scanner := bufio.NewScanner(f) 49 | for scanner.Scan() { 50 | data := strings.Split(scanner.Text(), ",") 51 | lat, err := strconv.ParseFloat(data[1], 64) 52 | if err != nil { 53 | return nil 54 | } 55 | long, err := strconv.ParseFloat(data[0], 64) 56 | if err != nil { 57 | return nil 58 | } 59 | r.Positions = append(r.Positions, Position{ 60 | Lat: lat, 61 | Long: long, 62 | }) 63 | } 64 | return nil 65 | } 66 | 67 | // ExportJsonPositions generates a slice of string in Json using PartialRoutePosition struct 68 | func (r *Route) ExportJsonPositions() ([]string, error) { 69 | var route PartialRoutePosition 70 | var result []string 71 | total := len(r.Positions) 72 | for k, v := range r.Positions { 73 | route.ID = r.ID 74 | route.ClientID = r.ClientID 75 | route.Position = []float64{v.Lat, v.Long} 76 | route.Finished = false 77 | if total-1 == k { 78 | route.Finished = true 79 | } 80 | jsonRoute, err := json.Marshal(route) 81 | if err != nil { 82 | return nil, err 83 | } 84 | result = append(result, string(jsonRoute)) 85 | } 86 | return result, nil 87 | } 88 | -------------------------------------------------------------------------------- /simulator/destinations/1.txt: -------------------------------------------------------------------------------- 1 | -15.82594,-47.92923 2 | -15.8261,-47.92911 3 | -15.82615,-47.92907 4 | -15.82637,-47.92889 5 | -15.82651,-47.92878 6 | -15.82655,-47.92875 7 | -15.82665,-47.92867 8 | -15.82636,-47.92827 9 | -15.82615,-47.92798 10 | -15.82651,-47.9277 11 | -15.82658,-47.92765 12 | -15.82701,-47.92732 13 | -15.82733,-47.92706 14 | -15.82749,-47.92694 15 | -15.8272,-47.92653 16 | -15.82717,-47.9265 17 | -15.82686,-47.92607 18 | -15.82683,-47.92603 19 | -15.82678,-47.92596 20 | -15.82676,-47.92594 21 | -15.82671,-47.92586 22 | -15.82666,-47.92578 23 | -15.82659,-47.92569 24 | -15.82644,-47.92548 25 | -15.8262,-47.92515 26 | -15.82614,-47.92506 27 | -15.82596,-47.92482 28 | -15.8259,-47.92474 29 | -15.82587,-47.9247 30 | -15.82579,-47.92457 31 | -15.82568,-47.92442 32 | -15.8261,-47.92412 33 | -15.82642,-47.92387 34 | -15.82643,-47.92386 35 | -15.82644,-47.92385 36 | -15.82645,-47.92384 37 | -15.82647,-47.92382 38 | -15.82648,-47.9238 39 | -15.82648,-47.92378 40 | -15.82649,-47.92376 41 | -15.82649,-47.92375 42 | -15.8265,-47.92373 43 | -15.8265,-47.92372 44 | -15.8265,-47.9237 45 | -15.8265,-47.92369 46 | -15.82649,-47.92363 47 | -15.8265,-47.92352 48 | -15.8265,-47.92351 49 | -15.8265,-47.92349 50 | -15.82651,-47.92348 51 | -15.82651,-47.92346 52 | -15.82652,-47.92345 53 | -15.82652,-47.92344 54 | -15.82653,-47.92342 55 | -15.82653,-47.92341 56 | -15.82654,-47.9234 57 | -15.82655,-47.92339 58 | -15.82656,-47.92338 59 | -15.82663,-47.92334 60 | -15.8267,-47.92329 61 | -15.82685,-47.92317 62 | -15.827,-47.92305 63 | -15.82711,-47.92298 64 | -15.82721,-47.92291 65 | -15.82731,-47.92283 66 | -15.82741,-47.92274 67 | -15.8275,-47.92267 68 | -15.82758,-47.9226 69 | -15.82761,-47.92257 70 | -15.82764,-47.92255 71 | -15.82775,-47.92246 72 | -15.82787,-47.92238 73 | -15.8281,-47.92222 74 | -15.8281,-47.92223 75 | -15.82811,-47.92223 76 | -15.82812,-47.92224 77 | -15.82812,-47.92225 78 | -15.82813,-47.92225 79 | -15.82814,-47.92226 80 | -15.82815,-47.92226 81 | -15.82816,-47.92227 82 | -15.82817,-47.92227 83 | -15.82818,-47.92228 84 | -15.82819,-47.92228 85 | -15.8282,-47.92228 86 | -15.82821,-47.92228 87 | -15.82822,-47.92228 88 | -15.82823,-47.92228 89 | -15.82824,-47.92227 90 | -15.82825,-47.92227 91 | -15.82826,-47.92227 92 | -15.82826,-47.92226 93 | -15.82827,-47.92226 94 | -15.82828,-47.92225 95 | -15.82847,-47.92251 96 | -15.82853,-47.9226 97 | -15.82923,-47.92354 98 | -15.82935,-47.92373 99 | -15.82947,-47.92391 100 | -15.82947,-47.92392 101 | -15.82946,-47.92393 102 | -15.82946,-47.92394 103 | -15.82946,-47.92395 104 | -15.82945,-47.92396 105 | -15.82945,-47.92397 106 | -15.82945,-47.92398 107 | -15.82946,-47.92399 108 | -15.82946,-47.924 109 | -15.82946,-47.92401 110 | -15.82946,-47.92402 111 | -15.82947,-47.92402 112 | -15.82947,-47.92403 113 | -15.82948,-47.92404 114 | -15.82949,-47.92405 115 | -15.8295,-47.92405 116 | -15.8295,-47.92406 117 | -15.82951,-47.92406 118 | -15.82952,-47.92407 119 | -15.82953,-47.92407 120 | -15.82954,-47.92407 121 | -15.82955,-47.92407 122 | -15.82956,-47.92407 123 | -15.82964,-47.92423 124 | -15.82974,-47.92444 125 | -15.82979,-47.9245 126 | -15.82998,-47.92475 127 | -15.82997,-47.92477 128 | -15.82997,-47.92478 129 | -15.82996,-47.92479 130 | -15.82996,-47.9248 131 | -15.82996,-47.92482 132 | -15.82996,-47.92483 133 | -15.82996,-47.92484 134 | -15.82996,-47.92486 135 | -15.82996,-47.92487 136 | -15.82997,-47.92488 137 | -15.82997,-47.92489 138 | -15.82998,-47.9249 139 | -15.82999,-47.92492 140 | -15.82999,-47.92493 141 | -15.83,-47.92494 142 | -15.83001,-47.92495 143 | -15.83002,-47.92496 144 | -15.83003,-47.92497 145 | -15.83004,-47.92497 146 | -15.83005,-47.92498 147 | -15.83006,-47.92499 148 | -15.83008,-47.92499 149 | -15.83009,-47.92499 150 | -15.8301,-47.925 151 | -15.83011,-47.925 152 | -15.83013,-47.925 153 | -15.83015,-47.925 154 | -15.83022,-47.92511 155 | -15.83034,-47.92529 156 | -15.83041,-47.92536 157 | -15.83047,-47.92543 158 | -15.83063,-47.92564 159 | -15.83063,-47.92565 160 | -15.83062,-47.92565 161 | -15.83062,-47.92566 162 | -15.83061,-47.92566 163 | -15.83061,-47.92567 164 | -15.83061,-47.92568 165 | -15.8306,-47.92568 166 | -15.8306,-47.92569 167 | -15.8306,-47.9257 168 | -15.8306,-47.92571 169 | -15.8306,-47.92572 170 | -15.8306,-47.92573 171 | -15.83061,-47.92573 172 | -15.83061,-47.92574 173 | -15.83061,-47.92575 174 | -15.83062,-47.92575 175 | -15.83062,-47.92576 176 | -15.83063,-47.92576 177 | -15.83063,-47.92577 178 | -15.83064,-47.92577 179 | -15.83068,-47.92593 180 | -15.83068,-47.92596 181 | -15.83068,-47.92599 182 | -15.83065,-47.92606 183 | -15.83044,-47.92624 184 | -15.83041,-47.92626 185 | -15.83038,-47.92628 186 | -15.83035,-47.92631 187 | -15.83033,-47.92634 188 | -15.8303,-47.92636 189 | -15.83029,-47.92637 190 | -15.83028,-47.9264 191 | -15.83027,-47.92642 192 | -15.83025,-47.92644 193 | -15.83024,-47.92646 194 | -15.83024,-47.92648 195 | -15.83023,-47.9265 196 | -15.83022,-47.92651 197 | -15.8302,-47.92656 198 | -15.83018,-47.92661 199 | -15.83015,-47.92665 200 | -15.83012,-47.9267 201 | -15.8301,-47.92674 202 | -15.83006,-47.9268 203 | -15.83003,-47.92684 204 | -15.83001,-47.92686 205 | -15.83,-47.92688 206 | -15.82998,-47.9269 207 | -15.82995,-47.92692 208 | -15.82993,-47.92694 209 | -15.82991,-47.92696 210 | -15.82988,-47.92698 211 | -15.82985,-47.92701 212 | -15.82983,-47.92703 213 | -15.8298,-47.92705 214 | -15.82979,-47.92706 215 | -15.82966,-47.92716 216 | -15.82972,-47.92723 217 | -15.8298,-47.92735 218 | -15.82966,-47.92746 219 | -15.82942,-47.92765 -------------------------------------------------------------------------------- /simulator/destinations/2.txt: -------------------------------------------------------------------------------- 1 | -15.82449,-47.92756 2 | -15.82404,-47.9269 3 | -15.82397,-47.9268 4 | -15.82386,-47.92661 5 | -15.82418,-47.92636 6 | -15.8243,-47.92627 7 | -15.82436,-47.92623 8 | -15.82441,-47.92619 9 | -15.82452,-47.92611 10 | -15.82465,-47.92601 11 | -15.82478,-47.9259 12 | -15.825,-47.9257 13 | -15.82501,-47.92573 14 | -15.82527,-47.92552 15 | -15.82537,-47.92546 16 | -15.82546,-47.92538 17 | -15.82554,-47.92532 18 | -15.82568,-47.92522 19 | -15.8257,-47.9252 20 | -15.82588,-47.92501 21 | -15.82601,-47.9252 22 | -15.82636,-47.92569 23 | -15.82648,-47.92584 24 | -15.82651,-47.92589 25 | -15.82656,-47.92596 26 | -15.8267,-47.92615 27 | -15.82733,-47.92706 28 | -15.82759,-47.9274 29 | -15.82807,-47.92805 30 | -15.82836,-47.92845 31 | -15.82843,-47.92839 32 | -15.82851,-47.92833 33 | -15.82825,-47.92797 34 | -15.82796,-47.92759 35 | -15.82773,-47.92727 36 | -15.82795,-47.92712 37 | -15.82804,-47.92703 38 | -15.82814,-47.92695 39 | -15.82812,-47.92692 40 | -15.8276,-47.92621 -------------------------------------------------------------------------------- /simulator/destinations/3.txt: -------------------------------------------------------------------------------- 1 | -15.82331,-47.92588 2 | -15.82327,-47.92584 3 | -15.82306,-47.92553 4 | -15.82284,-47.92522 5 | -15.82281,-47.92519 6 | -15.82277,-47.92513 7 | -15.82271,-47.92504 8 | -15.82262,-47.92492 9 | -15.8225,-47.92476 10 | -15.82235,-47.92454 11 | -15.8219,-47.92394 12 | -15.82185,-47.92387 13 | -15.82174,-47.92372 14 | -15.82164,-47.92357 15 | -15.82152,-47.92342 16 | -15.82105,-47.92377 17 | -15.8211,-47.92384 18 | -15.82119,-47.92395 19 | -15.82127,-47.92406 20 | -15.82133,-47.92413 21 | -15.82136,-47.92419 22 | -15.82159,-47.92451 23 | -15.82186,-47.92488 24 | -15.82188,-47.92491 25 | -15.82196,-47.92502 26 | -15.82208,-47.92519 27 | -15.82218,-47.92533 28 | -15.82229,-47.92548 29 | -15.82234,-47.92555 30 | -15.82237,-47.9256 31 | -15.82241,-47.92564 32 | -15.82261,-47.92592 33 | -15.82276,-47.92612 34 | -15.82278,-47.92615 35 | -15.82303,-47.9265 36 | -15.82311,-47.92663 37 | -15.82314,-47.92668 38 | -15.8232,-47.92687 39 | -15.8232,-47.92688 40 | -15.8232,-47.92689 41 | -15.8232,-47.9269 42 | -15.8232,-47.92691 43 | -15.8232,-47.92692 44 | -15.8232,-47.92693 45 | -15.8232,-47.92694 46 | -15.8232,-47.92695 47 | -15.82321,-47.92696 48 | -15.82321,-47.92697 49 | -15.82322,-47.92698 50 | -15.82322,-47.92699 51 | -15.82323,-47.927 52 | -15.82323,-47.92701 53 | -15.82324,-47.92701 54 | -15.82325,-47.92702 55 | -15.82325,-47.92703 56 | -15.82326,-47.92703 57 | -15.82327,-47.92704 58 | -15.82328,-47.92704 59 | -15.82329,-47.92705 60 | -15.8233,-47.92705 61 | -15.82331,-47.92705 62 | -15.82333,-47.92706 63 | -15.82335,-47.92706 64 | -15.82336,-47.92705 65 | -15.82337,-47.92705 66 | -15.82338,-47.92705 67 | -15.82339,-47.92705 68 | -15.8234,-47.92704 69 | -15.82341,-47.92703 70 | -15.82342,-47.92703 71 | -15.82343,-47.92702 72 | -15.82343,-47.92701 73 | -15.82344,-47.927 74 | -15.82345,-47.92699 75 | -15.82345,-47.92698 76 | -15.82346,-47.92697 77 | -15.82346,-47.92696 78 | -15.82346,-47.92695 79 | -15.82346,-47.92694 80 | -15.82347,-47.92694 81 | -15.82347,-47.92693 82 | -15.82375,-47.92669 83 | -15.8238,-47.92665 84 | -15.82386,-47.92661 85 | -15.82418,-47.92636 86 | -15.8243,-47.92627 87 | -15.82436,-47.92623 88 | -15.82441,-47.92619 89 | -15.82452,-47.92611 90 | -15.82465,-47.92601 91 | -15.82478,-47.9259 92 | -15.825,-47.9257 93 | -15.82501,-47.92573 94 | -15.82527,-47.92552 95 | -15.82537,-47.92546 96 | -15.82546,-47.92538 97 | -15.82554,-47.92532 98 | -15.82568,-47.92522 99 | -15.8257,-47.9252 100 | -15.82588,-47.92501 101 | -15.82596,-47.92482 102 | -15.8259,-47.92474 103 | -15.82587,-47.9247 104 | -15.82579,-47.92457 105 | -15.82568,-47.92442 106 | -15.8261,-47.92412 107 | -15.82642,-47.92387 108 | -15.82643,-47.92386 109 | -15.82644,-47.92385 110 | -15.82645,-47.92384 111 | -15.82647,-47.92382 112 | -15.82648,-47.9238 113 | -15.82648,-47.92378 114 | -15.82649,-47.92376 115 | -15.82649,-47.92375 116 | -15.8265,-47.92373 117 | -15.8265,-47.92372 118 | -15.8265,-47.9237 119 | -15.8265,-47.92369 120 | -15.82649,-47.92363 121 | -15.8265,-47.92352 122 | -15.8265,-47.92351 123 | -15.8265,-47.92349 124 | -15.82651,-47.92348 125 | -15.82651,-47.92346 126 | -15.82652,-47.92345 127 | -15.82652,-47.92344 128 | -15.82653,-47.92342 129 | -15.82653,-47.92341 130 | -15.82654,-47.9234 131 | -15.82655,-47.92339 132 | -15.82656,-47.92338 133 | -15.82663,-47.92334 134 | -15.8267,-47.92329 135 | -15.82685,-47.92317 136 | -15.827,-47.923 137 | -15.82711,-47.92298 138 | -15.82721,-47.92291 139 | -15.82731,-47.92283 140 | -15.82741,-47.92274 141 | -15.8275,-47.92267 142 | -15.82758,-47.9226 143 | -15.82761,-47.92257 144 | -15.82764,-47.92255 145 | -15.82775,-47.92246 146 | -15.82787,-47.92238 147 | -15.8281,-47.92222 148 | -15.8281,-47.92223 149 | -15.82811,-47.92223 150 | -15.82812,-47.92224 151 | -15.82812,-47.92225 152 | -15.82813,-47.92225 153 | -15.82814,-47.92226 154 | -15.82815,-47.92226 155 | -15.82816,-47.92227 156 | -15.82817,-47.92227 157 | -15.82818,-47.92228 158 | -15.82819,-47.92228 159 | -15.8282,-47.92228 160 | -15.82821,-47.92228 161 | -15.82822,-47.92228 162 | -15.82823,-47.92228 163 | -15.82824,-47.92227 164 | -15.82825,-47.92227 165 | -15.82826,-47.92227 166 | -15.82826,-47.92226 167 | -15.82827,-47.92226 168 | -15.82828,-47.92225 169 | -15.82847,-47.92251 170 | -15.82853,-47.9226 171 | -15.82923,-47.92354 172 | -15.82935,-47.92373 173 | -15.82947,-47.92391 174 | -15.82947,-47.92392 175 | -15.82946,-47.92393 176 | -15.82946,-47.92394 177 | -15.82946,-47.92395 178 | -15.82945,-47.92396 179 | -15.82945,-47.92397 180 | -15.82938,-47.924 181 | -15.82931,-47.92406 182 | -15.82925,-47.92412 183 | -15.82921,-47.92417 184 | -15.82917,-47.92423 185 | -15.82905,-47.92434 186 | -15.82897,-47.9244 187 | -15.82888,-47.92443 188 | -15.82879,-47.92445 189 | -15.8286,-47.92446 190 | -15.8286,-47.92445 191 | -15.8286,-47.92444 192 | -15.8286,-47.92443 193 | -15.82859,-47.92443 194 | -15.82859,-47.92442 195 | -15.82859,-47.92441 196 | -15.82858,-47.92441 197 | -15.82858,-47.9244 198 | -15.82857,-47.9244 199 | -15.82856,-47.9244 200 | -15.82856,-47.92439 201 | -15.82855,-47.92439 202 | -15.82854,-47.92439 203 | -15.82853,-47.92439 204 | -15.82853,-47.9244 205 | -15.82852,-47.9244 206 | -15.82851,-47.9244 207 | -15.82841,-47.92424 208 | -15.82826,-47.92405 209 | -15.82821,-47.92406 210 | -15.82815,-47.92407 211 | -15.8281,-47.92407 212 | -15.828,-47.924 213 | -15.82797,-47.92409 214 | -15.82794,-47.92409 215 | -15.8279,-47.9241 216 | -15.82787,-47.92411 217 | -15.82786,-47.92412 218 | -15.82784,-47.92412 219 | -15.82782,-47.92414 220 | -15.82781,-47.92415 221 | -15.82779,-47.92416 222 | -15.82777,-47.92418 223 | -15.82775,-47.92419 224 | -15.82773,-47.92422 225 | -15.82771,-47.92423 226 | -15.8277,-47.92425 227 | -15.82767,-47.92429 228 | -15.82766,-47.9243 229 | -15.82765,-47.92432 230 | -15.82764,-47.92434 231 | -15.82762,-47.92436 232 | -15.82761,-47.92438 233 | -15.8276,-47.9244 234 | -15.82759,-47.92442 235 | -15.82758,-47.92445 236 | -15.82757,-47.92447 237 | -15.82757,-47.92449 238 | -15.82756,-47.92452 239 | -15.82756,-47.92455 240 | -15.82755,-47.92457 241 | -15.82755,-47.92461 242 | -15.82754,-47.92466 243 | -15.82753,-47.92471 244 | -15.82749,-47.92478 245 | -15.82747,-47.92481 246 | -15.82747,-47.92483 247 | -15.82746,-47.92485 248 | -15.82746,-47.92486 249 | -15.82746,-47.92489 250 | -15.82745,-47.92491 251 | -15.82745,-47.92494 252 | -15.82745,-47.92495 253 | -15.82745,-47.92498 254 | -15.82745,-47.92501 255 | -15.82745,-47.92504 256 | -15.82746,-47.92505 257 | -15.82746,-47.92507 258 | -15.82746,-47.9251 259 | -15.82747,-47.92512 260 | -15.82747,-47.92514 261 | -15.82748,-47.92516 262 | -15.8275,-47.92519 263 | -15.82751,-47.92521 264 | -15.82752,-47.92523 265 | -15.82758,-47.92532 266 | -------------------------------------------------------------------------------- /simulator/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | build: . 6 | container_name: simulator 7 | volumes: 8 | - .:/go/src/ 9 | extra_hosts: 10 | - "host.docker.internal:172.17.0.1" -------------------------------------------------------------------------------- /simulator/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codeedu/imersaofsfc2-simulator 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/confluentinc/confluent-kafka-go v1.6.1 7 | github.com/joho/godotenv v1.3.0 8 | ) 9 | -------------------------------------------------------------------------------- /simulator/go.sum: -------------------------------------------------------------------------------- 1 | github.com/confluentinc/confluent-kafka-go v1.6.1 h1:YxM/UtMQ2vgJX2gIgeJFUD0ANQYTEvfo4Cs4qKUlmGE= 2 | github.com/confluentinc/confluent-kafka-go v1.6.1/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= 3 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 4 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 5 | -------------------------------------------------------------------------------- /simulator/infra/kafka/consumer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 9 | ) 10 | 11 | // KafkaConsumer holds all consumer logic and settings of Apache Kafka connections/ 12 | // Also has a Message channel which is a channel where the messages are going to be pushed 13 | type KafkaConsumer struct { 14 | MsgChan chan *ckafka.Message 15 | } 16 | 17 | // NewKafkaConsumer creates a new KafkaConsumer struct with its message channel as dependency 18 | func NewKafkaConsumer(msgChan chan *ckafka.Message) *KafkaConsumer { 19 | return &KafkaConsumer{ 20 | MsgChan: msgChan, 21 | } 22 | } 23 | 24 | // Consume consumes all message pulled from apache kafka and sent it to message channel 25 | func (k *KafkaConsumer) Consume() { 26 | configMap := &ckafka.ConfigMap{ 27 | "bootstrap.servers": os.Getenv("KafkaBootstrapServers"), 28 | "group.id": os.Getenv("KafkaConsumerGroupId"), 29 | // "security.protocol": os.Getenv("security.protocol"), 30 | // "sasl.mechanisms": os.Getenv("sasl.mechanisms"), 31 | // "sasl.username": os.Getenv("sasl.username"), 32 | // "sasl.password": os.Getenv("sasl.password"), 33 | } 34 | c, err := ckafka.NewConsumer(configMap) 35 | if err != nil { 36 | log.Fatalf("error consuming kafka message:" + err.Error()) 37 | } 38 | topics := []string{os.Getenv("KafkaReadTopic")} 39 | c.SubscribeTopics(topics, nil) 40 | fmt.Println("Kafka consumer has been started") 41 | for { 42 | msg, err := c.ReadMessage(-1) 43 | if err == nil { 44 | k.MsgChan <- msg 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /simulator/infra/kafka/producer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 8 | ) 9 | 10 | // NewKafkaProducer creates a ready to go kafka.Producer instance 11 | func NewKafkaProducer() *ckafka.Producer { 12 | configMap := &ckafka.ConfigMap{ 13 | "bootstrap.servers": os.Getenv("KafkaBootstrapServers"), 14 | // "security.protocol": os.Getenv("security.protocol"), 15 | // "sasl.mechanisms": os.Getenv("sasl.mechanisms"), 16 | // "sasl.username": os.Getenv("sasl.username"), 17 | // "sasl.password": os.Getenv("sasl.password"), 18 | } 19 | p, err := ckafka.NewProducer(configMap) 20 | if err != nil { 21 | log.Println(err.Error()) 22 | } 23 | return p 24 | } 25 | 26 | // Publish is simple function created to publish new message to kafka 27 | func Publish(msg string, topic string, producer *ckafka.Producer) error { 28 | message := &ckafka.Message{ 29 | TopicPartition: ckafka.TopicPartition{Topic: &topic, Partition: ckafka.PartitionAny}, 30 | Value: []byte(msg), 31 | } 32 | err := producer.Produce(message, nil) 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /simulator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | kafka2 "github.com/codeedu/imersaofsfc2-simulator/application/kafka" 6 | "github.com/codeedu/imersaofsfc2-simulator/infra/kafka" 7 | ckafka "github.com/confluentinc/confluent-kafka-go/kafka" 8 | "github.com/joho/godotenv" 9 | "log" 10 | ) 11 | 12 | func init() { 13 | err := godotenv.Load() 14 | if err != nil { 15 | log.Fatal("error loading .env file") 16 | } 17 | } 18 | 19 | func main() { 20 | msgChan := make(chan *ckafka.Message) 21 | consumer := kafka.NewKafkaConsumer(msgChan) 22 | go consumer.Consume() 23 | for msg := range msgChan { 24 | fmt.Println(string(msg.Value)) 25 | go kafka2.Produce(msg) 26 | } 27 | } 28 | --------------------------------------------------------------------------------