mirror of
https://github.com/docker-training/node-bulletin-board.git
synced 2025-05-17 11:39:30 +08:00
Add v4, with proxy, app and db
This commit is contained in:
parent
3f2894bea0
commit
1e92429385
2
bulletin-board-app/.gitignore
vendored
Normal file
2
bulletin-board-app/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
node_modules
|
10
bulletin-board-app/Dockerfile
Normal file
10
bulletin-board-app/Dockerfile
Normal file
@ -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 . .
|
21
bulletin-board-app/LICENSE
Normal file
21
bulletin-board-app/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Ryan Chenkie <ryan@elevatedigital.io> (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.
|
54
bulletin-board-app/app.js
Normal file
54
bulletin-board-app/app.js
Normal file
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
43
bulletin-board-app/backend/api.js
Normal file
43
bulletin-board-app/backend/api.js
Normal file
@ -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
|
||||
}
|
||||
};
|
34
bulletin-board-app/backend/db.js
Normal file
34
bulletin-board-app/backend/db.js
Normal file
@ -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;
|
3
bulletin-board-app/backend/index.js
Normal file
3
bulletin-board-app/backend/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
exports.index = function (req, res) {
|
||||
res.render('index');
|
||||
}
|
BIN
bulletin-board-app/fonts/geomanist/hinted-Geomanist-Book.woff2
Normal file
BIN
bulletin-board-app/fonts/geomanist/hinted-Geomanist-Book.woff2
Normal file
Binary file not shown.
55
bulletin-board-app/index.html
Normal file
55
bulletin-board-app/index.html
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Bulletin Board</title>
|
||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="site.css">
|
||||
<style>
|
||||
.form-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btn-danger {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="jumbotron">
|
||||
<h1>Welcome to the Bulletin Board</h1>
|
||||
</div>
|
||||
|
||||
<div class="container" id="events">
|
||||
<div class="col-sm-7">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3>Add an Event</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<input class="form-control" placeholder="Title" v-model="event.title">
|
||||
<textarea class="form-control" placeholder="Detail" v-model="event.detail"></textarea>
|
||||
<input type="date" class="form-control" placeholder="Date" v-model="event.date">
|
||||
<button class="btn btn-primary" v-on:click="addEvent">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="list-group">
|
||||
<a href="#" class="list-group-item" v-for="event in events">
|
||||
<h4 class="list-group-item-heading"><i class="glyphicon glyphicon-bullhorn"></i> {{ event.title }}</h4>
|
||||
<h5><i class="glyphicon glyphicon-calendar" v-if="event.date"></i> {{ event.date }}</h5>
|
||||
<p class="list-group-item-text" v-if="event.detail">{{ event.detail }}</p>
|
||||
<button class="btn btn-xs btn-danger" v-on:click="deleteEvent(event.id)">Delete</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script src="node_modules/vue/dist/vue.min.js"></script>
|
||||
<script src="node_modules/vue-resource/dist/vue-resource.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</html>
|
24
bulletin-board-app/package.json
Normal file
24
bulletin-board-app/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
20
bulletin-board-app/readme.md
Normal file
20
bulletin-board-app/readme.md
Normal file
@ -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).
|
||||
|
38
bulletin-board-app/server.js
Normal file
38
bulletin-board-app/server.js
Normal file
@ -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...');
|
50
bulletin-board-app/site.css
Normal file
50
bulletin-board-app/site.css
Normal file
@ -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;
|
||||
}
|
||||
|
10
bulletin-board-db/Dockerfile
Normal file
10
bulletin-board-db/Dockerfile
Normal file
@ -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
|
3
bulletin-board-db/init-db.sh
Normal file
3
bulletin-board-db/init-db.sh
Normal file
@ -0,0 +1,3 @@
|
||||
sleep 30s
|
||||
|
||||
/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P DockerCon!!! -i init-db.sql
|
20
bulletin-board-db/init-db.sql
Normal file
20
bulletin-board-db/init-db.sql
Normal file
@ -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;
|
4
bulletin-board-proxy/Dockerfile
Normal file
4
bulletin-board-proxy/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM nginx:1.13.6
|
||||
|
||||
RUN mkdir -p /data/nginx/cache
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
43
bulletin-board-proxy/nginx.conf
Normal file
43
bulletin-board-proxy/nginx.conf
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
29
docker-compose.yml
Normal file
29
docker-compose.yml
Normal file
@ -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:
|
Loading…
x
Reference in New Issue
Block a user