From 1e92429385c62ff24c0f9997a6aad7a8c9a057f7 Mon Sep 17 00:00:00 2001 From: Elton Stoneman Date: Mon, 20 Nov 2017 17:00:54 +0000 Subject: [PATCH] Add v4, with proxy, app and db --- bulletin-board-app/.gitignore | 2 + bulletin-board-app/Dockerfile | 10 ++++ bulletin-board-app/LICENSE | 21 +++++++ bulletin-board-app/app.js | 54 +++++++++++++++++ bulletin-board-app/backend/api.js | 43 ++++++++++++++ bulletin-board-app/backend/db.js | 34 +++++++++++ bulletin-board-app/backend/index.js | 3 + .../geomanist/hinted-Geomanist-Book.woff2 | Bin 0 -> 23504 bytes bulletin-board-app/index.html | 55 ++++++++++++++++++ bulletin-board-app/package.json | 24 ++++++++ bulletin-board-app/readme.md | 20 +++++++ bulletin-board-app/server.js | 38 ++++++++++++ bulletin-board-app/site.css | 50 ++++++++++++++++ bulletin-board-db/Dockerfile | 10 ++++ bulletin-board-db/init-db.sh | 3 + bulletin-board-db/init-db.sql | 20 +++++++ bulletin-board-proxy/Dockerfile | 4 ++ bulletin-board-proxy/nginx.conf | 43 ++++++++++++++ docker-compose.yml | 29 +++++++++ 19 files changed, 463 insertions(+) create mode 100644 bulletin-board-app/.gitignore create mode 100644 bulletin-board-app/Dockerfile create mode 100644 bulletin-board-app/LICENSE create mode 100644 bulletin-board-app/app.js create mode 100644 bulletin-board-app/backend/api.js create mode 100644 bulletin-board-app/backend/db.js create mode 100644 bulletin-board-app/backend/index.js create mode 100644 bulletin-board-app/fonts/geomanist/hinted-Geomanist-Book.woff2 create mode 100644 bulletin-board-app/index.html create mode 100644 bulletin-board-app/package.json create mode 100644 bulletin-board-app/readme.md create mode 100644 bulletin-board-app/server.js create mode 100644 bulletin-board-app/site.css create mode 100644 bulletin-board-db/Dockerfile create mode 100644 bulletin-board-db/init-db.sh create mode 100644 bulletin-board-db/init-db.sql create mode 100644 bulletin-board-proxy/Dockerfile create mode 100644 bulletin-board-proxy/nginx.conf create mode 100644 docker-compose.yml diff --git a/bulletin-board-app/.gitignore b/bulletin-board-app/.gitignore new file mode 100644 index 0000000..91dfed8 --- /dev/null +++ b/bulletin-board-app/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules \ No newline at end of file diff --git a/bulletin-board-app/Dockerfile b/bulletin-board-app/Dockerfile new file mode 100644 index 0000000..22e6ce5 --- /dev/null +++ b/bulletin-board-app/Dockerfile @@ -0,0 +1,10 @@ +FROM node:6.11.5 + +WORKDIR /usr/src/app +COPY package.json . +RUN npm install + +EXPOSE 8080 +CMD [ "npm", "start" ] + +COPY . . \ No newline at end of file diff --git a/bulletin-board-app/LICENSE b/bulletin-board-app/LICENSE new file mode 100644 index 0000000..e1d1dc8 --- /dev/null +++ b/bulletin-board-app/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Ryan Chenkie (http://elevatedigital.io) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bulletin-board-app/app.js b/bulletin-board-app/app.js new file mode 100644 index 0000000..a2b2b95 --- /dev/null +++ b/bulletin-board-app/app.js @@ -0,0 +1,54 @@ +new Vue({ + el: '#events', + + data: { + event: { title: '', detail: '', date: '' }, + events: [] + }, + + ready: function () { + this.fetchEvents(); + }, + + methods: { + + fetchEvents: function () { + var events = []; + this.$http.get('/api/events') + .success(function (events) { + this.$set('events', events); + console.log(events); + }) + .error(function (err) { + console.log(err); + }); + }, + + addEvent: function () { + if (this.event.title.trim()) { + this.$http.post('/api/events', this.event) + .success(function (res) { + this.events.push(this.event); + console.log('Event added!'); + }) + .error(function (err) { + console.log(err); + }); + } + }, + + deleteEvent: function (id) { + if (confirm('Are you sure you want to delete this event?')) { + this.$http.delete('api/events/' + id) + .success(function (res) { + console.log(res); + var index = this.events.find(x => x.id === id) + this.events.splice(index, 1); + }) + .error(function (err) { + console.log(err); + }); + } + } + } +}); \ No newline at end of file diff --git a/bulletin-board-app/backend/api.js b/bulletin-board-app/backend/api.js new file mode 100644 index 0000000..0edf7fe --- /dev/null +++ b/bulletin-board-app/backend/api.js @@ -0,0 +1,43 @@ +var db = require('./db.js'); + +exports.events = function (req, res) { + console.log('Loading DB events...'); + db.Events + .findAll() + .then(events => { + console.log('Fetched events, count: ' + events.length); + res.json(events); + }) + .catch(err => { + console.error('** Fetch failed: ', err); + }); +}; + +exports.event = function (req, res) { + console.log('Handling event call, method: ' + req.method + ', event ID: ' + req.params.eventId) + switch(req.method) { + case "DELETE": + db.Events + .destroy({ + where: { + id: req.params.eventId + } + }).then(function() { + console.log('Deleted event with id: ' + req.params.eventId) + res.status(200).end(); + }); + break + case "POST": + db.Events + .create({ + title: req.body.title, + detail: req.body.detail, + date: req.body.date + }) + .then(function() { + res.send('{}'); + res.status(201).end(); + }); + break + } +}; \ No newline at end of file diff --git a/bulletin-board-app/backend/db.js b/bulletin-board-app/backend/db.js new file mode 100644 index 0000000..cb62262 --- /dev/null +++ b/bulletin-board-app/backend/db.js @@ -0,0 +1,34 @@ +var Sequelize = require('sequelize'); +var username = 'sa'; +var password = 'DockerCon!!!'; +var host = 'bb-db'; +var dbName = 'BulletinBoard'; + +var sequelize = new Sequelize(dbName, username, password, { + dialect: 'mssql', + host: host, + port: 1433, + dialectOptions: { + requestTimeout: 30000 + } +}); + +sequelize + .authenticate() + .then(() => { + console.log('Successful connection to SQL Server.'); + }) + .catch(err => { + console.error('** SQL Server connection failed: ', err); + process.exit(1); + }); + +var Event = sequelize.define('event', { + title: Sequelize.STRING, + detail: Sequelize.STRING, + date: Sequelize.DATE +}); + +Event.sync(); + +exports.Events = Event; \ No newline at end of file diff --git a/bulletin-board-app/backend/index.js b/bulletin-board-app/backend/index.js new file mode 100644 index 0000000..6270938 --- /dev/null +++ b/bulletin-board-app/backend/index.js @@ -0,0 +1,3 @@ +exports.index = function (req, res) { + res.render('index'); +} \ No newline at end of file diff --git a/bulletin-board-app/fonts/geomanist/hinted-Geomanist-Book.woff2 b/bulletin-board-app/fonts/geomanist/hinted-Geomanist-Book.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..aa46da7eed2c71b11b9fde4dfe33cc8d7a87f3fd GIT binary patch literal 23504 zcmV)0K+eB+Pew8T0RR9109()i5&!@I0UtO309$7O0RR9100000000000000000000 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 + + + + Bulletin Board + + + + + + +
+

Welcome to the Bulletin Board

+
+ +
+
+
+
+

Add an Event

+
+
+
+ + + + +
+
+
+
+ +
+ + + + + + \ No newline at end of file diff --git a/bulletin-board-app/package.json b/bulletin-board-app/package.json new file mode 100644 index 0000000..ceea631 --- /dev/null +++ b/bulletin-board-app/package.json @@ -0,0 +1,24 @@ +{ + "name": "vue-event-bulletin", + "version": "1.0.0", + "description": "Demo application for the scotch.io tutorial", + "main": "server.js", + "author": "Ryan Chenkie, Jason Lam", + "license": "MIT", + "dependencies": { + "bootstrap": "^3.3.6", + "ejs": "^2.3.4", + "express": "^4.13.3", + "morgan": "^1.6.1", + "vue": "^1.0.10", + "vue-resource": "^0.1.17", + "tedious": "^2.0.1", + "sequelize": "^4.20.1" + }, + "devDependencies": { + "body-parser": "^1.14.1", + "errorhandler": "^1.4.2", + "method-override": "^2.3.5", + "morgan": "^1.6.1" + } +} diff --git a/bulletin-board-app/readme.md b/bulletin-board-app/readme.md new file mode 100644 index 0000000..d2b5e37 --- /dev/null +++ b/bulletin-board-app/readme.md @@ -0,0 +1,20 @@ +## Vue Events Bulletin Board + +This is the code for the Vue.js [tutorial on Scotch.io](https://scotch.io/tutorials/build-a-single-page-time-tracking-app-with-vue-js-introduction). In the tutorial we build a events bulletin board application and cover the basics of [Vue](http://vuejs.org/). + +## Installation + +1. Run `npm install`. +2. Run `node server.js`. +3. Visit [http://localhost:8080](http://localhost:8080). + +## RESTful API (contributed by Jason Lam) + +1. **Use Node.js & Express for backend server and router.** +2. **RESTful requests towards the server to simulate CRUD on *events* model, instead of local hardcoded ones.** +3. Translated into Traditional Chinese. + +## RESTful API written in Go + +If you would like to use a backend written in Go, [thewhitetulip](http://github.com/thewhitetulip) has written on. See [the source code](https://github.com/thewhitetulip/go-vue-events). + diff --git a/bulletin-board-app/server.js b/bulletin-board-app/server.js new file mode 100644 index 0000000..dbdcb2d --- /dev/null +++ b/bulletin-board-app/server.js @@ -0,0 +1,38 @@ +var express = require('express'), + bodyParser = require('body-parser'), + methodOverride = require('method-override'), + errorHandler = require('errorhandler'), + morgan = require('morgan'), + routes = require('./backend'), + api = require('./backend/api'); + +var app = module.exports = express(); + +app.engine('html', require('ejs').renderFile); +app.set('view engine', 'html'); +app.use(morgan('dev')); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); +app.use(methodOverride()); +app.use(express.static(__dirname + '/')); +app.use('/build', express.static('public')); + +var env = process.env.NODE_ENV; +if ('development' == env) { + app.use(errorHandler({ + dumpExceptions: true, + showStack: true + })); +} + +if ('production' == app.get('env')) { + app.use(errorHandler()); +} + +app.get('/', routes.index); +app.get('/api/events', api.events); +app.post('/api/events', api.event); +app.delete('/api/events/:eventId', api.event) + +app.listen(8080); +console.log('Magic happens on port 8080...'); \ No newline at end of file diff --git a/bulletin-board-app/site.css b/bulletin-board-app/site.css new file mode 100644 index 0000000..c7979bc --- /dev/null +++ b/bulletin-board-app/site.css @@ -0,0 +1,50 @@ +/* v2 */ +@font-face { + font-family: 'Geomanist Book'; + src: url(fonts/geomanist/hinted-Geomanist-Book.eot); + src: url(fonts/geomanist/hinted-Geomanist-Book.eot?#iefix) format('embedded-opentype'),url(../fonts/geomanist/hinted-Geomanist-Book.woff2) format('woff2'),url(../fonts/geomanist/hinted-Geomanist-Book.woff) format('woff'),url(../fonts/geomanist/hinted-Geomanist-Book.ttf) format('truetype'),url(../fonts/geomanist/hinted-Geomanist-Book.svg#Geomanist-Book) format('svg'); + font-weight: 400; + font-style: normal +} + +body { + padding-bottom: 20px; + background-color: rgb(98, 41, 145); + color: #FFF; + font-size: 20px; +} + +/* Wrapping element */ +/* Set some basic padding to keep content from hitting the edges */ +.body-content { + padding-left: 15px; + padding-right: 15px; +} + +/* Set widths on the form inputs since otherwise they're 100% wide */ +input, +select, +textarea { + max-width: 280px; +} + + +/* Responsive: Portrait tablets and up */ +@media screen and (min-width: 768px) { + .body-content { + padding: 0; + } +} + +/* v2 */ + +.jumbotron { + font-family: 'Geomanist Book'; + background-color: #380a66; +} + +h1, h2 { + font-family: 'Geomanist Book'; + text-align: center; +} + diff --git a/bulletin-board-db/Dockerfile b/bulletin-board-db/Dockerfile new file mode 100644 index 0000000..16ff848 --- /dev/null +++ b/bulletin-board-db/Dockerfile @@ -0,0 +1,10 @@ +FROM microsoft/mssql-server-linux:2017-CU1 + +ENV ACCEPT_EULA=Y \ + MSSQL_SA_PASSWORD=DockerCon!!! + +WORKDIR /init +COPY init-db.* ./ + +RUN chmod +x ./init-db.sh +RUN /opt/mssql/bin/sqlservr & ./init-db.sh \ No newline at end of file diff --git a/bulletin-board-db/init-db.sh b/bulletin-board-db/init-db.sh new file mode 100644 index 0000000..58db756 --- /dev/null +++ b/bulletin-board-db/init-db.sh @@ -0,0 +1,3 @@ +sleep 30s + +/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P DockerCon!!! -i init-db.sql \ No newline at end of file diff --git a/bulletin-board-db/init-db.sql b/bulletin-board-db/init-db.sql new file mode 100644 index 0000000..7ea5f58 --- /dev/null +++ b/bulletin-board-db/init-db.sql @@ -0,0 +1,20 @@ +CREATE DATABASE BulletinBoard; +GO + +USE BulletinBoard; + +CREATE TABLE Events ( + Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, + Title NVARCHAR(50), + Detail NVARCHAR(200), + [Date] DATETIMEOFFSET, + CreatedAt DATETIMEOFFSET NOT NULL, + UpdatedAt DATETIMEOFFSET NOT NULL +); + +INSERT INTO Events (Title, Detail, [Date], CreatedAt, UpdatedAt) VALUES +(N'Docker for Beginners', N'Introduction to Docker using Node.js', '2017-11-21', GETDATE(), GETDATE()), +(N'Advanced Orchestration', N'Deep dive into Docker Swarm', '2017-12-25', GETDATE(), GETDATE()), +(N'Docker on Windows', N'From 101 to production', '2018-01-01', GETDATE(), GETDATE()); + +SELECT * FROM BulletinBoard.dbo.Events; \ No newline at end of file diff --git a/bulletin-board-proxy/Dockerfile b/bulletin-board-proxy/Dockerfile new file mode 100644 index 0000000..91f2408 --- /dev/null +++ b/bulletin-board-proxy/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:1.13.6 + +RUN mkdir -p /data/nginx/cache +COPY nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/bulletin-board-proxy/nginx.conf b/bulletin-board-proxy/nginx.conf new file mode 100644 index 0000000..76316c6 --- /dev/null +++ b/bulletin-board-proxy/nginx.conf @@ -0,0 +1,43 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:30m inactive=24h max_size=200m use_temp_path=off; + + proxy_pass_request_headers on; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header Connection keep-alive; + + gzip on; + gzip_proxied any; + + add_header X-Host $hostname; + add_header X-Cache-Status $upstream_cache_status; + + map $sent_http_content_type $expires { + default off; + ~image/ 6M; + } + + server { + listen 80 default_server; + server_name _; + + expires $expires; + + location / { + proxy_pass http://bb-app:8080/; + proxy_cache STATIC; + proxy_cache_valid 200 1h; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a8536e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.3' + +services: + + bb-app: + image: sixeyed/bulletin-board-app + build: + context: ./bulletin-board-app + networks: + - bb-net + + bb-db: + image: sixeyed/bulletin-board-db + build: + context: ./bulletin-board-db + networks: + - bb-net + + bb-proxy: + image: sixeyed/bulletin-board-proxy + build: + context: ./bulletin-board-proxy + ports: + - "80:80" + networks: + - bb-net + +networks: + bb-net: \ No newline at end of file