From acf07967d64e7adc9ceb11e83654437ff33eae2e Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Wed, 19 Nov 2014 23:36:15 +0100 Subject: [PATCH 01/10] Use better express app setup and use node-sdk --- node-js/.gitignore | 1 - node-js/README.md | 13 ---- node-js/config.js | 4 -- node-js/package.json | 24 ------- nodejs/.gitignore | 4 ++ nodejs/Gruntfile.coffee | 62 ++++++++++++++++++ nodejs/README.md | 12 ++++ {node-js => nodejs}/app.js | 0 nodejs/assets/images/favicon.ico | Bin 0 -> 1150 bytes nodejs/assets/stylesheets/main.css | 72 +++++++++++++++++++++ nodejs/create_config.sh | 10 +++ nodejs/package.json | 47 ++++++++++++++ nodejs/src/app.coffee | 95 ++++++++++++++++++++++++++++ nodejs/src/logger.coffee | 11 ++++ nodejs/src/middleware/logger.coffee | 52 +++++++++++++++ nodejs/src/routes.coffee | 17 +++++ nodejs/views/main.jade | 75 ++++++++++++++++++++++ 17 files changed, 457 insertions(+), 42 deletions(-) delete mode 100644 node-js/.gitignore delete mode 100644 node-js/README.md delete mode 100644 node-js/config.js delete mode 100644 node-js/package.json create mode 100644 nodejs/.gitignore create mode 100644 nodejs/Gruntfile.coffee create mode 100644 nodejs/README.md rename {node-js => nodejs}/app.js (100%) create mode 100644 nodejs/assets/images/favicon.ico create mode 100644 nodejs/assets/stylesheets/main.css create mode 100755 nodejs/create_config.sh create mode 100644 nodejs/package.json create mode 100644 nodejs/src/app.coffee create mode 100644 nodejs/src/logger.coffee create mode 100644 nodejs/src/middleware/logger.coffee create mode 100644 nodejs/src/routes.coffee create mode 100644 nodejs/views/main.jade diff --git a/node-js/.gitignore b/node-js/.gitignore deleted file mode 100644 index 3c3629e..0000000 --- a/node-js/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/node-js/README.md b/node-js/README.md deleted file mode 100644 index dcf7a7a..0000000 --- a/node-js/README.md +++ /dev/null @@ -1,13 +0,0 @@ -Node.js Hello on SPHERE.IO -============== - -An example to authenticate your application to [SPHERE.IO](http://sphere.io) and get your products, using Node.js. - -Run -============ - -```bash -$ cd node-js -$ npm install -$ node app -``` \ No newline at end of file diff --git a/node-js/config.js b/node-js/config.js deleted file mode 100644 index 8f7316b..0000000 --- a/node-js/config.js +++ /dev/null @@ -1,4 +0,0 @@ -// SPHERE.IO application settings -exports.client_id = 'client_id'; // Your application client id -exports.client_secret = 'client_secret'; // Your application client secret -exports.project_key = "project_key"; // Your project key \ No newline at end of file diff --git a/node-js/package.json b/node-js/package.json deleted file mode 100644 index be5e961..0000000 --- a/node-js/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "sphere-hello-api", - "description": "First steps to access the API of SPHERE.IO", - "tags": [ - "sphere.io", - "oauth", - "api", - "hello" - ], - "version": "0.1.0", - "author": { - "name": "Nicola Molinari", - "email": "nicola.molinari@commercetools.de" - }, - "private": false, - "engines": { - "node": "*" - }, - "dependencies": { - "express": "*", - "request": "2.9.x", - "randomstring": "1.0.x" - } -} diff --git a/nodejs/.gitignore b/nodejs/.gitignore new file mode 100644 index 0000000..70ccaee --- /dev/null +++ b/nodejs/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +node_modules +config.js +lib diff --git a/nodejs/Gruntfile.coffee b/nodejs/Gruntfile.coffee new file mode 100644 index 0000000..73feb6c --- /dev/null +++ b/nodejs/Gruntfile.coffee @@ -0,0 +1,62 @@ +module.exports = (grunt) -> + + # Load grunt tasks automatically + require('load-grunt-tasks')(grunt) + + # project configuration + grunt.initConfig + + # load package information + pkg: grunt.file.readJSON 'package.json' + + # make sure code style is correct + coffeelint: + options: grunt.file.readJSON('node_modules/sphere-coffeelint/coffeelint.json') + default: ['Gruntfile.coffee', './**/*.coffee'] + + # empties folders to start fresh + clean: + default: + files: [{ + dot: true + src: ['lib'] + }] + + # compiles coffeescript + coffee: + options: + bare: true + sourceMap: false + default: + files: grunt.file.expandMapping(['**/*.coffee'], 'lib/', + flatten: false + cwd: 'src' + ext: '.js' + rename: (dest, matchedSrcPath) -> + dest + matchedSrcPath + ) + + # starts express server and set the environment + express: + options: + port: 3000 + debug: false + default: + options: + node_env: 'development' + script: 'lib/app.js' + + # watching for changes + watch: + options: + spawn: false # Without this option specified express won't be reloaded + default: + files: [ + 'Gruntfile.coffee', + 'src/**/*.coffee', + './**/*.jade' + ] + tasks: ['build', 'express'] + + grunt.registerTask 'build', ['clean', 'coffee'] + grunt.registerTask 'serve', ['build', 'express', 'watch'] diff --git a/nodejs/README.md b/nodejs/README.md new file mode 100644 index 0000000..989c2a3 --- /dev/null +++ b/nodejs/README.md @@ -0,0 +1,12 @@ +![SPHERE.IO icon](https://admin.sphere.io/assets/images/sphere_logo_rgb_long.png) + +# Node.js Hello API + +This is an example application to show how easy is to use the SPHERE.IO API using an Express.js application. + +```bash +$ npm install -g grunt-cli && npm install +$ grunt serve +``` + +> The source code is written in `coffeescript`, if you want to see the `javascript` version simply execute `grunt build` and look at the `lib` folder. diff --git a/node-js/app.js b/nodejs/app.js similarity index 100% rename from node-js/app.js rename to nodejs/app.js diff --git a/nodejs/assets/images/favicon.ico b/nodejs/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..961e4b1dbaacad570c7583eb427eb8402b2d96ac GIT binary patch literal 1150 zcmb7^%P%BR6vnT_mRN~kf)$AcD}MnIv9_|Cm5s2H@P`Z%E%At9nuUmo){`OL<5ATw zifW0+5S8hP7TrX;>(=Mo?!I(&S4Z5+sZ-~x`}^wLo10>+h_13SM)z9QQ_NU7W2}LK zBvR5kCC2Do?m&T&NXDRDkK_8E2bN_Pn3XRJ?&N9|-`5-9S}9G%(rzpMM!euWkq=~) zgc--#%_Q6#&2OCKWnTW%f!qIUc|MWoG3GZ~h$|_`N)M5LDc!a{48y?A;Vj(0+cUhg z&!o2!TcJKY#PuBh4uOCl!9Wl@2iZR9^-p*!i0daVY~y5M7=rg@!k@~bpT*5C>}i8| ze7=-Z9kZR6K0fGr7~JBHOt$F62dSTPU4zN3?5U2=*}UNVeji-xD!LYqF&N}Hd$i~b zIie|1@PZRDwW*?QT0zw}1$C|v>OCeJS2+d(9D2;zMDTOlDmrHrR1;S-te|$}ov-(B z^5JOmatwty&f|6;EfPbA`;^$D6en0GUi$Q^`Fr}5;8+abqy2}5FBu=%zvR9ia_rw* yNZLPd+nDF1@QI>z_5?L!>Gccl=mXAXLdA>8@$(`EHEPir$bI`>=l%~qTfYLEsc}63 literal 0 HcmV?d00001 diff --git a/nodejs/assets/stylesheets/main.css b/nodejs/assets/stylesheets/main.css new file mode 100644 index 0000000..b08a229 --- /dev/null +++ b/nodejs/assets/stylesheets/main.css @@ -0,0 +1,72 @@ +/* + Inspired by http://getbootstrap.com/examples/offcanvas/ + */ + +body { + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; + padding-top: 70px; +} + +/* + * Style tweaks + * -------------------------------------------------- + */ +html, +body { + overflow-x: hidden; /* Prevent scroll on narrow devices */ +} +body { + padding-top: 70px; +} +footer { + padding: 30px 0; +} + +/* + * Off Canvas + * -------------------------------------------------- + */ +@media screen and (max-width: 767px) { + .row-offcanvas { + position: relative; + -webkit-transition: all .25s ease-out; + -o-transition: all .25s ease-out; + transition: all .25s ease-out; + } + + .row-offcanvas-right { + right: 0; + } + + .row-offcanvas-left { + left: 0; + } + + .row-offcanvas-right + .sidebar-offcanvas { + right: -50%; /* 6 columns */ + } + + .row-offcanvas-left + .sidebar-offcanvas { + left: -50%; /* 6 columns */ + } + + .row-offcanvas-right.active { + right: 50%; /* 6 columns */ + } + + .row-offcanvas-left.active { + left: 50%; /* 6 columns */ + } + + .sidebar-offcanvas { + position: absolute; + top: 0; + width: 50%; /* 6 columns */ + } +} diff --git a/nodejs/create_config.sh b/nodejs/create_config.sh new file mode 100755 index 0000000..0ca0799 --- /dev/null +++ b/nodejs/create_config.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cat > "config.js" << EOF +/* SPHERE.IO credentials */ +exports.config = { + client_id: "${SPHERE_CLIENT_ID}", + client_secret: "${SPHERE_CLIENT_SECRET}", + project_key: "${SPHERE_PROJECT_KEY}" +} +EOF \ No newline at end of file diff --git a/nodejs/package.json b/nodejs/package.json new file mode 100644 index 0000000..9fe11db --- /dev/null +++ b/nodejs/package.json @@ -0,0 +1,47 @@ +{ + "name": "sphere-hello-api", + "description": "Get started using the SPHERE.IO API with Node.js SDK", + "tags": [ + "sphere", + "sphereio", + "oauth", + "api", + "node", + "sdk", + "hello" + ], + "version": "0.1.0", + "author": { + "name": "Nicola Molinari", + "email": "nicola.molinari@commercetools.de" + }, + "private": false, + "engines": { + "node": ">= 0.10.x" + }, + "dependencies": { + "bluebird": "2.3.x", + "body-parser": "1.9.x", + "bunyan": "1.1.x", + "compression": "1.2.x", + "cookie-parser": "1.3.x", + "cookie-session": "1.0.x", + "express": "4.10.x", + "jade": "1.7.x", + "multer": "0.1.x", + "serve-favicon": "2.1.x", + "sphere-node-sdk": "latest", + "underscore": "1.7.0", + "useragent": "2.0.x" + }, + "devDependencies": { + "grunt": "latest", + "grunt-coffeelint": "0.0.8", + "grunt-contrib-clean": "0.5.0", + "grunt-contrib-coffee": "0.10.1", + "grunt-contrib-watch": "0.6.1", + "grunt-express-server": "0.4.x", + "load-grunt-tasks": "1.0.x", + "sphere-coffeelint": "git://github.com/sphereio/sphere-coffeelint.git#master" + } +} diff --git a/nodejs/src/app.coffee b/nodejs/src/app.coffee new file mode 100644 index 0000000..656e793 --- /dev/null +++ b/nodejs/src/app.coffee @@ -0,0 +1,95 @@ +path = require 'path' +domain = require 'domain' +express = require 'express' +Logger = require './logger' +{SphereClient} = require 'sphere-node-sdk' + +APP_DIR = path.join(__dirname, '../') +pkg = require "#{APP_DIR}package.json" + +server = null +gracefullyExiting = false + +app = express() +env = app.get 'env' + +{port, logStream} = switch env + when 'production' + port: 3200 + logStream: [ + {level: 'info', path: "/var/log/#{pkg.name}/log"} + ] + else + port: 3000 + logStream: [ + {level: 'info', stream: process.stdout} + ] + +logger = new Logger + name: pkg.name + streams: logStream + +server = require('http').createServer(app) +logger.info "Starting express application on port #{port} (#{env})" + +handleTearDown = -> + gracefullyExiting = true + logger.info 'Attempting gracefully shutdown of server, waiting for remaining connections to complete.' + + server.close -> + logger.info 'No more connections, shutting down server.' + process.exit() + setTimeout -> + logger.error 'Could not close connections in time, forcefully shutting down.' + process.exit(1) + , 30 * 1000 # 30s + +process.on 'SIGINT', handleTearDown +process.on 'SIGTERM', handleTearDown + +app.set 'port', port +app.set 'views', "#{APP_DIR}views" +app.set 'view engine', 'jade' +app.set 'trust proxy', true +app.use '/assets', express.static("#{APP_DIR}assets") +app.use require('./middleware/logger')(logger) +app.use (req, res, next) -> + requestDomain = domain.create() + requestDomain.add(req) + requestDomain.add(res) + requestDomain.on 'error', next + requestDomain.run(next) +app.use (req, res, next) -> # enable CORS + res.header 'Access-Control-Allow-Origin', '*' + res.header 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS' + res.header 'Access-Control-Allow-Headers', 'Accept, Content-Type, Origin' + if req.method is 'OPTIONS' + res.status(200).send() + else + next() +app.use (req, res, next) -> + return next() unless gracefullyExiting + res.setHeader 'Connection', 'close' + res.status(502).send message: 'Server is in the process of restarting.' + +# see list of middlewares for express 4.x +# https://github.com/senchalabs/connect#middleware +app.use require('serve-favicon')("#{APP_DIR}assets/images/favicon.ico") +app.use require('multer')() +app.use require('body-parser').json() +app.use require('body-parser').urlencoded(extended: false) +app.use require('cookie-parser')() +app.use require('cookie-session')({secret: 'iamasecret'}) +app.use require('compression')() +app.use (err, req, res, next) -> + logger.error err + res.status(500).send message: 'Oops, something went wrong!' + +client = new SphereClient require('../config') +require('./routes')(app, client) + +# starts server +server.listen port +logger.info "Listening for HTTP on http://localhost:#{port}" + +module.exports = app diff --git a/nodejs/src/logger.coffee b/nodejs/src/logger.coffee new file mode 100644 index 0000000..6950d3a --- /dev/null +++ b/nodejs/src/logger.coffee @@ -0,0 +1,11 @@ +_ = require 'underscore' +bunyan = require 'bunyan' + +module.exports = class + + constructor: (config = {}) -> + return bunyan.createLogger _.defaults config, + serializers: bunyan.stdSerializers + streams: [ + {level: 'info', stream: process.stdout} + ] \ No newline at end of file diff --git a/nodejs/src/middleware/logger.coffee b/nodejs/src/middleware/logger.coffee new file mode 100644 index 0000000..5a2fee7 --- /dev/null +++ b/nodejs/src/middleware/logger.coffee @@ -0,0 +1,52 @@ +useragent = require 'useragent' + +expressify = (req, res, cb) -> + status = res.statusCode + method = req.method + url = req.url or '-' + referer = req.header('referer') or '-' + ua = useragent.parse(req.header('user-agent')) + httpVersion = "#{req.httpVersionMajor}.#{req.httpVersionMinor}" + ip = ip or req.ip or req.socket?.remoteAddress or + req.socket?.socket?.remoteAddresss or '127.0.0.1' + + meta = + remoteAddress: ip + method: method + url: url + referer: referer + 'user-agent': ua + body: req.body and req.body.toString and + req.body.toString().substring(0, Math.max(req.body.toString().length, 20)) + 'http-version': httpVersion + statusCode: status + req: req + res: res + + message = [ + ip + '- -' + method + url + "HTTP/#{httpVersion}" + status + res.get('Content-Length') + referer + ua.family + "#{ua.major}.#{ua.minor}" + ua.os + ].join(' ') + + cb(meta, message) + +module.exports = (logger) -> + (req, res, next) -> + expressLogger = logger.child widget_type: 'express' + expressify req, res, (meta, message) -> + # be less verbose regarding static resources + if meta.url.indexOf('/assets') < 0 + expressLogger.info message + else + expressLogger.debug message + res.on 'finish', -> expressLogger.debug meta, message + next() \ No newline at end of file diff --git a/nodejs/src/routes.coffee b/nodejs/src/routes.coffee new file mode 100644 index 0000000..882dc1e --- /dev/null +++ b/nodejs/src/routes.coffee @@ -0,0 +1,17 @@ +_ = require 'underscore' +Promise = require 'bluebird' + +module.exports = (app, client) -> + + # render start page + app.get '/', (req, res) -> + Promise.props + products: client.productProjections.fetch() + categories: client.categories.fetch() + .then (result) -> + + res.render 'main', + title: 'Hello API' + lang: 'en' + products: result.products.body + categories: result.categories.body diff --git a/nodejs/views/main.jade b/nodejs/views/main.jade new file mode 100644 index 0000000..a65f411 --- /dev/null +++ b/nodejs/views/main.jade @@ -0,0 +1,75 @@ +doctype html +html + head + meta(charset='utf-8') + meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') + meta(http-equiv='content-type', content='text/html; charset=UTF-8') + meta(name='viewport' content='width=device-width, initial-scale=1') + + + title SPHERE.IO™ - #{title} + link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css', media='screen') + link(rel='stylesheet', href='/assets/stylesheets/main.css', media='screen') + + body + + nav.navbar.navbar-fixed-top.navbar-inverse(role='navigation') + div.container + div.navbar-header + button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#navbar', aria-expanded='false', aria-controls='navbar') + span.sr-only Toggle nav + span.icon-bar + span.icon-bar + span.icon-bar + a.navbar-brand(href='#') My Project + div#navbar.navbar-collapse.collapse(aria-expanded='false', style='height: 1px;') + ul.nav.navbar-nav + li.active + a(href='#') Home + + div.container + div.row.row-offcanvas.row-offcanvas-right + div.col-xs-12.col-sm-9 + p.pull-right.visible-xs + button.btn.btn-primary.btn-xs(type='button', data-toggle='offcanvas') Menu + div.jumbotron + h1 + | Hello, + s world + | SPHERE.IO API! + p + | This is an example application to show how easy is to use the SPHERE.IO API using an Express.js application. + div.row + if products.count > 0 + each product in products.results + - var img = product.masterVariant.images.length ? product.masterVariant.images[0].url : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxkZWZzLz48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI0VFRUVFRSIvPjxnPjx0ZXh0IHg9Ijc0LjA0Njg3NSIgeT0iMTAwIiBzdHlsZT0iZmlsbDojQUFBQUFBO2ZvbnQtd2VpZ2h0OmJvbGQ7Zm9udC1mYW1pbHk6QXJpYWwsIEhlbHZldGljYSwgT3BlbiBTYW5zLCBzYW5zLXNlcmlmLCBtb25vc3BhY2U7Zm9udC1zaXplOjEwcHQ7ZG9taW5hbnQtYmFzZWxpbmU6Y2VudHJhbCI+MjAweDIwMDwvdGV4dD48L2c+PC9zdmc+' + div.col-xs-6.col-lg-4 + h2 #{product.name[lang]} + img(src='#{img}', width='200') + div #{product.description[lang]} + p + a.btn.btn-default(href='#', role='button') More details » + else + p No products + + div#sidebar.col-xs-6.col-sm-3.sidebar-offcanvas(role='navigation') + div.list-group + if categories.count > 0 + each category in categories.results + a.list-group-item(href='#') #{category.name[lang]} + else + span.list-group-item No categories + + hr + footer + p © SPHERE.IO 2014 + + script(src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js') + script(src='//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/js/bootstrap.min.js') + + script. + $(document).ready(function () { + $('body').on('click', '*[data-toggle=offcanvas]', function () { + $('.row-offcanvas').toggleClass('active') + }); + }); \ No newline at end of file From 7443b425fab6b8d87e7db98c3bd13cd147a2bb09 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 00:15:12 +0100 Subject: [PATCH 02/10] Provide navigation to product and category pages --- nodejs/README.md | 2 ++ nodejs/assets/stylesheets/main.css | 5 ++++ nodejs/src/routes.coffee | 38 ++++++++++++++++++++++++++++-- nodejs/views/category.jade | 31 ++++++++++++++++++++++++ nodejs/views/index.jade | 35 +++++++++++++++++++++++++++ nodejs/views/main.jade | 35 +++------------------------ nodejs/views/product.jade | 7 ++++++ 7 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 nodejs/views/category.jade create mode 100644 nodejs/views/index.jade create mode 100644 nodejs/views/product.jade diff --git a/nodejs/README.md b/nodejs/README.md index 989c2a3..daf18eb 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -4,6 +4,8 @@ This is an example application to show how easy is to use the SPHERE.IO API using an Express.js application. +**Make sure to have some Products and Categories in your project if you want to see something** + ```bash $ npm install -g grunt-cli && npm install $ grunt serve diff --git a/nodejs/assets/stylesheets/main.css b/nodejs/assets/stylesheets/main.css index b08a229..2532902 100644 --- a/nodejs/assets/stylesheets/main.css +++ b/nodejs/assets/stylesheets/main.css @@ -10,6 +10,11 @@ body { background-color: #fff; padding-top: 70px; } +@media screen and (min-width: 768px) { + .jumbotron h1, .jumbotron .h1 { + font-size: 50px; + } +} /* * Style tweaks diff --git a/nodejs/src/routes.coffee b/nodejs/src/routes.coffee index 882dc1e..8146f47 100644 --- a/nodejs/src/routes.coffee +++ b/nodejs/src/routes.coffee @@ -1,6 +1,8 @@ _ = require 'underscore' Promise = require 'bluebird' +LANG = 'en' + module.exports = (app, client) -> # render start page @@ -10,8 +12,40 @@ module.exports = (app, client) -> categories: client.categories.fetch() .then (result) -> - res.render 'main', + res.render 'index', title: 'Hello API' - lang: 'en' + lang: LANG products: result.products.body categories: result.categories.body + + .catch (e) -> res.status(400).json e.body + + app.get '/product/:id', (req, res) -> + client.productProjections.byId(req.params.id).fetch() + .then (result) -> + + res.render 'product', + title: result.body.name[LANG] + lang: LANG + product: result.body + + .catch (e) -> res.status(400).json e.body + + app.get '/category/:id', (req, res) -> + Promise.props + products: + client.productProjections + .where "categories(id = \"#{req.params.id}\")" + .fetch() + categories: client.categories.fetch() + category: client.categories.byId(req.params.id).fetch() + .then (result) -> + + res.render 'category', + title: 'Hello API' + lang: LANG + products: result.products.body + categories: result.categories.body + category: result.category.body + + .catch (e) -> res.status(400).json e.body diff --git a/nodejs/views/category.jade b/nodejs/views/category.jade new file mode 100644 index 0000000..809b5d8 --- /dev/null +++ b/nodejs/views/category.jade @@ -0,0 +1,31 @@ +extends main + +block content + + div.row.row-offcanvas.row-offcanvas-right + div.col-xs-12.col-sm-9 + p.pull-right.visible-xs + button.btn.btn-primary.btn-xs(type='button', data-toggle='offcanvas') Menu + div + h1 #{category.name[lang]} + div.row + if products.count > 0 + each product in products.results + - var img = product.masterVariant.images.length ? product.masterVariant.images[0].url : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxkZWZzLz48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI0VFRUVFRSIvPjxnPjx0ZXh0IHg9Ijc0LjA0Njg3NSIgeT0iMTAwIiBzdHlsZT0iZmlsbDojQUFBQUFBO2ZvbnQtd2VpZ2h0OmJvbGQ7Zm9udC1mYW1pbHk6QXJpYWwsIEhlbHZldGljYSwgT3BlbiBTYW5zLCBzYW5zLXNlcmlmLCBtb25vc3BhY2U7Zm9udC1zaXplOjEwcHQ7ZG9taW5hbnQtYmFzZWxpbmU6Y2VudHJhbCI+MjAweDIwMDwvdGV4dD48L2c+PC9zdmc+' + div.col-xs-6.col-lg-4 + h2 #{product.name[lang]} + img(src='#{img}', width='200') + div #{product.description[lang] ? product.description[lang] : 'n/a'} + p + a.btn.btn-default(href='/product/#{product.id}', role='button') More details » + else + p No products + + div#sidebar.col-xs-6.col-sm-3.sidebar-offcanvas(role='navigation') + div.list-group + if categories.count > 0 + each cat in categories.results + - var isActive = cat.id == category.id ? 'active' : '' + a(class='list-group-item #{isActive}', href='/category/#{cat.id}') #{cat.name[lang]} + else + span.list-group-item No categories diff --git a/nodejs/views/index.jade b/nodejs/views/index.jade new file mode 100644 index 0000000..9c0b8a4 --- /dev/null +++ b/nodejs/views/index.jade @@ -0,0 +1,35 @@ +extends main + +block content + + div.row.row-offcanvas.row-offcanvas-right + div.col-xs-12.col-sm-9 + p.pull-right.visible-xs + button.btn.btn-primary.btn-xs(type='button', data-toggle='offcanvas') Menu + div.jumbotron + h1 + | Hello, + s world + | SPHERE.IO API! + p + | This is an example application to show how easy is to use the SPHERE.IO API using an Express.js application. + div.row + if products.count > 0 + each product in products.results + - var img = product.masterVariant.images.length ? product.masterVariant.images[0].url : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxkZWZzLz48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI0VFRUVFRSIvPjxnPjx0ZXh0IHg9Ijc0LjA0Njg3NSIgeT0iMTAwIiBzdHlsZT0iZmlsbDojQUFBQUFBO2ZvbnQtd2VpZ2h0OmJvbGQ7Zm9udC1mYW1pbHk6QXJpYWwsIEhlbHZldGljYSwgT3BlbiBTYW5zLCBzYW5zLXNlcmlmLCBtb25vc3BhY2U7Zm9udC1zaXplOjEwcHQ7ZG9taW5hbnQtYmFzZWxpbmU6Y2VudHJhbCI+MjAweDIwMDwvdGV4dD48L2c+PC9zdmc+' + div.col-xs-6.col-lg-4 + h2 #{product.name[lang]} + img(src='#{img}', width='200') + div #{product.description[lang] ? product.description[lang] : 'n/a'} + p + a.btn.btn-default(href='/product/#{product.id}', role='button') More details » + else + p No products + + div#sidebar.col-xs-6.col-sm-3.sidebar-offcanvas(role='navigation') + div.list-group + if categories.count > 0 + each category in categories.results + a.list-group-item(href='/category/#{category.id}') #{category.name[lang]} + else + span.list-group-item No categories \ No newline at end of file diff --git a/nodejs/views/main.jade b/nodejs/views/main.jade index a65f411..440dbfe 100644 --- a/nodejs/views/main.jade +++ b/nodejs/views/main.jade @@ -21,44 +21,15 @@ html span.icon-bar span.icon-bar span.icon-bar - a.navbar-brand(href='#') My Project + a.navbar-brand(href='/') My Project div#navbar.navbar-collapse.collapse(aria-expanded='false', style='height: 1px;') ul.nav.navbar-nav li.active - a(href='#') Home + a(href='/') Home div.container - div.row.row-offcanvas.row-offcanvas-right - div.col-xs-12.col-sm-9 - p.pull-right.visible-xs - button.btn.btn-primary.btn-xs(type='button', data-toggle='offcanvas') Menu - div.jumbotron - h1 - | Hello, - s world - | SPHERE.IO API! - p - | This is an example application to show how easy is to use the SPHERE.IO API using an Express.js application. - div.row - if products.count > 0 - each product in products.results - - var img = product.masterVariant.images.length ? product.masterVariant.images[0].url : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxkZWZzLz48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI0VFRUVFRSIvPjxnPjx0ZXh0IHg9Ijc0LjA0Njg3NSIgeT0iMTAwIiBzdHlsZT0iZmlsbDojQUFBQUFBO2ZvbnQtd2VpZ2h0OmJvbGQ7Zm9udC1mYW1pbHk6QXJpYWwsIEhlbHZldGljYSwgT3BlbiBTYW5zLCBzYW5zLXNlcmlmLCBtb25vc3BhY2U7Zm9udC1zaXplOjEwcHQ7ZG9taW5hbnQtYmFzZWxpbmU6Y2VudHJhbCI+MjAweDIwMDwvdGV4dD48L2c+PC9zdmc+' - div.col-xs-6.col-lg-4 - h2 #{product.name[lang]} - img(src='#{img}', width='200') - div #{product.description[lang]} - p - a.btn.btn-default(href='#', role='button') More details » - else - p No products - div#sidebar.col-xs-6.col-sm-3.sidebar-offcanvas(role='navigation') - div.list-group - if categories.count > 0 - each category in categories.results - a.list-group-item(href='#') #{category.name[lang]} - else - span.list-group-item No categories + block content hr footer diff --git a/nodejs/views/product.jade b/nodejs/views/product.jade new file mode 100644 index 0000000..e5f767a --- /dev/null +++ b/nodejs/views/product.jade @@ -0,0 +1,7 @@ +extends main + +block content + + div.row.row-offcanvas.row-offcanvas-right + div.col-xs-12.col-sm-9 + h1 #{product.name[lang]} From 08ff95af3add540d4f6c5b6fd620d6305e8187d6 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 00:17:48 +0100 Subject: [PATCH 03/10] Cleanup --- nodejs/app.js | 81 --------------------------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 nodejs/app.js diff --git a/nodejs/app.js b/nodejs/app.js deleted file mode 100644 index 4bad158..0000000 --- a/nodejs/app.js +++ /dev/null @@ -1,81 +0,0 @@ -// Module dependencies -var express = require('express'), - querystring = require('querystring'), - request = require('request'), - Config = require('./config') - -var app = express() - -// Configuration -app.configure(function(){ - app.use(express.logger('dev')) - app.use(express.bodyParser()) - app.use(express.methodOverride()) - app.use(app.router) -}) - -app.configure('development', function(){ - app.use(express.errorHandler({ dumpExceptions: true, showStack: true })) -}) - -// Routes -app.get('/', login) - -app.listen(3000, function(){ - console.log("Server listening on port 3000...") -}) - - -function login(req, res){ - console.log("Requesting an Access Token...") - - var params = { - 'grant_type': 'client_credentials', - 'scope': 'manage_project:' + Config.project_key - } - - var payload = querystring.stringify(params) - var request_options = { - uri: 'https://' + Config.client_id + ':' + Config.client_secret + '@' + 'auth.sphere.io/oauth/token', - method: 'POST', - body: payload, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': payload.length - }, - timeout: 20000 - }; - - request(request_options, function(error, response, body) { - var json_body = JSON.parse(body) - console.log(json_body) - - if (response.statusCode == 200) { - console.log("...authorized!") - getProducts(req, res, json_body) - } else { - throw new Error('Failed to get Access Token.') - } - }) -} - - -function getProducts(req, res, json_body){ - console.log("Fetching products...") - - var request_options = { - uri: 'https://api.sphere.io' + "/" + Config.project_key + "/product-projections", - method: 'GET', - headers: { - 'Authorization': 'Bearer ' + json_body.access_token - }, - timeout: 20000 - } - - request(request_options, function(error, response, body) { - if (response.statusCode == 200) { - products = JSON.parse(body) - res.json(products) - } - }) -} \ No newline at end of file From ba51db36bc3e635bc8289b1f41a52236291ba4b7 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 00:55:55 +0100 Subject: [PATCH 04/10] Provide tests + setup travis --- .travis.yml | 5 + nodejs/Gruntfile.coffee | 37 +++++- nodejs/create_config.sh | 6 +- nodejs/package.json | 7 ++ nodejs/src/{ => coffee}/app.coffee | 0 nodejs/src/{ => coffee}/logger.coffee | 0 .../src/{ => coffee}/middleware/logger.coffee | 0 nodejs/src/{ => coffee}/routes.coffee | 0 nodejs/src/spec/SpecHelper.coffee | 52 +++++++++ .../spec/functional/functional.spec.coffee | 82 ++++++++++++++ nodejs/test/SpecHelper.js | 90 +++++++++++++++ nodejs/test/functional/functional.spec.js | 105 ++++++++++++++++++ 12 files changed, 378 insertions(+), 6 deletions(-) rename nodejs/src/{ => coffee}/app.coffee (100%) rename nodejs/src/{ => coffee}/logger.coffee (100%) rename nodejs/src/{ => coffee}/middleware/logger.coffee (100%) rename nodejs/src/{ => coffee}/routes.coffee (100%) create mode 100644 nodejs/src/spec/SpecHelper.coffee create mode 100644 nodejs/src/spec/functional/functional.spec.coffee create mode 100644 nodejs/test/SpecHelper.js create mode 100644 nodejs/test/functional/functional.spec.js diff --git a/.travis.yml b/.travis.yml index c862e75..7db8f6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: java script: - bash create_config.sh && cd java && ./gradlew run +- cd nodejs && npm install -g grunt-cli && npm install && grunt test jdk: - openjdk6 - openjdk7 @@ -11,3 +12,7 @@ env: - secure: ISBTx5dCLRYtT+vp5MXFiZoJMF9l/I+W40fCIGp/OYJVm5uX3QdY9tqmPPJtHyGNYC/U2MNrhg/Z+8opwxQ931e9kCUfg/7nk6EX8t0gYV4tMKkaqr+NJ7S7uyu8SrFbcwABzUo5/91ujKkrsp/R5yQ/kCggDND+utpEVSWB9LY= - secure: cTja//s9O9IYrCOgjeFp4j0MJzVfjCnADlF69hPQsLaJNAPAGc6JRcUeaOeVZHSMI3y3qh4IGgfAm3QuxyUFTn5RP0OVpDAYri29Ubn7SYA55cho7K+YUZbrnCFuFMypZ8dVVbdZA2cFefUUnVhuGXHFlVUWUPUwUFd0o2oSw5Y= - secure: j3EAxprAcKq4aaGn/WA9VKn41C9Kismp/I0caLWP6k2dZvk1SZGnaiZXuAeqLxQMQ4cU4WH6qupnxCb/oS+0OxIe6UE7QAM7Xk8e5jp52NTubCkBJH04gi3BSVapLtDK1ePerNJuwOo0LZh9hwGwMWGy2XEZe59B5V50vvyp8Qo= + # nodejs config + - secure: "QZ2NlSrBo/higMc3uuhq769VKtLW2gGa5l9xFxpdNamfuHEjGIQtUhMYQHH3WrP73dC3zAzmB6TslsDxnJCVGAMG3u18nEppXK533dqUnEkForqQhYreVMd4l9JDrTnnHhoHzF1dWHUSjGPXAfuzIjEmJisGPmX8PtdqHSnwNUs=" + - secure: "OmiPopj19efW/6KY2wjj+HOuORUweLG/iJdDNUXfe4kWuCXyOwcmEd2EqPT8Q59f8roWhUChNv6Q/ICtdfAgoIVYeZrEwI04Cq/aBEaull03qVZQbxxeNihd/C88R3pCbMF0vNsbVPELCFdSs4+Axe7TnEyRPGYIuxHX1oGPbnQ=" + - secure: "N+fLn7A4s1edSuauSMXn8pCjboNo4fqbKC5H3PGRB5fyHH/DSJhAMH3m3hKXDfojIock79Te0v5IEy0sf5Pl0ARrdZ9T8cdXO4MvuJTTr45OBzoSE5LHfGaTgPzOMChgfbQc7kg3gmIP7Roejg192/Q3jFtSH3HkxNlIauE2uFc=" diff --git a/nodejs/Gruntfile.coffee b/nodejs/Gruntfile.coffee index 73feb6c..f3e912e 100644 --- a/nodejs/Gruntfile.coffee +++ b/nodejs/Gruntfile.coffee @@ -19,7 +19,7 @@ module.exports = (grunt) -> default: files: [{ dot: true - src: ['lib'] + src: ['lib', 'test'] }] # compiles coffeescript @@ -30,7 +30,23 @@ module.exports = (grunt) -> default: files: grunt.file.expandMapping(['**/*.coffee'], 'lib/', flatten: false - cwd: 'src' + cwd: 'src/coffee' + ext: '.js' + rename: (dest, matchedSrcPath) -> + dest + matchedSrcPath + ) + test: + files: grunt.file.expandMapping(['**/*.spec.coffee'], 'test/', + flatten: false + cwd: 'src/spec' + ext: '.spec.js' + rename: (dest, matchedSrcPath) -> + dest + matchedSrcPath + ) + testHelpers: + files: grunt.file.expandMapping(['**/*Helper.coffee'], 'test/', + flatten: false + cwd: 'src/spec' ext: '.js' rename: (dest, matchedSrcPath) -> dest + matchedSrcPath @@ -45,6 +61,11 @@ module.exports = (grunt) -> options: node_env: 'development' script: 'lib/app.js' + test: + options: + output: 'Listening' # waits for matching message before passing control back to grunt + node_env: 'test' + script: "lib/app.js" # watching for changes watch: @@ -58,5 +79,15 @@ module.exports = (grunt) -> ] tasks: ['build', 'express'] + shell: + options: + stdout: true + stderr: true + failOnError: true + jasmine: + command: 'jasmine-node --verbose --captureExceptions test' + grunt.registerTask 'build', ['clean', 'coffee'] - grunt.registerTask 'serve', ['build', 'express', 'watch'] + grunt.registerTask 'run', ['build', 'express:run'] + grunt.registerTask 'serve', ['run', 'watch'] + grunt.registerTask 'test', ['build', 'express:test', 'shell:jasmine'] diff --git a/nodejs/create_config.sh b/nodejs/create_config.sh index 0ca0799..c92fb8a 100755 --- a/nodejs/create_config.sh +++ b/nodejs/create_config.sh @@ -3,8 +3,8 @@ cat > "config.js" << EOF /* SPHERE.IO credentials */ exports.config = { - client_id: "${SPHERE_CLIENT_ID}", - client_secret: "${SPHERE_CLIENT_SECRET}", - project_key: "${SPHERE_PROJECT_KEY}" + client_id: "${NODE_SPHERE_CLIENT_ID}", + client_secret: "${NODE_SPHERE_CLIENT_SECRET}", + project_key: "${NODE_SPHERE_PROJECT_KEY}" } EOF \ No newline at end of file diff --git a/nodejs/package.json b/nodejs/package.json index 9fe11db..c9d3dab 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -19,6 +19,10 @@ "engines": { "node": ">= 0.10.x" }, + "scripts": { + "start": "grunt serve", + "test": "grunt test" + }, "dependencies": { "bluebird": "2.3.x", "body-parser": "1.9.x", @@ -41,7 +45,10 @@ "grunt-contrib-coffee": "0.10.1", "grunt-contrib-watch": "0.6.1", "grunt-express-server": "0.4.x", + "grunt-shell": "0.7.0", + "jasmine-node": "1.13.1", "load-grunt-tasks": "1.0.x", + "request": "2.34.x", "sphere-coffeelint": "git://github.com/sphereio/sphere-coffeelint.git#master" } } diff --git a/nodejs/src/app.coffee b/nodejs/src/coffee/app.coffee similarity index 100% rename from nodejs/src/app.coffee rename to nodejs/src/coffee/app.coffee diff --git a/nodejs/src/logger.coffee b/nodejs/src/coffee/logger.coffee similarity index 100% rename from nodejs/src/logger.coffee rename to nodejs/src/coffee/logger.coffee diff --git a/nodejs/src/middleware/logger.coffee b/nodejs/src/coffee/middleware/logger.coffee similarity index 100% rename from nodejs/src/middleware/logger.coffee rename to nodejs/src/coffee/middleware/logger.coffee diff --git a/nodejs/src/routes.coffee b/nodejs/src/coffee/routes.coffee similarity index 100% rename from nodejs/src/routes.coffee rename to nodejs/src/coffee/routes.coffee diff --git a/nodejs/src/spec/SpecHelper.coffee b/nodejs/src/spec/SpecHelper.coffee new file mode 100644 index 0000000..458b1d5 --- /dev/null +++ b/nodejs/src/spec/SpecHelper.coffee @@ -0,0 +1,52 @@ +fs = require 'fs' +_ = require 'underscore' +request = require 'request' +Promise = require 'bluebird' + +BASE_DOMAIN = 'http://localhost:3000' + +class Requester + + options: (path, method) -> + uri: BASE_DOMAIN + path + json: true + method: method + rejectUnauthorized: false + + get: (path) -> + new Promise (resolve, reject) => + request @options(path, 'GET'), (e, r, b) -> + if e + console.error e + reject e + else + resolve + response: r + body: b + + post: (path, body) -> + new Promise (resolve, reject) => + request _.extend({}, @options(path, 'POST'), {body: body}), (e, r, b) -> + if e + console.error e + reject e + else + resolve + response: r + body: b + + upload: (path, filePath) -> + new Promise (resolve, reject) => + r = request @options(path, 'POST'), (e, r, b) -> + if e + console.error e + reject e + else + resolve + response: r + body: b + form = r.form() + form.append 'csvFile', fs.createReadStream(filePath) + +module.exports = + http: new Requester diff --git a/nodejs/src/spec/functional/functional.spec.coffee b/nodejs/src/spec/functional/functional.spec.coffee new file mode 100644 index 0000000..69dc07a --- /dev/null +++ b/nodejs/src/spec/functional/functional.spec.coffee @@ -0,0 +1,82 @@ +fs = require 'fs' +_ = require 'underscore' +http = require 'http' +Promise = require 'bluebird' +{SphereClient} = require 'sphere-node-sdk' +helper = require '../SpecHelper' +Config = require '../../config' + +client = new SphereClient Config + +describe 'Functional Spec', -> + + checkCORSHeaders = (res) -> + expect(res.headers['access-control-allow-origin']).toBe '*' + expect(res.headers['access-control-allow-headers']).toBe 'Accept, Content-Type, Origin' + expect(res.headers['access-control-allow-methods']).toBe 'GET, POST, OPTIONS' + + beforeEach (done) -> + Promise.props + products: client.productProjections.perPage(1).fetch() + categories: client.categories.perPage(1).fetch() + .then (result) => + @product = _.first result.products.body.results + @category = _.first result.categories.body.results + done() + .catch (e) -> done(e) + + describe ':: GET /', -> + + it 'should render index page', (done) -> + helper.http.get '/' + .then (result) -> + expect(result.response.statusCode).toBe 200 + expect(result.response.headers['content-type']).toBe 'text/html; charset=utf-8' + checkCORSHeaders(result.response) + expect(result.body).toContain 'Hello API' + done() + .catch (error) -> done(error) + + describe ':: GET /product/:id', -> + + it 'should render product view', (done) -> + helper.http.get "/product/#{@product.id}" + .then (result) => + expect(result.response.statusCode).toBe 200 + expect(result.response.headers['content-type']).toBe 'text/html; charset=utf-8' + checkCORSHeaders(result.response) + expect(result.body).toContain @product.name.en + done() + .catch (error) -> done(error) + + it 'should return 400 (json)', (done) -> + helper.http.get "/product/#{@category.id}" # use a non-product UUID to test not found result + .then (result) -> + expect(result.response.statusCode).toBe 400 + expect(result.response.headers['content-type']).toBe 'application/json; charset=utf-8' + checkCORSHeaders(result.response) + expect(result.body.message).toContain 'not found' + done() + .catch (error) -> done(error) + + describe ':: GET /category/:id', -> + + it 'should render category view', (done) -> + helper.http.get "/category/#{@category.id}" + .then (result) => + expect(result.response.statusCode).toBe 200 + expect(result.response.headers['content-type']).toBe 'text/html; charset=utf-8' + checkCORSHeaders(result.response) + expect(result.body).toContain @category.name.en + done() + .catch (error) -> done(error) + + it 'should return 400 (json)', (done) -> + helper.http.get "/category/#{@product.id}" # use a non-category UUID to test not found result + .then (result) -> + expect(result.response.statusCode).toBe 400 + expect(result.response.headers['content-type']).toBe 'application/json; charset=utf-8' + checkCORSHeaders(result.response) + expect(result.body.message).toContain 'not found' + done() + .catch (error) -> done(error) diff --git a/nodejs/test/SpecHelper.js b/nodejs/test/SpecHelper.js new file mode 100644 index 0000000..e973bd6 --- /dev/null +++ b/nodejs/test/SpecHelper.js @@ -0,0 +1,90 @@ +var BASE_DOMAIN, Promise, Requester, fs, request, _; + +fs = require('fs'); + +_ = require('underscore'); + +request = require('request'); + +Promise = require('bluebird'); + +BASE_DOMAIN = 'http://localhost:3000'; + +Requester = (function() { + function Requester() {} + + Requester.prototype.options = function(path, method) { + return { + uri: BASE_DOMAIN + path, + json: true, + method: method, + rejectUnauthorized: false + }; + }; + + Requester.prototype.get = function(path) { + return new Promise((function(_this) { + return function(resolve, reject) { + return request(_this.options(path, 'GET'), function(e, r, b) { + if (e) { + console.error(e); + return reject(e); + } else { + return resolve({ + response: r, + body: b + }); + } + }); + }; + })(this)); + }; + + Requester.prototype.post = function(path, body) { + return new Promise((function(_this) { + return function(resolve, reject) { + return request(_.extend({}, _this.options(path, 'POST'), { + body: body + }), function(e, r, b) { + if (e) { + console.error(e); + return reject(e); + } else { + return resolve({ + response: r, + body: b + }); + } + }); + }; + })(this)); + }; + + Requester.prototype.upload = function(path, filePath) { + return new Promise((function(_this) { + return function(resolve, reject) { + var form, r; + r = request(_this.options(path, 'POST'), function(e, r, b) { + if (e) { + console.error(e); + return reject(e); + } else { + return resolve({ + response: r, + body: b + }); + } + }); + form = r.form(); + return form.append('csvFile', fs.createReadStream(filePath)); + }; + })(this)); + }; + + return Requester; + +})(); + +module.exports = { + http: new Requester +}; diff --git a/nodejs/test/functional/functional.spec.js b/nodejs/test/functional/functional.spec.js new file mode 100644 index 0000000..34231a1 --- /dev/null +++ b/nodejs/test/functional/functional.spec.js @@ -0,0 +1,105 @@ +var Config, Promise, SphereClient, client, fs, helper, http, _; + +fs = require('fs'); + +_ = require('underscore'); + +http = require('http'); + +Promise = require('bluebird'); + +SphereClient = require('sphere-node-sdk').SphereClient; + +helper = require('../SpecHelper'); + +Config = require('../../config'); + +client = new SphereClient(Config); + +describe('Functional Spec', function() { + var checkCORSHeaders; + checkCORSHeaders = function(res) { + expect(res.headers['access-control-allow-origin']).toBe('*'); + expect(res.headers['access-control-allow-headers']).toBe('Accept, Content-Type, Origin'); + return expect(res.headers['access-control-allow-methods']).toBe('GET, POST, OPTIONS'); + }; + beforeEach(function(done) { + return Promise.props({ + products: client.productProjections.perPage(1).fetch(), + categories: client.categories.perPage(1).fetch() + }).then((function(_this) { + return function(result) { + _this.product = _.first(result.products.body.results); + _this.category = _.first(result.categories.body.results); + return done(); + }; + })(this))["catch"](function(e) { + return done(e); + }); + }); + describe(':: GET /', function() { + return it('should render index page', function(done) { + return helper.http.get('/').then(function(result) { + expect(result.response.statusCode).toBe(200); + expect(result.response.headers['content-type']).toBe('text/html; charset=utf-8'); + checkCORSHeaders(result.response); + expect(result.body).toContain('Hello API'); + return done(); + })["catch"](function(error) { + return done(error); + }); + }); + }); + describe(':: GET /product/:id', function() { + it('should render product view', function(done) { + return helper.http.get("/product/" + this.product.id).then((function(_this) { + return function(result) { + expect(result.response.statusCode).toBe(200); + expect(result.response.headers['content-type']).toBe('text/html; charset=utf-8'); + checkCORSHeaders(result.response); + expect(result.body).toContain(_this.product.name.en); + return done(); + }; + })(this))["catch"](function(error) { + return done(error); + }); + }); + return it('should return 400 (json)', function(done) { + return helper.http.get("/product/" + this.category.id).then(function(result) { + expect(result.response.statusCode).toBe(400); + expect(result.response.headers['content-type']).toBe('application/json; charset=utf-8'); + checkCORSHeaders(result.response); + expect(result.body.message).toContain('not found'); + return done(); + })["catch"](function(error) { + return done(error); + }); + }); + }); + return describe(':: GET /category/:id', function() { + it('should render category view', function(done) { + return helper.http.get("/category/" + this.category.id).then((function(_this) { + return function(result) { + expect(result.response.statusCode).toBe(200); + expect(result.response.headers['content-type']).toBe('text/html; charset=utf-8'); + checkCORSHeaders(result.response); + expect(result.body).toContain(_this.category.name.en); + return done(); + }; + })(this))["catch"](function(error) { + return done(error); + }); + }); + return it('should return 400 (json)', function(done) { + return helper.http.get("/category/" + this.product.id).then(function(result) { + expect(result.response.statusCode).toBe(400); + expect(result.response.headers['content-type']).toBe('application/json; charset=utf-8'); + checkCORSHeaders(result.response); + expect(result.body.message).toContain('not found'); + return done(); + })["catch"](function(error) { + return done(error); + }); + }); + }); +}); From 1a171d3dab3baedb4dd89769b8886e6f8e7890fa Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 00:56:59 +0100 Subject: [PATCH 05/10] Cleanup --- nodejs/.gitignore | 1 + nodejs/test/SpecHelper.js | 90 ------------------- nodejs/test/functional/functional.spec.js | 105 ---------------------- 3 files changed, 1 insertion(+), 195 deletions(-) delete mode 100644 nodejs/test/SpecHelper.js delete mode 100644 nodejs/test/functional/functional.spec.js diff --git a/nodejs/.gitignore b/nodejs/.gitignore index 70ccaee..42d746b 100644 --- a/nodejs/.gitignore +++ b/nodejs/.gitignore @@ -2,3 +2,4 @@ node_modules config.js lib +test diff --git a/nodejs/test/SpecHelper.js b/nodejs/test/SpecHelper.js deleted file mode 100644 index e973bd6..0000000 --- a/nodejs/test/SpecHelper.js +++ /dev/null @@ -1,90 +0,0 @@ -var BASE_DOMAIN, Promise, Requester, fs, request, _; - -fs = require('fs'); - -_ = require('underscore'); - -request = require('request'); - -Promise = require('bluebird'); - -BASE_DOMAIN = 'http://localhost:3000'; - -Requester = (function() { - function Requester() {} - - Requester.prototype.options = function(path, method) { - return { - uri: BASE_DOMAIN + path, - json: true, - method: method, - rejectUnauthorized: false - }; - }; - - Requester.prototype.get = function(path) { - return new Promise((function(_this) { - return function(resolve, reject) { - return request(_this.options(path, 'GET'), function(e, r, b) { - if (e) { - console.error(e); - return reject(e); - } else { - return resolve({ - response: r, - body: b - }); - } - }); - }; - })(this)); - }; - - Requester.prototype.post = function(path, body) { - return new Promise((function(_this) { - return function(resolve, reject) { - return request(_.extend({}, _this.options(path, 'POST'), { - body: body - }), function(e, r, b) { - if (e) { - console.error(e); - return reject(e); - } else { - return resolve({ - response: r, - body: b - }); - } - }); - }; - })(this)); - }; - - Requester.prototype.upload = function(path, filePath) { - return new Promise((function(_this) { - return function(resolve, reject) { - var form, r; - r = request(_this.options(path, 'POST'), function(e, r, b) { - if (e) { - console.error(e); - return reject(e); - } else { - return resolve({ - response: r, - body: b - }); - } - }); - form = r.form(); - return form.append('csvFile', fs.createReadStream(filePath)); - }; - })(this)); - }; - - return Requester; - -})(); - -module.exports = { - http: new Requester -}; diff --git a/nodejs/test/functional/functional.spec.js b/nodejs/test/functional/functional.spec.js deleted file mode 100644 index 34231a1..0000000 --- a/nodejs/test/functional/functional.spec.js +++ /dev/null @@ -1,105 +0,0 @@ -var Config, Promise, SphereClient, client, fs, helper, http, _; - -fs = require('fs'); - -_ = require('underscore'); - -http = require('http'); - -Promise = require('bluebird'); - -SphereClient = require('sphere-node-sdk').SphereClient; - -helper = require('../SpecHelper'); - -Config = require('../../config'); - -client = new SphereClient(Config); - -describe('Functional Spec', function() { - var checkCORSHeaders; - checkCORSHeaders = function(res) { - expect(res.headers['access-control-allow-origin']).toBe('*'); - expect(res.headers['access-control-allow-headers']).toBe('Accept, Content-Type, Origin'); - return expect(res.headers['access-control-allow-methods']).toBe('GET, POST, OPTIONS'); - }; - beforeEach(function(done) { - return Promise.props({ - products: client.productProjections.perPage(1).fetch(), - categories: client.categories.perPage(1).fetch() - }).then((function(_this) { - return function(result) { - _this.product = _.first(result.products.body.results); - _this.category = _.first(result.categories.body.results); - return done(); - }; - })(this))["catch"](function(e) { - return done(e); - }); - }); - describe(':: GET /', function() { - return it('should render index page', function(done) { - return helper.http.get('/').then(function(result) { - expect(result.response.statusCode).toBe(200); - expect(result.response.headers['content-type']).toBe('text/html; charset=utf-8'); - checkCORSHeaders(result.response); - expect(result.body).toContain('Hello API'); - return done(); - })["catch"](function(error) { - return done(error); - }); - }); - }); - describe(':: GET /product/:id', function() { - it('should render product view', function(done) { - return helper.http.get("/product/" + this.product.id).then((function(_this) { - return function(result) { - expect(result.response.statusCode).toBe(200); - expect(result.response.headers['content-type']).toBe('text/html; charset=utf-8'); - checkCORSHeaders(result.response); - expect(result.body).toContain(_this.product.name.en); - return done(); - }; - })(this))["catch"](function(error) { - return done(error); - }); - }); - return it('should return 400 (json)', function(done) { - return helper.http.get("/product/" + this.category.id).then(function(result) { - expect(result.response.statusCode).toBe(400); - expect(result.response.headers['content-type']).toBe('application/json; charset=utf-8'); - checkCORSHeaders(result.response); - expect(result.body.message).toContain('not found'); - return done(); - })["catch"](function(error) { - return done(error); - }); - }); - }); - return describe(':: GET /category/:id', function() { - it('should render category view', function(done) { - return helper.http.get("/category/" + this.category.id).then((function(_this) { - return function(result) { - expect(result.response.statusCode).toBe(200); - expect(result.response.headers['content-type']).toBe('text/html; charset=utf-8'); - checkCORSHeaders(result.response); - expect(result.body).toContain(_this.category.name.en); - return done(); - }; - })(this))["catch"](function(error) { - return done(error); - }); - }); - return it('should return 400 (json)', function(done) { - return helper.http.get("/category/" + this.product.id).then(function(result) { - expect(result.response.statusCode).toBe(400); - expect(result.response.headers['content-type']).toBe('application/json; charset=utf-8'); - checkCORSHeaders(result.response); - expect(result.body.message).toContain('not found'); - return done(); - })["catch"](function(error) { - return done(error); - }); - }); - }); -}); From 294cac3cc93bebb6b8d8c4271c22c5bd922a3b42 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 01:02:30 +0100 Subject: [PATCH 06/10] Adjust path to nodejs build --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7db8f6a..feaa885 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: java script: -- bash create_config.sh && cd java && ./gradlew run -- cd nodejs && npm install -g grunt-cli && npm install && grunt test +- projects_dir=$(pwd) +- bash create_config.sh && cd $project_dir/java && ./gradlew run +- cd $project_dir/nodejs && npm install -g grunt-cli && npm install && grunt test jdk: - openjdk6 - openjdk7 From 8f021f6a09c2208ff5852a41065e92ad9414ceb4 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 01:09:35 +0100 Subject: [PATCH 07/10] Correctly use variable --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index feaa885..4d5cf63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: java script: - projects_dir=$(pwd) -- bash create_config.sh && cd $project_dir/java && ./gradlew run -- cd $project_dir/nodejs && npm install -g grunt-cli && npm install && grunt test +- "bash create_config.sh && cd ${project_dir}/java && ./gradlew run" +- "cd ${project_dir}/nodejs && npm install -g grunt-cli && npm install && grunt test" jdk: - openjdk6 - openjdk7 From f48b6e3067dc2905bd682348b937c96e23f20822 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 01:24:36 +0100 Subject: [PATCH 08/10] Trying defining variable --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4d5cf63..86113f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: java script: -- projects_dir=$(pwd) -- "bash create_config.sh && cd ${project_dir}/java && ./gradlew run" -- "cd ${project_dir}/nodejs && npm install -g grunt-cli && npm install && grunt test" +- PROJ_DIR=$(pwd) +- 'bash create_config.sh && cd "${PROJ_DIR}"/java && ./gradlew run' +- 'cd "${PROJ_DIR}"/nodejs && npm install -g grunt-cli && npm install && grunt test' jdk: - openjdk6 - openjdk7 From b108b79bdd8d0468f847e3e9291be9d5449ec41a Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 01:28:43 +0100 Subject: [PATCH 09/10] Forgot to generate config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 86113f7..0822f11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: java script: - PROJ_DIR=$(pwd) - 'bash create_config.sh && cd "${PROJ_DIR}"/java && ./gradlew run' -- 'cd "${PROJ_DIR}"/nodejs && npm install -g grunt-cli && npm install && grunt test' +- 'cd "${PROJ_DIR}"/nodejs && ./create_config.sh && npm install -g grunt-cli && npm install && grunt test' jdk: - openjdk6 - openjdk7 From 005b7c0dc91d25441df0cc5e314941c42f35e764 Mon Sep 17 00:00:00 2001 From: Nicola Molinari Date: Thu, 20 Nov 2014 10:39:57 +0100 Subject: [PATCH 10/10] Adjust grunt task --- nodejs/Gruntfile.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs/Gruntfile.coffee b/nodejs/Gruntfile.coffee index f3e912e..2cb3b9c 100644 --- a/nodejs/Gruntfile.coffee +++ b/nodejs/Gruntfile.coffee @@ -88,6 +88,6 @@ module.exports = (grunt) -> command: 'jasmine-node --verbose --captureExceptions test' grunt.registerTask 'build', ['clean', 'coffee'] - grunt.registerTask 'run', ['build', 'express:run'] + grunt.registerTask 'run', ['build', 'express:default'] grunt.registerTask 'serve', ['run', 'watch'] grunt.registerTask 'test', ['build', 'express:test', 'shell:jasmine']