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 0000000..aa46da7 Binary files /dev/null and b/bulletin-board-app/fonts/geomanist/hinted-Geomanist-Book.woff2 differ diff --git a/bulletin-board-app/index.html b/bulletin-board-app/index.html new file mode 100644 index 0000000..eac1533 --- /dev/null +++ b/bulletin-board-app/index.html @@ -0,0 +1,55 @@ + + + + + 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