diff --git a/.travis.yml b/.travis.yml index c862e75..0822f11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: java script: -- bash create_config.sh && cd java && ./gradlew run +- PROJ_DIR=$(pwd) +- 'bash create_config.sh && cd "${PROJ_DIR}"/java && ./gradlew run' +- 'cd "${PROJ_DIR}"/nodejs && ./create_config.sh && npm install -g grunt-cli && npm install && grunt test' jdk: - openjdk6 - openjdk7 @@ -11,3 +13,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/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/app.js b/node-js/app.js deleted file mode 100644 index 4bad158..0000000 --- a/node-js/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 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..42d746b --- /dev/null +++ b/nodejs/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +config.js +lib +test diff --git a/nodejs/Gruntfile.coffee b/nodejs/Gruntfile.coffee new file mode 100644 index 0000000..2cb3b9c --- /dev/null +++ b/nodejs/Gruntfile.coffee @@ -0,0 +1,93 @@ +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', 'test'] + }] + + # compiles coffeescript + coffee: + options: + bare: true + sourceMap: false + default: + files: grunt.file.expandMapping(['**/*.coffee'], 'lib/', + flatten: false + 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 + ) + + # starts express server and set the environment + express: + options: + port: 3000 + debug: false + default: + 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: + options: + spawn: false # Without this option specified express won't be reloaded + default: + files: [ + 'Gruntfile.coffee', + 'src/**/*.coffee', + './**/*.jade' + ] + 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 'run', ['build', 'express:default'] + grunt.registerTask 'serve', ['run', 'watch'] + grunt.registerTask 'test', ['build', 'express:test', 'shell:jasmine'] diff --git a/nodejs/README.md b/nodejs/README.md new file mode 100644 index 0000000..daf18eb --- /dev/null +++ b/nodejs/README.md @@ -0,0 +1,14 @@ +![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. + +**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 +``` + +> 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/nodejs/assets/images/favicon.ico b/nodejs/assets/images/favicon.ico new file mode 100644 index 0000000..961e4b1 Binary files /dev/null and b/nodejs/assets/images/favicon.ico differ diff --git a/nodejs/assets/stylesheets/main.css b/nodejs/assets/stylesheets/main.css new file mode 100644 index 0000000..2532902 --- /dev/null +++ b/nodejs/assets/stylesheets/main.css @@ -0,0 +1,77 @@ +/* + 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; +} +@media screen and (min-width: 768px) { + .jumbotron h1, .jumbotron .h1 { + font-size: 50px; + } +} + +/* + * 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..c92fb8a --- /dev/null +++ b/nodejs/create_config.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +cat > "config.js" << EOF +/* SPHERE.IO credentials */ +exports.config = { + 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 new file mode 100644 index 0000000..c9d3dab --- /dev/null +++ b/nodejs/package.json @@ -0,0 +1,54 @@ +{ + "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" + }, + "scripts": { + "start": "grunt serve", + "test": "grunt test" + }, + "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", + "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/coffee/app.coffee b/nodejs/src/coffee/app.coffee new file mode 100644 index 0000000..656e793 --- /dev/null +++ b/nodejs/src/coffee/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/coffee/logger.coffee b/nodejs/src/coffee/logger.coffee new file mode 100644 index 0000000..6950d3a --- /dev/null +++ b/nodejs/src/coffee/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/coffee/middleware/logger.coffee b/nodejs/src/coffee/middleware/logger.coffee new file mode 100644 index 0000000..5a2fee7 --- /dev/null +++ b/nodejs/src/coffee/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/coffee/routes.coffee b/nodejs/src/coffee/routes.coffee new file mode 100644 index 0000000..8146f47 --- /dev/null +++ b/nodejs/src/coffee/routes.coffee @@ -0,0 +1,51 @@ +_ = require 'underscore' +Promise = require 'bluebird' + +LANG = 'en' + +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 'index', + title: 'Hello API' + 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/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/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 new file mode 100644 index 0000000..440dbfe --- /dev/null +++ b/nodejs/views/main.jade @@ -0,0 +1,46 @@ +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 + + block content + + 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 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]}