diff --git a/app/controllers/items.js b/app/controllers/items.js index 32f80f8..3f47c37 100644 --- a/app/controllers/items.js +++ b/app/controllers/items.js @@ -9,3 +9,15 @@ exports.create = function(req, res){ }); }; +exports.index = function(req, res){ + Item.findForSale(req.query, function(err, items){ + res.render('items/index', {items:items}); + }); +}; + +exports.markonsale = function(req, res){ + Item.markOnSale(req.params.itemId, function(){ + res.redirect('/profile'); + }); +}; + diff --git a/app/models/item.js b/app/models/item.js index cbabc44..22a2251 100644 --- a/app/models/item.js +++ b/app/models/item.js @@ -38,6 +38,18 @@ Item.create = function(data, cb){ Item.collection.save(i, cb); }; +Item.findForSale = function(query, cb){ + var filter = {onSale:true}, + sort = {}; + if(query.sort){sort[query.sort] = query.order * 1;} + Item.collection.find(filter).sort(sort).toArray(cb); +}; + +Item.markOnSale = function(itemId, cb){ + var _id = Mongo.ObjectID(itemId); + Item.collection.update({_id:_id}, {$set: {onSale: true}}, cb); +}; + module.exports = Item; function getNumberOfBids(item, cb){ diff --git a/app/routes/routes.js b/app/routes/routes.js index c2f426c..222bf62 100644 --- a/app/routes/routes.js +++ b/app/routes/routes.js @@ -35,6 +35,8 @@ module.exports = function(app, express){ app.put('/profile', users.update); app.get('/profile', users.profile); app.post('/items', items.create); + app.get('/marketplace', items.index); + app.put('/items/:itemId', items.markonsale); console.log('Express: Routes Loaded'); }; diff --git a/app/static/css/style.css b/app/static/css/style.css index 22faa3b..193578f 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -1 +1 @@ -.profile-photo{height:200px;width:150px;background-size:cover}.item-photo{height:100px;width:100px;background-size:cover} \ No newline at end of file +.profile-photo{height:200px;width:150px;background-size:cover}.item-photo{height:100px;width:100px;background-size:cover}#marketplace-map{height:400px} \ No newline at end of file diff --git a/app/static/css/style.less b/app/static/css/style.less index 8711eb1..52f9fbc 100644 --- a/app/static/css/style.less +++ b/app/static/css/style.less @@ -13,3 +13,7 @@ body{ width: 100px; background-size: cover; } + +#marketplace-map { + height: 400px; +} diff --git a/app/static/img/marker.png b/app/static/img/marker.png new file mode 100644 index 0000000..5876a56 Binary files /dev/null and b/app/static/img/marker.png differ diff --git a/app/static/js/user/items-index.js b/app/static/js/user/items-index.js new file mode 100644 index 0000000..e9e6ac7 --- /dev/null +++ b/app/static/js/user/items-index.js @@ -0,0 +1,22 @@ +/* jshint unused:false, camelcase:false */ +/* global google, cartographer, addMarker */ + +(function(){ + 'use strict'; + + var map; + + $(document).ready(function(){ + map = cartographer('marketplace-map', 39.8282, -98.5795, 4); + var rows = $('.item').toArray(); + // console.log(rows); + if(rows.length){ + rows.forEach(function(row){ + // console.log(row); + addMarker(map, parseFloat($(row).attr('data-lat')), parseFloat($(row).attr('data-lng')), $(row).attr('data-loc'), '/img/marker.png'); + }); + } + }); + +})(); + diff --git a/app/static/js/user/main.js b/app/static/js/user/main.js index dd6924f..204628b 100644 --- a/app/static/js/user/main.js +++ b/app/static/js/user/main.js @@ -1,8 +1,29 @@ -(function(){ - 'use strict'; +/* jshint unused:false, camelcase:false */ +/* global google */ - $(document).ready(function(){ +function geocode(address, cb){ + 'use strict'; + var geocoder = new google.maps.Geocoder(); + geocoder.geocode({address:address}, function(results, status){ + //console.log('results', results); + var name = results[0].formatted_address, + lat = results[0].geometry.location.lat(), + lng = results[0].geometry.location.lng(); + cb(name, lat, lng); }); +} -})(); +function cartographer(cssId, lat, lng, zoom){ + 'use strict'; + var mapOptions = {center: new google.maps.LatLng(lat, lng), zoom: zoom, mapTypeId: google.maps.MapTypeId.ROADMAP}, + map = new google.maps.Map(document.getElementById(cssId), mapOptions); + return map; +} + +function addMarker(map, lat, lng, name, icon){ + 'use strict'; + var latLng = new google.maps.LatLng(lat, lng); + // console.log(latLng); + new google.maps.Marker({map: map, position: latLng, title: name, animation: google.maps.Animation.DROP, icon: icon}); +} diff --git a/app/static/js/user/profile.js b/app/static/js/user/profile.js index 3d9cc2f..8e8b551 100644 --- a/app/static/js/user/profile.js +++ b/app/static/js/user/profile.js @@ -1,34 +1,23 @@ /* jshint unused:false, camelcase:false */ -/* global google */ +/* global google, geocode */ (function(){ 'use strict'; $(document).ready(function(){ - $('button[type=submit]').click(geocodeAndSubmit); + $('#submitItem').click(geocodeAndSubmit); }); function geocodeAndSubmit(e){ var location = $('input[name=location]').val(); geocode(location, function(name, lat, lng){ $('input[name=location]').val(name); - $('form').append(''); - $('form').append(''); - $('form').submit(); + $('#newItem').append(''); + $('#newItem').append(''); + $('#newItem').submit(); }); e.preventDefault(); } - function geocode(address, cb){ - var geocoder = new google.maps.Geocoder(); - geocoder.geocode({address:address}, function(results, status){ - //console.log('results', results); - var name = results[0].formatted_address, - lat = results[0].geometry.location.lat(), - lng = results[0].geometry.location.lng(); - cb(name, lat, lng); - }); - } - })(); diff --git a/app/views/items/index.jade b/app/views/items/index.jade new file mode 100644 index 0000000..3a760c9 --- /dev/null +++ b/app/views/items/index.jade @@ -0,0 +1,28 @@ +extends ../shared/template +block content + h2 Profile + .panel.panel-default + .panel-body + .row + .col-xs-12 + #marketplace-map + .row + .col-xs-12 + table.table + thead + tr + th Photo + th Name + th Location + th Description + tbody + each item in items + tr.item(data-loc='#{item.location}', data-lat='#{item.lat}', data-lng='#{item.lng}') + td.item-photo(style='background-image:url(#{item.photo})') + td: a(href='/items/#{item._id}')= item.name + td= item.location + td= item.description + +block scripts + script(src='/js/user/items-index.js') + diff --git a/app/views/users/profile.jade b/app/views/users/profile.jade index 5a698c5..0f0ffd3 100644 --- a/app/views/users/profile.jade +++ b/app/views/users/profile.jade @@ -23,7 +23,7 @@ block content if user.photo .profile-photo(style='background-image:url(#{user.photo});') .col-xs-4 - form(role='form', method='post', action='/items') + form#newItem(role='form', method='post', action='/items') .form-group label Name input.form-control(name='name', type='text', placeholder='Red Wine') @@ -39,7 +39,7 @@ block content .form-group label Description textarea.form-control(name='description') - button.btn.btn-default(type='submit') Add Vintage + button#submitItem.btn.btn-default(type='submit') Add Vintage .row .col-xs-6 table.table @@ -48,15 +48,22 @@ block content th Photo th Name th Out for Trade? - th Mark For Sale + th Want to Trade? tbody each item in items unless item.onSale tr td.item-photo(style='background-image:url(#{item.photo});') td= item.name - td= item.isBiddable - td Checkbox + submit button + if(!item.isBiddable) + td No + if(item.isBiddable) + td Yes + td + form(method='post', action='/items/#{item._id}') + input(type='hidden', name='_method', value='put') + button(type='submit') Trade + .col-xs-1 .col-xs-5 table.table diff --git a/test/acceptance/items.js b/test/acceptance/items.js new file mode 100644 index 0000000..463d960 --- /dev/null +++ b/test/acceptance/items.js @@ -0,0 +1,73 @@ +/* global describe, before, beforeEach, it */ + +'use strict'; + +process.env.DB = 'wine-seller-test'; + +var expect = require('chai').expect, + cp = require('child_process'), + app = require('../../app/index'), + cookie = null, + request = require('supertest'); + +describe('users', function(){ + before(function(done){ + request(app).get('/').end(done); + }); + + beforeEach(function(done){ + cp.execFile(__dirname + '/../scripts/clean-db.sh', [process.env.DB], {cwd:__dirname + '/../scripts'}, function(err, stdout, stderr){ + request(app) + .post('/login') + .send('email=nodeapptest%2Bbob%40gmail.com&password=1234') + .end(function(err, res){ + cookie = res.headers['set-cookie'][0]; + done(); + }); + }); + }); + + describe('post /items', function(){ + it('should redirect to the profile page', function(done){ + request(app) + .post('/items') + .set('cookie', cookie) + .send('name=Red+Wine&photo=http%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fc%2Fcc%2FFrench_beaujolais_red_wine_bottle.jpg&tags=red%2C+wine&location=Nashville%2C+TN%2C+USA&description=Red+Wine') + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/profile'); + done(); + }); + }); + }); + + describe('get /marketplace', function(){ + it('should display the marketplace page', function(done){ + request(app) + .get('/marketplace') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(200); + expect(res.text).to.include('Marketplace'); + expect(res.text).to.include('White Wine'); + done(); + }); + }); + }); + + describe('put /profile', function(){ + it('should redirect to the profile page after item is put on sale', function(done){ + request(app) + .post('/items/a00000000000000000000001') + .send('_method=put') + .set('cookie', cookie) + .end(function(err, res){ + expect(res.status).to.equal(302); + expect(res.headers.location).to.equal('/profile'); + done(); + }); + }); + }); + +}); + diff --git a/test/acceptance/npm-debug.log b/test/acceptance/npm-debug.log deleted file mode 100644 index 4c4d405..0000000 --- a/test/acceptance/npm-debug.log +++ /dev/null @@ -1,17 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ '/usr/local/node/bin/node', '/usr/local/node/bin/npm', 'test' ] -2 info using npm@1.4.14 -3 info using node@v0.10.29 -4 error Error: ENOENT, open '/home/nss/apps/code/wine-seller/test/acceptance/package.json' -5 error If you need help, you may report this *entire* log, -5 error including the npm and node versions, at: -5 error -6 error System Linux 3.13.0-24-generic -7 error command "/usr/local/node/bin/node" "/usr/local/node/bin/npm" "test" -8 error cwd /home/nss/apps/code/wine-seller/test/acceptance -9 error node -v v0.10.29 -10 error npm -v 1.4.14 -11 error path /home/nss/apps/code/wine-seller/test/acceptance/package.json -12 error code ENOENT -13 error errno 34 -14 verbose exit [ 34, true ] diff --git a/test/acceptance/users.js b/test/acceptance/users.js index 19225fb..19f3ab7 100644 --- a/test/acceptance/users.js +++ b/test/acceptance/users.js @@ -2,7 +2,7 @@ 'use strict'; -process.env.DB = 'template-test'; +process.env.DB = 'wine-seller-test'; var expect = require('chai').expect, cp = require('child_process'), diff --git a/test/unit/item.js b/test/unit/item.js index bb6197f..0112e31 100644 --- a/test/unit/item.js +++ b/test/unit/item.js @@ -62,5 +62,26 @@ describe('Item', function(){ }); }); + describe('.findForSale', function(){ + it('should find items in database that are for sale based on query parameters', function(done){ + Item.findForSale({}, function(err, items){ + expect(items).to.have.length(1); + expect(items[0].name).to.equal('White Wine'); + done(); + }); + }); + }); + + describe('.markForSale', function(){ + it('should set an item onSale to true', function(done){ + Item.markOnSale('a00000000000000000000001', function(){ + Item.findById('a00000000000000000000001', function(err, item){ + expect(item.onSale).to.be.true; + done(); + }); + }); + }); + }); + });