commit ecce295d7f11cfb68abaef16015befb8d620a891 Author: Ambrose Chua Date: Thu Dec 1 01:19:07 2016 +0800 Initial commit diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..3971cb2 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,7 @@ +{ + "rules": { + "indent": ["error", "tab"], + "no-tabs": "off" + }, + "extends": "airbnb" +} diff --git a/assets/bid.js b/assets/bid.js new file mode 100644 index 0000000..0c02d75 --- /dev/null +++ b/assets/bid.js @@ -0,0 +1,69 @@ +$('#placeBid').click(function () { + var name = $('#name').val(); + var phone = $('#phone').val(); + var amount = $('#amount').val(); + + var success = function success(data) { + var message = data.message; + $('#bidModal').modal('hide'); + $('#success').html( + 'Success! Your bid has been counted. ' + (message ? 'Message: ' + message : '') + ).fadeIn(100); + + update(); + }; + + var error = function error(xhr) { + var message = JSON.parse(xhr.responseText).message; + $('#bidModal').modal('hide'); + $('#error').html( + 'Error! Your bid was rejected. ' + (message ? 'Reason: ' + message : '') + ).fadeIn(100); + }; + + $('#error').fadeOut(); + $('#success').fadeOut(); + + $.ajax({ + method: 'PUT', + url: window.location.pathname.replace(/\/$/g, '') + '/bids/', + dataType: 'json', + data: { + name: name, + phone: phone, + amount: amount + }, + success: success, + error: error + }); +}); + +var update = function update() { + var success = function success(data) { + $('#highest').text('$' + (data.bid.highest || data.bid.starting)); + }; + + var error = function error(xhr) { + var message = xhr.responseText; + console.warn('error', message); + }; + + $.ajax({ + method: 'GET', + url: window.location.pathname.replace(/\/$/g, ''), + dataType: 'json', + success: success, + error: error + }); +}; + +setInterval(update, 1000); + +//$('#amount').change(function () { +setInterval(function () { + var amount = $('#amount').val(); + $('#bidModal').find('.modal-title').text('Place bid of $' + amount); +}, 500); +//}); + + diff --git a/assets/item.css b/assets/item.css new file mode 100644 index 0000000..1d2d818 --- /dev/null +++ b/assets/item.css @@ -0,0 +1,22 @@ +.crop-wide { + display: none; +} +.crop-narrow { + display: block; +} + +.images { + margin: -1em; +} +.image { + margin: 1em; +} + +@media (min-width: 768px) { + .crop-wide { + display: block; + } + .crop-narrow { + display: none; + } +} diff --git a/assets/items.css b/assets/items.css new file mode 100644 index 0000000..69f499b --- /dev/null +++ b/assets/items.css @@ -0,0 +1,17 @@ +.items { + margin: -1em; +} +.item { + margin: 1em; +} + +@media (min-width: 768px) { + .items { + display: flex; + flex-wrap: wrap; + } + .item { + flex: 1 0 auto; + width: calc(50% - 4em); + } +} diff --git a/assets/main.css b/assets/main.css new file mode 100644 index 0000000..e88fde5 --- /dev/null +++ b/assets/main.css @@ -0,0 +1,3 @@ +main { + padding-top: 6vw; +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..3810652 --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +/* eslint-env node, es6 */ + +const express = require('express'); +const routes = require('./routes'); + +const app = express(); +app.use(routes); + +app.listen(process.env.PORT || 8080, process.env.IP || "127.0.0.1"); diff --git a/logic.js b/logic.js new file mode 100644 index 0000000..1b634fb --- /dev/null +++ b/logic.js @@ -0,0 +1,76 @@ +/* eslint-env node, es6 */ + +const fs = require('fs'); +const path = require('path'); + +const low = require('lowdb'); +const fileAsync = require('lowdb/lib/file-async'); + +class Logic { + constructor() { + const BASE_DIR = path.join(__dirname, 'data'); + const DB_FILE = path.join(BASE_DIR, 'data.json'); + const CONFIG_FILE = path.join(BASE_DIR, 'config.json'); + + this.config = require(CONFIG_FILE); // TODO: Replace with readFileSync + this.db = low(DB_FILE, { + storage: fileAsync, + }); + this.db.defaults({ items: [] }).value(); + this.baseDir = BASE_DIR; + } + + getConfig() { + return this.config; + } + + async getIndex() { + return await new Promise((resolve, reject) => { + fs.readFile(path.join(this.baseDir, 'index.html'), 'utf8', (err, content) => { + if (err) return reject(err); + return resolve(content); + }); + }); + } + + async getItems() { + return this.db.get('items').value(); + } + + async getItem(id) { + const dbitem = this.db.get('items').find({ id }); + const item = Object.assign({}, dbitem.value()); + item.bid.next = (item.bid.highest || item.bid.starting) + item.bid.increment; + return item; + } + + async putItemBid(id, bid) { + const dbitem = this.db.get('items').find({ id }); + const item = Object.assign({}, dbitem.value()); + item.bid.next = (item.bid.highest || item.bid.starting) + item.bid.increment; + + const amount = parseInt(bid.amount, 10); + const name = bid.name; + const phone = bid.phone; + if (!item.bid.bids) { + dbitem.get('bid').set('bids', []).value(); + } + if (amount < item.bid.next) { + throw new Error(`Please bid $${item.bid.next} or higher`); + } + if (!name) { + throw new Error('Please give us your name'); + } + if (!phone || phone.length < 8) { + throw new Error('Please give us a valid phone number'); + } + dbitem.get('bid').get('bids').push({ + amount, + name, + phone, + }).value(); + dbitem.get('bid').set('highest', amount).value(); + } +} + +module.exports = Logic; diff --git a/package.json b/package.json new file mode 100644 index 0000000..97b86ba --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-auction", + "scripts": { + "start": "node --harmony index.js" + }, + "dependencies": { + "body-parser": "^1.15.2", + "bootstrap": "^4.0.0-alpha.5", + "express": "^4.14.0", + "express-handlebars": "^3.0.0", + "imgix.js": "^3.0.4", + "jquery": "^3.1.1", + "lowdb": "^0.14.0" + } +} diff --git a/routes.js b/routes.js new file mode 100644 index 0000000..f0d234a --- /dev/null +++ b/routes.js @@ -0,0 +1,93 @@ +/* eslint-env node, es6 */ + +const path = require('path'); + +const express = require('express'); +const bodyparser = require('body-parser'); +const hbs = require('express-handlebars'); +const Logic = require('./logic'); + +const routes = express(); +routes.engine('hbs', hbs({ + extname: '.hbs', + defaultLayout: 'default', + helpers: { + ifincludes: (a, b, options) => { + if (a && a.includes && a.includes(b)) { + return options.fn(this); + } + return options.inverse(this); + }, + }, +})); +routes.set('view engine', 'hbs'); +routes.use('/jquery', express.static(path.join(__dirname, 'node_modules/jquery/dist'))); +routes.use('/bootstrap', express.static(path.join(__dirname, 'node_modules/bootstrap/dist'))); +routes.use('/imgix.js', express.static(path.join(__dirname, 'node_modules/imgix.js/dist'))); +routes.use('/assets', express.static(path.join(__dirname, 'assets'))); +routes.use(bodyparser.urlencoded({ + extended: true, +})); + +const logic = new Logic(); +const config = logic.getConfig(); + +routes.get('/', (req, res, next) => { + logic.getIndex().then((content) => { + res.render('index', { + content, + config, + }); + }).catch((err) => { + next(err); + }); +}); + +routes.get('/items/', (req, res, next) => { + logic.getItems().then((items) => { + switch (req.accepts(['json', 'html'])) { + case 'json': + return res.json(items); + case 'html': + default: + return res.render('items', { + items, + config, + }); + } + }).catch((err) => { + next(err); + }); +}); + +routes.get('/items/:id', (req, res, next) => { + logic.getItem(parseInt(req.params.id, 10)).then((item) => { + switch (req.accepts(['json', 'html'])) { + case 'json': + return res.json(item); + case 'html': + default: + return res.render('item', { + item, + config, + }); + } + }).catch((err) => { + next(err); + }); +}); + +routes.put('/items/:id/bids/', (req, res) => { + logic.putItemBid(parseInt(req.params.id, 10), req.body).then(() => { + res.json({ + success: true, + }); + }).catch((err) => { + res.status(400).json({ + error: true, + message: err.message, + }); + }); +}); + +module.exports = routes; diff --git a/views/index.hbs b/views/index.hbs new file mode 100644 index 0000000..ec2a45b --- /dev/null +++ b/views/index.hbs @@ -0,0 +1,11 @@ +
+
+
+ {{{content}}} + +
+ Go to auction +
+
+
+
diff --git a/views/item.hbs b/views/item.hbs new file mode 100644 index 0000000..fede355 --- /dev/null +++ b/views/item.hbs @@ -0,0 +1,85 @@ +{{#if item}} +{{#with item}} +
+
+
+ + +
+
+
+
+
+ {{#each images}} + + {{/each}} +
+
+
+
Description
+

{{description}}

+
{{#if bid.highest}}Highest{{else}}Starting{{/if}} bid
+

${{#if bid.highest}}{{bid.highest}}{{else}}{{bid.starting}}{{/if}}

+
+
+
+ $ + + + + +
+
+
+
+
+
+{{/with}} +{{else}} +
+
+
+
+ Error! Item not found. +
+
+
+
+{{/if}} + + + diff --git a/views/items.hbs b/views/items.hbs new file mode 100644 index 0000000..47fc955 --- /dev/null +++ b/views/items.hbs @@ -0,0 +1,16 @@ +
+
+
+
+ {{#each items}} + + {{/each}} +
+
+
+
diff --git a/views/layouts/default.hbs b/views/layouts/default.hbs new file mode 100644 index 0000000..d8ef569 --- /dev/null +++ b/views/layouts/default.hbs @@ -0,0 +1,19 @@ + + + + + + {{#if title}}{{title}}{{else}}{{@root.config.title}}{{/if}} + + + + + + + {{{body}}} + + + + + +