├── README.md ├── app_docker_compose ├── README.md ├── api │ ├── api.products │ │ ├── Dockerfile │ │ ├── app.js │ │ ├── app │ │ │ ├── commands │ │ │ │ └── app.product.command.js │ │ │ ├── controllers │ │ │ │ └── app.products.controller.js │ │ │ ├── events │ │ │ │ ├── app.product.publish.event.js │ │ │ │ ├── app.products.subscribe.event.js │ │ │ │ └── app.sale.subscribe.event.js │ │ │ ├── models │ │ │ │ └── app.product.model.js │ │ │ └── repository │ │ │ │ └── app.products.repository.js │ │ ├── config │ │ │ ├── amqp.config.js │ │ │ ├── db.config.js │ │ │ └── microservices.db │ │ └── package.json │ └── api.sales │ │ ├── Dockerfile │ │ ├── app.js │ │ ├── app │ │ ├── commands │ │ │ └── app.sale.command.js │ │ ├── controllers │ │ │ └── app.sale.controller.js │ │ ├── events │ │ │ ├── app.sale.publish.event.js │ │ │ └── app.sale.subscribe.event.js │ │ ├── models │ │ │ └── app.sale.model.js │ │ └── repository │ │ │ └── app.sale.repository.js │ │ ├── config │ │ ├── amqp.config.js │ │ ├── db.config.js │ │ └── microservices.db │ │ └── package.json ├── docker-compose.yml └── web │ ├── Dockerfile │ ├── app.js │ ├── app │ ├── app.js │ ├── controllers │ │ ├── app.home.ctrl.js │ │ └── app.sales.ctrl.js │ ├── index.html │ ├── services │ │ └── app.home.srv.js │ └── views │ │ ├── home.html │ │ └── sales.html │ ├── package.json │ └── public │ └── css │ └── style.css └── app_dockerfile ├── Dockerfile ├── app.js ├── dev-config.json ├── index.js ├── package.json ├── public ├── images │ ├── cat.ico │ └── grass.svg └── stylesheets │ └── style.css ├── routes └── index.js └── views ├── index.jade └── layout.jade /README.md: -------------------------------------------------------------------------------- 1 | # example-course-containers 2 | 3 | Letscode repository for container examples with Dockerfile and Docker Compose 4 | -------------------------------------------------------------------------------- /app_docker_compose/README.md: -------------------------------------------------------------------------------- 1 | #Arquitetura de Microsserviços. 2 | 3 | Código fonte da palestra sobre Arquitetura de Microsserviços, Overview, Implementação e Deploy . 4 | 5 | ## Pré-configuração e instalação do ambiente. 6 | 7 | Node.js - Tecnologia de servidor utilizada 8 | [Download](https://nodejs.org/en/download/) 9 | 10 | RabbitMQ - Tecnologia utilizada para a troca de mensagens entre serviços 11 | [Download](https://www.rabbitmq.com/download.html) 12 | 13 | Docker 14 | [Download](https://www.docker.com/community-edition) 15 | 16 | Visual Studio Code - Opcional 17 | [Download](https://code.visualstudio.com/download) 18 | 19 | ## Rodando a aplicação 20 | 21 | Interface web 22 | ./web 23 | 24 | > npm install 25 | 26 | > node app 27 | 28 | API - Produtos 29 | ./api/api.products 30 | 31 | > npm install 32 | 33 | > node app 34 | 35 | API - Vendas 36 | ./api/api.sales 37 | 38 | > npm install 39 | 40 | > node app 41 | 42 | ## Docker compose 43 | > docker-compose up --build 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.23 2 | 3 | RUN mkdir -p /api.products 4 | 5 | WORKDIR /api.products 6 | 7 | COPY package.json /api.products 8 | 9 | RUN npm install 10 | 11 | COPY . /api.products 12 | 13 | EXPOSE 3000 14 | 15 | CMD [ "node", "app" ] -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | 4 | var bodyParser = require("body-parser"); 5 | var router = express.Router(); 6 | var http = require('http'); 7 | var productController = require('./app/controllers/app.products.controller'); 8 | var subscriberProduct = require('./app/events/app.products.subscribe.event'); 9 | var subscriberSale = require('./app/events/app.sale.subscribe.event'); 10 | var db = require('./config/db.config'); 11 | var cors = require("cors"); 12 | 13 | subscriberProduct.subscribe(); 14 | subscriberSale.subscribe(); 15 | 16 | app.set('PORT', process.env.PORT || 3000); 17 | 18 | // aqui resolvemos o problema do cors 19 | app.options('*', cors()); 20 | 21 | app.all('/*', function (req, res, next) { 22 | res.header("Access-Control-Allow-Origin", "*"); 23 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 24 | res.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type"); 25 | next(); 26 | }); 27 | 28 | app.use(bodyParser.json()); 29 | app.use(bodyParser.urlencoded({ 30 | "extended": false 31 | })); 32 | 33 | app.use('/', productController); 34 | 35 | db 36 | .authenticate() 37 | .then(() => { 38 | console.log('Conectado ao sqlite com sucesso!'); 39 | db.sync().then(function () { 40 | console.log('Sincronizando banco de dados'); 41 | }); 42 | }) 43 | .catch(err => { 44 | console.error('Erro ao conectar com sqlite:', err); 45 | }); 46 | 47 | http.createServer(app).listen(app.get('PORT'), function () { 48 | console.log('Rodando na porta ' + app.get('PORT')); 49 | }); -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/commands/app.product.command.js: -------------------------------------------------------------------------------- 1 | var poublisher = require("../events/app.product.publish.event"); 2 | const db = require('../../config/db.config'); 3 | const Product = require("../models/app.product.model"); 4 | 5 | module.exports = { 6 | 7 | create: function (model, callback) { 8 | db.transaction().then(function (t) { 9 | Product.create({ 10 | code: model.code, 11 | description: model.description, 12 | unitPrice: model.unitPrice, 13 | quantity: model.quantity 14 | }, { 15 | transaction: t 16 | }).then(function (product) { 17 | t.commit(); 18 | poublisher.created(product.dataValues); 19 | callback({ 20 | isValid: true 21 | }); 22 | }).catch(function (error) { 23 | t.rollback(); 24 | callback({ 25 | isValid: false, 26 | error: error 27 | }); 28 | }); 29 | }); 30 | }, 31 | update: function (id, model) { 32 | db.transaction().then(function (t) { 33 | Product.findById(id, { 34 | transaction: t, 35 | }).then(product => { 36 | product.update({ 37 | code: model.code, 38 | description: model.description, 39 | unitPrice: model.unitPrice, 40 | quantity: model.quantity 41 | }, { 42 | transaction: t, 43 | }).then(function () { 44 | t.commit(); 45 | if (typeof callback === 'function') { 46 | callback({ 47 | isValid: true 48 | }); 49 | } 50 | }).catch(function (error) { 51 | t.rollback(); 52 | callback({ 53 | isValid: false, 54 | error: error 55 | }); 56 | }); 57 | }, error => { 58 | callback({ 59 | isValid: false, 60 | error: 'Prodto não encontrado' 61 | }); 62 | }); 63 | 64 | 65 | }); 66 | 67 | }, 68 | 69 | delete: function (id, ) { 70 | db.transaction().then(function (t) { 71 | Product.findById(id, { 72 | transaction: t, 73 | }).then(product => { 74 | product.destroy({ 75 | transaction: t, 76 | }).then(function () { 77 | t.commit(); 78 | if (typeof callback === 'function') { 79 | callback({ 80 | isValid: true 81 | });} 82 | }).catch(function (error) { 83 | t.rollback(); 84 | callback({ 85 | isValid: false, 86 | error: error 87 | }); 88 | }); 89 | }, error => { 90 | callback({ 91 | isValid: false, 92 | error: 'Prodto não encontrado' 93 | }); 94 | }); 95 | 96 | }); 97 | 98 | }, 99 | 100 | updateStockByEvent: function (data) { 101 | db.transaction().then(function (t) { 102 | Product.findById(data.productId, { 103 | transaction: t, 104 | }).then(product => { 105 | console.info('Produto antes da alteração', product.dataValues); 106 | product.update({ 107 | quantity: (product.dataValues.quantity - data.quantity) 108 | }, { 109 | transaction: t, 110 | }).then(function () { 111 | t.commit(); 112 | }).catch(function (error) { 113 | t.rollback(); 114 | }); 115 | }, error => {}); 116 | }); 117 | 118 | } 119 | }; -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/controllers/app.products.controller.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var commands = require("../commands/app.product.command"); 4 | var repository = require("../repository/app.products.repository"); 5 | router.route("/") 6 | .get(function (req, res) { 7 | repository.all(response => { 8 | res.json(response); 9 | }); 10 | 11 | }) 12 | .post(function (req, res) { 13 | commands.create(req.body, response => { 14 | res.json(response); 15 | }); 16 | }); 17 | router.route("/:id") 18 | .get(function (req, res) { 19 | repository.get(req.params.id, response => { 20 | res.json(response); 21 | }); 22 | }) 23 | .put(function (req, res) { 24 | commands.update(req.params.id, req.body, response => { 25 | res.json(response); 26 | }); 27 | }) 28 | .delete(function (req, res) { 29 | commands.delete(req.params.id, response => { 30 | res.json(response); 31 | }); 32 | }); 33 | module.exports = router; -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/events/app.product.publish.event.js: -------------------------------------------------------------------------------- 1 | const connector = require('../../config/amqp.config'); 2 | const amqp = require('amqp'); 3 | const conn = amqp.createConnection(connector.conn); 4 | var exchange; 5 | conn.on('error', function (e) { 6 | console.log("Erro ao conectar no RabbitMQ: ", e); 7 | }); 8 | conn.on('ready', function (data) { 9 | console.log("Conectado com o RabbitMQ"); 10 | exchange = conn.exchange('product', { 11 | type: 'fanout' 12 | }); 13 | }); 14 | module.exports = { 15 | created: function (data) { 16 | console.info("[Evento] Novo evento disparado"); 17 | console.info("Novo produto", data); 18 | exchange.publish('', { 19 | "data": data, 20 | "action": "CREATE" 21 | }, {}) 22 | }, 23 | updated: function (data) { 24 | console.info("[Evento] Novo evento disparado"); 25 | console.info("Produto atualizado", data); 26 | exchange.publish('', { 27 | "data": data, 28 | "action": "UPDATE" 29 | }, {}) 30 | }, 31 | deleted: function (data) { 32 | console.info("[Evento] Novo evento disparado"); 33 | console.info("Produto removido", data); 34 | exchange.publish('', { 35 | "data": data, 36 | "action": "DELETE" 37 | }, {}) 38 | } 39 | }; -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/events/app.products.subscribe.event.js: -------------------------------------------------------------------------------- 1 | const connector = require('../../config/amqp.config'); 2 | const amqp = require('amqp'); 3 | const conn = amqp.createConnection(connector.conn); 4 | 5 | module.exports = { 6 | subscribe: function () { 7 | conn.on('error', function (e) { 8 | console.log("Erro ao se conectar com o RabbitMQ ", e); 9 | }); 10 | conn.on('ready', function () { 11 | console.log('Conectado com o RabbitMQ'); 12 | conn.exchange("product", options = { 13 | type: 'fanout' 14 | }, function (exchange) { 15 | console.log('Inscrito no product exchange'); 16 | conn.queue("product.event.queue", function (queue) { 17 | queue.bind(exchange, ''); 18 | queue.subscribe(function (message) { 19 | console.log("Novo evento recebido"); 20 | console.info('Recebendo evento de produto', message); 21 | }); 22 | }); 23 | 24 | }); 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/events/app.sale.subscribe.event.js: -------------------------------------------------------------------------------- 1 | const connector = require('../../config/amqp.config'); 2 | const amqp = require('amqp'); 3 | const conn = amqp.createConnection(connector.conn); 4 | const command = require("../commands/app.product.command"); 5 | module.exports = { 6 | subscribe: function () { 7 | conn.on('error', function (e) { 8 | console.log("Erro ao se conectar com o RabbitMQ ", e); 9 | }); 10 | conn.on('ready', function () { 11 | console.log('Conectado com o RabbitMQ'); 12 | conn.exchange("sale", options = { 13 | type: 'fanout' 14 | }, function (exchange) { 15 | console.log('Assinando a fila de vendas'); 16 | conn.queue("sale.event.queue", function (queue) { 17 | queue.bind(exchange, ''); 18 | queue.subscribe(function (message) { 19 | console.log("Novo evento recebido"); 20 | console.info('Recebendo evento de venda', message); 21 | command.updateStockByEvent(message.data); 22 | }); 23 | }); 24 | 25 | }); 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/models/app.product.model.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const db = require('../../config/db.config'); 3 | const Product = db.define('Product', { 4 | id: { 5 | autoIncrement: true, 6 | primaryKey: true, 7 | type: Sequelize.INTEGER 8 | }, 9 | code: { 10 | type: Sequelize.STRING, 11 | 12 | }, 13 | description: { 14 | type: Sequelize.STRING 15 | }, 16 | unitPrice: { 17 | type: Sequelize.DECIMAL 18 | }, 19 | quantity: { 20 | type: Sequelize.INTEGER 21 | }, 22 | minimumQuantity: { 23 | type: Sequelize.INTEGER 24 | }, 25 | }); 26 | module.exports = Product; -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/app/repository/app.products.repository.js: -------------------------------------------------------------------------------- 1 | const Product = require("../models/app.product.model"); 2 | module.exports = { 3 | all: function () { 4 | if (typeof callback === 'function') { 5 | Product.all().then(products => { 6 | callback(products); 7 | }, error => { 8 | callback({ 9 | error: error 10 | }); 11 | }); 12 | } 13 | }, 14 | get: function (id) { 15 | if (typeof callback === 'function') { 16 | Product.findById(id).then(product => { 17 | callback(product); 18 | }) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/config/amqp.config.js: -------------------------------------------------------------------------------- 1 | const conn = { 2 | host: process.env.RMQ_HOST || 'localhost' || '192.168.99.100', 3 | port: 5672, 4 | login: 'guest', 5 | password: 'guest', 6 | ssl: { 7 | enabled: false 8 | } 9 | }; 10 | const options = { 11 | defaultExchangeName: 'events', 12 | reconnect: true, 13 | reconnectBackoffStrategy: 'linear', 14 | reconnectExponentialLimit: 120000, 15 | reconnectBackoffTime: 1000 16 | }; 17 | exports.conn = conn; 18 | exports.options = options; 19 | -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/config/db.config.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = new Sequelize('', '', '', { 3 | dialect: 'sqlite', 4 | storage: './config/microservices.db' 5 | }); 6 | module.exports = sequelize; -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/config/microservices.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talits/example-course-containers/2b06f380d6dceebf640620d1971ccfa71ba81958/app_docker_compose/api/api.products/config/microservices.db -------------------------------------------------------------------------------- /app_docker_compose/api/api.products/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api.products", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Talita Bernardes", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amqp": "^0.2.6", 13 | "cors": "^2.8.4", 14 | "express": "^4.16.1", 15 | "body-parser": "^1.18.0", 16 | "sequelize": "6.0.0-beta.1", 17 | "sqlite3": "^5.0.2" 18 | } 19 | } -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.23 2 | 3 | RUN mkdir -p /api.sales 4 | 5 | WORKDIR /api.sales 6 | 7 | COPY package.json /api.sales 8 | 9 | RUN npm install 10 | 11 | COPY . /api.sales 12 | 13 | EXPOSE 3001 14 | 15 | CMD [ "node", "app" ] -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | 4 | var bodyParser = require("body-parser"); 5 | var router = express.Router(); 6 | var http = require('http'); 7 | var saleController = require('./app/controllers/app.sale.controller'); 8 | var subscriber = require('./app/events/app.sale.subscribe.event'); 9 | var db = require('./config/db.config'); 10 | var cors = require("cors"); 11 | 12 | subscriber.subscribe(); 13 | 14 | app.set('PORT', process.env.PORT || 3001); 15 | 16 | // resolvendo problema com o CORS 17 | app.options('*', cors()); 18 | 19 | app.all('/*', function (req, res, next) { 20 | res.header("Access-Control-Allow-Origin", "*"); 21 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 22 | res.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type"); 23 | next(); 24 | }); 25 | 26 | app.use(bodyParser.json()); 27 | 28 | app.use(bodyParser.urlencoded({ 29 | "extended": false 30 | })); 31 | 32 | app.use('/', saleController); 33 | 34 | db 35 | .authenticate() 36 | .then(() => { 37 | console.log('Conectado ao sqlite com sucesso!'); 38 | db.sync().then(function () { 39 | console.log('Sincronizando banco de dados'); 40 | }); 41 | }) 42 | .catch(err => { 43 | console.error('Erro ao conectar com sqlite:', err); 44 | }); 45 | 46 | http.createServer(app).listen(app.get('PORT'), function () { 47 | console.log('Rodando na porta ' + app.get('PORT')); 48 | }); -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app/commands/app.sale.command.js: -------------------------------------------------------------------------------- 1 | var poublisher = require("../events/app.sale.publish.event"); 2 | const db = require('../../config/db.config'); 3 | const Sale = require("../models/app.sale.model"); 4 | module.exports = { 5 | create: function (model, callback) { 6 | db.transaction().then(function (t) { 7 | Sale.create({ 8 | productId: model.productId, 9 | quantity: model.quantity, 10 | total: model.total 11 | }, { 12 | transaction: t 13 | }).then(function (sale) { 14 | t.commit(); 15 | poublisher.created(sale.dataValues); 16 | callback({ 17 | isValid: true 18 | }); 19 | }).catch(function (error) { 20 | t.rollback(); 21 | callback({ 22 | isValid: false, 23 | error: error 24 | }); 25 | }); 26 | }); 27 | }, 28 | update: function (id, model, callback) { 29 | db.transaction().then(function (t) { 30 | Sale.findById(id, { 31 | transaction: t, 32 | }).then(sale => { 33 | sale.update({ 34 | productId: model.productId, 35 | quantity: model.quantity, 36 | total: model.total 37 | }, { 38 | transaction: t, 39 | }).then(function () { 40 | t.commit(); 41 | callback({ 42 | isValid: true 43 | }); 44 | }).catch(function (error) { 45 | t.rollback(); 46 | callback({ 47 | isValid: false, 48 | error: error 49 | }); 50 | }); 51 | }, error => { 52 | callback({ 53 | isValid: false, 54 | error: 'Prodto não encontrado' 55 | }); 56 | }); 57 | 58 | 59 | }); 60 | 61 | }, 62 | delete: function (id) { 63 | db.transaction().then(function (t) { 64 | Sale.findById(id, { 65 | transaction: t, 66 | }).then(sale => { 67 | sale.destroy({ 68 | transaction: t, 69 | }).then(function () { 70 | t.commit(); 71 | if (typeof callback === 'function') { 72 | callback({ 73 | isValid: true 74 | }); 75 | } 76 | }).catch(function (error) { 77 | t.rollback(); 78 | callback({ 79 | isValid: false, 80 | error: error 81 | }); 82 | }); 83 | }, error => { 84 | callback({ 85 | isValid: false, 86 | error: 'Prodto não encontrado' 87 | }); 88 | }); 89 | 90 | }); 91 | 92 | } 93 | }; -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app/controllers/app.sale.controller.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var commands = require("../commands/app.sale.command"); 4 | var repository = require("../repository/app.sale.repository"); 5 | router.route("/") 6 | .get(function (req, res) { 7 | repository.all(response => { 8 | res.json(response); 9 | }); 10 | 11 | }) 12 | .post(function (req, res) { 13 | commands.create(req.body, response => { 14 | res.json(response); 15 | }); 16 | }); 17 | router.route("/:id") 18 | .get(function (req, res) { 19 | repository.get(req.params.id, response => { 20 | res.json(response); 21 | }); 22 | }) 23 | .put(function (req, res) { 24 | commands.update(req.params.id, req.body, response => { 25 | res.json(response); 26 | }); 27 | }) 28 | .delete(function (req, res) { 29 | commands.delete(req.params.id, response => { 30 | res.json(response); 31 | }); 32 | }); 33 | module.exports = router; -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app/events/app.sale.publish.event.js: -------------------------------------------------------------------------------- 1 | const connector = require('../../config/amqp.config'); 2 | const amqp = require('amqp'); 3 | const conn = amqp.createConnection(connector.conn); 4 | var exchange; 5 | conn.on('error', function (e) { 6 | console.log("Erro ao conectar no RabbitMQ: ", e); 7 | }); 8 | conn.on('ready', function (data) { 9 | console.log("Conectado com o RabbitMQ"); 10 | exchange = conn.exchange('sale', { 11 | type: 'fanout' 12 | }); 13 | }); 14 | module.exports = { 15 | created: function (data) { 16 | console.info("[Evento] Novo evento disparado"); 17 | console.info("Nova venda", data); 18 | exchange.publish('', { 19 | "data": data, 20 | "action": "CREATE" 21 | }, {}) 22 | }, 23 | updated: function (data) { 24 | console.info("[Evento] Novo evento disparado"); 25 | console.info("venda atualizada", data); 26 | exchange.publish('', { 27 | "data": data, 28 | "action": "UPDATE" 29 | }, {}) 30 | }, 31 | deleted: function (data) { 32 | console.info("[Evento] Novo evento disparado"); 33 | console.info("venda removido", data); 34 | exchange.publish('', { 35 | "data": data, 36 | "action": "DELETE" 37 | }, {}) 38 | } 39 | }; -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app/events/app.sale.subscribe.event.js: -------------------------------------------------------------------------------- 1 | const connector = require('../../config/amqp.config'); 2 | const amqp = require('amqp'); 3 | const conn = amqp.createConnection(connector.conn); 4 | 5 | module.exports = { 6 | subscribe: function () { 7 | conn.on('error', function (e) { 8 | console.log("Erro ao se conectar com o RabbitMQ ", e); 9 | }); 10 | conn.on('ready', function () { 11 | console.log('Conectado com o RabbitMQ'); 12 | conn.exchange("sale", options = { 13 | type: 'fanout' 14 | }, function (exchange) { 15 | conn.queue("sale.event.queue", function (queue) { 16 | queue.bind(exchange, ''); 17 | queue.subscribe(function (message) { 18 | console.log("Novo evento recebido"); 19 | console.info('Recebendo evento de venda', message); 20 | }); 21 | }); 22 | 23 | }); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app/models/app.sale.model.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const db = require('../../config/db.config'); 3 | const Sale = db.define('Sale', { 4 | id: { 5 | autoIncrement: true, 6 | primaryKey: true, 7 | type: Sequelize.INTEGER 8 | }, 9 | productId: { 10 | type: Sequelize.INTEGER, 11 | 12 | }, 13 | quantity: { 14 | type: Sequelize.STRING 15 | }, 16 | total: { 17 | type: Sequelize.DECIMAL 18 | } 19 | }); 20 | module.exports = Sale; -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/app/repository/app.sale.repository.js: -------------------------------------------------------------------------------- 1 | const Product = require("../models/app.sale.model"); 2 | module.exports = { 3 | all: function () { 4 | if (typeof callback === 'function') { 5 | Product.all().then(products => { 6 | callback(products); 7 | }, error => { 8 | callback({ 9 | error: error 10 | }); 11 | }); 12 | } 13 | }, 14 | get: function (id) { 15 | if (typeof callback === 'function') { 16 | Product.findById(id).then(product => { 17 | callback(product); 18 | }) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/config/amqp.config.js: -------------------------------------------------------------------------------- 1 | const conn = { 2 | host: process.env.RMQ_HOST || 'localhost' || '192.168.99.100', 3 | port: 5672, 4 | login: 'guest', 5 | password: 'guest', 6 | ssl: { 7 | enabled: false 8 | } 9 | }; 10 | const options = { 11 | defaultExchangeName: 'events', 12 | reconnect: true, 13 | reconnectBackoffStrategy: 'linear', 14 | reconnectExponentialLimit: 120000, 15 | reconnectBackoffTime: 1000 16 | }; 17 | exports.conn = conn; 18 | exports.options = options; 19 | -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/config/db.config.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = new Sequelize('', '', '', { 3 | dialect: 'sqlite', 4 | storage: './config/microservices.db' 5 | }); 6 | module.exports = sequelize; -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/config/microservices.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talits/example-course-containers/2b06f380d6dceebf640620d1971ccfa71ba81958/app_docker_compose/api/api.sales/config/microservices.db -------------------------------------------------------------------------------- /app_docker_compose/api/api.sales/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api.sales", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Talita Bernardes", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amqp": "^0.2.6", 13 | "cors": "^2.8.4", 14 | "express": "^4.16.1", 15 | "body-parser": "^1.18.0", 16 | "sequelize": "6.0.0-beta.1", 17 | "sqlite3": "^5.0.2" 18 | } 19 | } -------------------------------------------------------------------------------- /app_docker_compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | products: 4 | restart: always 5 | build: ./api/api.products/. 6 | ports: 7 | - "3000:3000" 8 | container_name: products 9 | depends_on: 10 | - rmq 11 | environment: 12 | PORT: 3000 13 | RMQ_HOST: 'rmq' 14 | DEBUG: "true" 15 | 16 | sales: 17 | restart: always 18 | build: ./api/api.sales/. 19 | depends_on: 20 | - rmq 21 | ports: 22 | - "3001:3001" 23 | container_name: sales 24 | environment: 25 | PORT: 3001 26 | RMQ_HOST: 'rmq' 27 | DEBUG: "true" 28 | 29 | rmq: 30 | image: rabbitmq:management 31 | container_name: rmq 32 | hostname: rmq 33 | ports: 34 | - '15672:15672' 35 | - '5672:5672' 36 | 37 | web: 38 | restart: always 39 | build: ./web/. 40 | ports: 41 | - "80:4004" 42 | container_name: web 43 | environment: 44 | PORT: 4004 45 | DEBUG: "true" 46 | 47 | -------------------------------------------------------------------------------- /app_docker_compose/web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:argon 2 | 3 | RUN mkdir -p web 4 | 5 | WORKDIR /web 6 | 7 | COPY package.json /web 8 | 9 | RUN npm install 10 | 11 | COPY . /web 12 | 13 | EXPOSE 4004 14 | 15 | CMD [ "node", "app" ] 16 | -------------------------------------------------------------------------------- /app_docker_compose/web/app.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | var path = require("path"); 4 | app.use("/public", express.static(path.join(__dirname, 'node_modules'))); 5 | app.use('/img', express.static(path.join(__dirname, 'public/images'))); 6 | app.use('/js', express.static(path.join(__dirname, 'public/js'))); 7 | app.use('/css', express.static(path.join(__dirname, 'public/css'))); 8 | app.use('/app', express.static(path.join(__dirname, 'app'))); 9 | app.get('/', function (req, res) { 10 | res.sendFile(path.join(__dirname + '/app/index.html')); 11 | }) 12 | app.listen(4004); 13 | console.log("Rodando na porta 4004"); -------------------------------------------------------------------------------- /app_docker_compose/web/app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('app', [ 2 | 'app.home.controllers', 3 | 'app.sales.controllers', 4 | 'app.services', 5 | 'ngRoute' 6 | ]) 7 | .config(function ($routeProvider) { 8 | $routeProvider 9 | .when("/", { 10 | templateUrl: "app/views/home.html", 11 | controller: "app.home.controller" 12 | }) 13 | .when("/sales", { 14 | templateUrl: "app/views/sales.html", 15 | controller: "app.sales.controller" 16 | }); 17 | }); -------------------------------------------------------------------------------- /app_docker_compose/web/app/controllers/app.home.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('app.home.controllers', []) 2 | .controller('app.home.controller', ['$scope', 'api.products', function ($scope, api) { 3 | $scope.product = {}; 4 | $scope.busy = { 5 | list: false, 6 | form: false 7 | }; 8 | $scope.list = function () { 9 | $scope.busy.list = true; 10 | api 11 | .query() 12 | .then(function (response) { 13 | $scope.products = response.data; 14 | $scope.busy.list = false; 15 | }) 16 | } 17 | $scope.submit = function (form) { 18 | if (form.$valid) { 19 | $scope.busy.form = true; 20 | api 21 | .save($scope.product) 22 | .then(function (response) { 23 | $scope.products = response.data; 24 | $scope.busy.form = false; 25 | $scope.product = {}; 26 | $scope.list(); 27 | }) 28 | } 29 | } 30 | 31 | $scope.validateStock = function (product) { 32 | if (product.quantity > product.minimumQuantity) 33 | return 'Saldável'; 34 | else if (product.quantity == product.minimumQuantity) 35 | return 'Ideal'; 36 | else 37 | return 'Baixo' 38 | } 39 | 40 | $scope.list(); 41 | }]); -------------------------------------------------------------------------------- /app_docker_compose/web/app/controllers/app.sales.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('app.sales.controllers', []) 2 | .controller('app.sales.controller', ['$scope', 'api.sales', 'api.products', '$location', function ($scope, sales, products, $location) { 3 | $scope.sale = {}; 4 | $scope.product = {}; 5 | $scope.busy = { 6 | list: false, 7 | form: false 8 | }; 9 | $scope.list = function () { 10 | $scope.busy.list = true; 11 | sales 12 | .query() 13 | .then(function (response) { 14 | $scope.sales = response.data; 15 | $scope.busy.list = false; 16 | }) 17 | } 18 | $scope.listProducts = function () { 19 | 20 | products 21 | .query() 22 | .then(function (response) { 23 | $scope.products = response.data; 24 | $scope.calculate(); 25 | $scope.list(); 26 | }) 27 | } 28 | $scope.submit = function (form) { 29 | if (form.$valid) { 30 | $scope.busy.form = true; 31 | $scope.sale.productId = parseInt($scope.sale.productId); 32 | sales 33 | .save($scope.sale) 34 | .then(function (response) { 35 | // $location.path("/"); 36 | $scope.busy.form = false; 37 | $scope.sale = {}; 38 | $scope.list(); 39 | }) 40 | } 41 | } 42 | 43 | $scope.findProdcutById = function (id) { 44 | if (!$scope.products) return undefined; 45 | return $scope.products.filter(function (item) { 46 | return item.id == id; 47 | })[0]; 48 | } 49 | 50 | $scope.getProdcutDescriptionById = function (id) { 51 | var product = $scope.findProdcutById(id); 52 | if (!product) return " - "; 53 | return product.description; 54 | } 55 | 56 | $scope.calculate = function () { 57 | var product = $scope.findProdcutById($scope.sale.productId); 58 | $scope.sale.total = ((!$scope.sale.quantity ? 0 : $scope.sale.quantity) * (!product ? 0 : product.unitPrice)); 59 | } 60 | 61 | $scope.listProducts(); 62 | }]); -------------------------------------------------------------------------------- /app_docker_compose/web/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 |
21 |

Microsserviços

22 | 27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 |

Arquitetura de Microsserviços, by Talita Bernardes Pereira.

38 |
39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app_docker_compose/web/app/services/app.home.srv.js: -------------------------------------------------------------------------------- 1 | angular.module('app.services', []) 2 | .factory('api.products', function ($http) { 3 | 4 | var api = {}; 5 | 6 | var url = window.location.origin.replace(':4004', '') + ':3000/'; 7 | 8 | console.info('url', url); 9 | 10 | api.save = function (data) { 11 | return $http({ 12 | method: 'POST', 13 | url: url, 14 | data: data 15 | }); 16 | } 17 | 18 | 19 | api.get = function (id) { 20 | return $http({ 21 | method: 'GET', 22 | url: url + id 23 | }); 24 | } 25 | 26 | api.query = function () { 27 | return $http({ 28 | method: 'GET', 29 | url: url 30 | }); 31 | } 32 | 33 | api.update = function (id, data) { 34 | return $http({ 35 | method: 'PUT', 36 | url: url, 37 | data: data 38 | }); 39 | } 40 | 41 | return api; 42 | }) 43 | .factory('api.sales', function ($http) { 44 | 45 | var api = {}; 46 | 47 | var url = window.location.origin.replace(':4004', '') + ':3001/'; 48 | 49 | console.info('url', url); 50 | 51 | api.save = function (data) { 52 | return $http({ 53 | method: 'POST', 54 | url: url, 55 | data: data 56 | }); 57 | } 58 | 59 | 60 | api.get = function (id) { 61 | return $http({ 62 | method: 'GET', 63 | url: url + id 64 | }); 65 | } 66 | 67 | api.query = function () { 68 | return $http({ 69 | method: 'GET', 70 | url: url 71 | }); 72 | } 73 | 74 | api.update = function (id, data) { 75 | return $http({ 76 | method: 'PUT', 77 | url: url, 78 | data: data 79 | }); 80 | } 81 | 82 | return api; 83 | }); -------------------------------------------------------------------------------- /app_docker_compose/web/app/views/home.html: -------------------------------------------------------------------------------- 1 |
2 |

Cadastrar Produto

3 |
4 |
5 |
6 | 7 | 8 | Código do produto 9 |
10 |
11 | 12 | 14 | Descrição do produto 15 |
16 |
17 |
18 |
19 | 20 | 21 | Preço unitário do produto 22 |
23 |
24 | 25 | 26 | Quantidade do produto 27 |
28 |
29 | 30 | 32 | Quantidade mínima em estoque 33 |
34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 |
42 | 43 |

Produtos cadastrados

44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
#CódigoDescriçãoPreço unitárioQuantidadeSituação do estoqueTotal em estoque
{{product.id}}{{product.code}}{{product.description}}{{ product.unitPrice | currency }}{{product.quantity}} {{ validateStock(product) }}{{ (product.quantity * product.unitPrice) | currency }}
71 |
72 |
73 |
-------------------------------------------------------------------------------- /app_docker_compose/web/app/views/sales.html: -------------------------------------------------------------------------------- 1 |
2 |

Lançamento de venda

3 |
4 |
5 |
6 | 7 | 10 | Selecione um produto 11 |
12 |
13 |
14 |
15 | 16 | 18 | Quantidade 19 |
20 |
21 | 22 |

{{ sale.total | currency }}

23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 |
32 |
33 | 34 |

Vendas lançadas

35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
#ProdutoQuantidadeTotalData
{{sale.id}}{{ getProdcutDescriptionById(sale.productId) }}{{sale.quantity}}{{ sale.total | currency }}{{ sale.createdAt | date: 'dd/MM/yyyy HH:mm' }}
60 |
61 |
62 |
63 |
-------------------------------------------------------------------------------- /app_docker_compose/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Talita Bernardes", 10 | "license": "ISC", 11 | "dependencies": { 12 | "angular": "^1.6.6", 13 | "angular-route": "^1.6.6", 14 | "express": "^4.16.2" 15 | } 16 | } -------------------------------------------------------------------------------- /app_docker_compose/web/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | 6 | /* Links */ 7 | 8 | a, 9 | a:focus, 10 | a:hover { 11 | color: #fff; 12 | } 13 | 14 | 15 | /* Custom default button */ 16 | 17 | .btn-secondary, 18 | .btn-secondary:hover, 19 | .btn-secondary:focus { 20 | color: #333; 21 | text-shadow: none; 22 | /* Prevent inheritance from `body` */ 23 | background-color: #fff; 24 | border: .05rem solid #fff; 25 | } 26 | 27 | 28 | /* 29 | * Base structure 30 | */ 31 | 32 | html, 33 | body { 34 | height: 100%; 35 | background-color: #333; 36 | } 37 | 38 | body { 39 | color: #fff; 40 | text-align: center; 41 | text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5); 42 | } 43 | 44 | 45 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 46 | 47 | .site-wrapper { 48 | display: table; 49 | width: 100%; 50 | height: 100%; 51 | /* For at least Firefox */ 52 | min-height: 100%; 53 | -webkit-box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5); 54 | box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5); 55 | } 56 | 57 | .site-wrapper-inner { 58 | display: table-cell; 59 | vertical-align: top; 60 | } 61 | 62 | .cover-container { 63 | margin-right: auto; 64 | margin-left: auto; 65 | } 66 | 67 | 68 | /* Padding for spacing */ 69 | 70 | .inner { 71 | padding: 2rem; 72 | } 73 | 74 | 75 | /* 76 | * Header 77 | */ 78 | 79 | .masthead { 80 | margin-bottom: 2rem; 81 | } 82 | 83 | .masthead-brand { 84 | margin-bottom: 0; 85 | } 86 | 87 | .nav-masthead .nav-link { 88 | padding: .25rem 0; 89 | font-weight: bold; 90 | color: rgba(255, 255, 255, .5); 91 | background-color: transparent; 92 | border-bottom: .25rem solid transparent; 93 | } 94 | 95 | .nav-masthead .nav-link:hover, 96 | .nav-masthead .nav-link:focus { 97 | border-bottom-color: rgba(255, 255, 255, .25); 98 | } 99 | 100 | .nav-masthead .nav-link+.nav-link { 101 | margin-left: 1rem; 102 | } 103 | 104 | .nav-masthead .active { 105 | color: #fff; 106 | border-bottom-color: #fff; 107 | } 108 | 109 | @media (min-width: 48em) { 110 | .masthead-brand { 111 | float: left; 112 | } 113 | .nav-masthead { 114 | float: right; 115 | } 116 | } 117 | 118 | 119 | /* 120 | * Cover 121 | */ 122 | 123 | .cover { 124 | padding: 0 1.5rem; 125 | } 126 | 127 | .cover .btn-lg { 128 | padding: .75rem 1.25rem; 129 | font-weight: bold; 130 | } 131 | 132 | 133 | /* 134 | * Footer 135 | */ 136 | 137 | .mastfoot { 138 | color: rgba(255, 255, 255, .5); 139 | } 140 | 141 | 142 | /* 143 | * Affix and center 144 | */ 145 | 146 | @media (min-width: 40em) { 147 | /* Pull out the header and footer */ 148 | .masthead { 149 | position: fixed; 150 | top: 0; 151 | } 152 | .mastfoot { 153 | position: fixed; 154 | bottom: 0; 155 | } 156 | /* Start the vertical centering */ 157 | .site-wrapper-inner { 158 | vertical-align: middle; 159 | } 160 | /* Handle the widths */ 161 | .masthead, 162 | .mastfoot, 163 | .cover-container { 164 | width: 100%; 165 | /* Must be percentage or pixels for horizontal alignment */ 166 | } 167 | } 168 | 169 | @media (min-width: 62em) { 170 | .masthead, 171 | .mastfoot, 172 | .cover-container { 173 | width: 62em; 174 | } 175 | } 176 | 177 | button { 178 | cursor: pointer 179 | } 180 | 181 | .spinner { 182 | width: 40px; 183 | height: 40px; 184 | background-color: #fff; 185 | margin: 100px auto; 186 | -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out; 187 | animation: sk-rotateplane 1.2s infinite ease-in-out; 188 | } 189 | 190 | @-webkit-keyframes sk-rotateplane { 191 | 0% { 192 | -webkit-transform: perspective(120px) 193 | } 194 | 50% { 195 | -webkit-transform: perspective(120px) rotateY(180deg) 196 | } 197 | 100% { 198 | -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) 199 | } 200 | } 201 | 202 | @keyframes sk-rotateplane { 203 | 0% { 204 | transform: perspective(120px) rotateX(0deg) rotateY(0deg); 205 | -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg) 206 | } 207 | 50% { 208 | transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg); 209 | -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) 210 | } 211 | 100% { 212 | transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); 213 | -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); 214 | } 215 | } 216 | 217 | .grid-scroll { 218 | max-height: 200px; 219 | overflow-y: auto; 220 | } -------------------------------------------------------------------------------- /app_dockerfile/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.16 2 | 3 | LABEL APP V2 4 | 5 | COPY . /app 6 | 7 | WORKDIR /app 8 | 9 | RUN npm install 10 | 11 | ENTRYPOINT [ "npm", "start" ] -------------------------------------------------------------------------------- /app_dockerfile/app.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let path = require('path'); 3 | let index = require('./routes/index'); 4 | 5 | let app = express(); 6 | app.set('views', path.join(__dirname, 'views')); 7 | app.set('view engine', 'jade'); 8 | app.use(express.static(path.join(__dirname, 'public'))); 9 | app.use('/', index); 10 | 11 | module.exports = app; 12 | -------------------------------------------------------------------------------- /app_dockerfile/dev-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Containers are so good.", 3 | "message": "Welcome." 4 | } 5 | -------------------------------------------------------------------------------- /app_dockerfile/index.js: -------------------------------------------------------------------------------- 1 | let app = require('./app'); 2 | let http = require('http'); 3 | let port = process.env.PORT || 3000; 4 | 5 | app.set('port', port); 6 | 7 | let server = http.createServer(app); 8 | server.listen(port); 9 | -------------------------------------------------------------------------------- /app_dockerfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-node-app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./index.js" 7 | }, 8 | "dependencies": { 9 | "express": "~4.16.3", 10 | "jade": "~1.11.0", 11 | "nconf": "^0.8.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app_dockerfile/public/images/cat.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talits/example-course-containers/2b06f380d6dceebf640620d1971ccfa71ba81958/app_dockerfile/public/images/cat.ico -------------------------------------------------------------------------------- /app_dockerfile/public/images/grass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app_dockerfile/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Titillium+Web:400,700'); 2 | 3 | body { 4 | font-family: "Titillium Web", Helvetica, Arial, sans-serif; 5 | color: #FFFFFF; 6 | background-color: #96ceb4; 7 | margin: 0; 8 | } 9 | 10 | h1 { 11 | margin-top: 6em; 12 | text-align: center; 13 | font-weight: 400; 14 | } 15 | 16 | a { 17 | color: #00B7FF; 18 | } 19 | 20 | footer { 21 | position: fixed; 22 | min-height: 102px; 23 | bottom: 0; 24 | background-image: url("/images/image-bg-footer.svg"); 25 | background-repeat: no-repeat; 26 | background-size: cover; 27 | width: 100%; 28 | } 29 | 30 | .logos { 31 | margin: 4em auto 0; 32 | width: 340px; 33 | display: flex; 34 | } 35 | 36 | .logos img { 37 | padding: 0 16px; 38 | } 39 | 40 | .habicat-jumping { 41 | position: absolute; 42 | left: 10%; 43 | bottom: 6em; 44 | } 45 | 46 | .habicat-set { 47 | position: absolute; 48 | right: 20%; 49 | bottom: 3px; 50 | } 51 | -------------------------------------------------------------------------------- /app_dockerfile/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var nconf = require('nconf'); 4 | 5 | 6 | nconf.file({ file: process.argv[2] || './dev-config.json' }); 7 | 8 | router.get('/', function(req, res, next) { 9 | res.render('index', { 10 | title: nconf.get('title'), 11 | message: nconf.get('message') 12 | }); 13 | }); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /app_dockerfile/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | -------------------------------------------------------------------------------- /app_dockerfile/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | 9 | footer 10 | img.habicat-set(src="images/cat.ico" alt="gato morto") 11 | --------------------------------------------------------------------------------