├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── app.js ├── app ├── bus.js ├── bus.routes.activity.js ├── bus.routes.cron.js ├── bus.routes.js ├── bus.routes.mailchimp.js ├── bus.routes.users.js ├── config.js ├── db.maintain.js ├── express.js ├── locales │ ├── en.js │ └── fr.js ├── mail.js ├── passport.js ├── payment.account.js ├── payment.invoice.js ├── payment.js ├── payment.postfinance.js ├── payment.stripe.js ├── payment.test.js ├── queue.js ├── routes.js ├── utils.js └── validator.js ├── config ├── config-development.js ├── config-shared-test.js ├── config-shared.js └── config-test.js ├── controllers ├── api.js ├── auth.js ├── categories.js ├── documents.js ├── emails.js ├── home.js ├── orders.js ├── products.js ├── psp.js ├── shops.js ├── stats.js ├── users.js ├── validate │ └── validate.js └── wallets.js ├── docs ├── backup.md ├── data │ ├── cart.json │ ├── cors-middleware-express-with-proxy.js │ ├── product-coop.html │ ├── products-a.json │ ├── products-b.json │ ├── twitter.json │ └── user.json └── style_site.css ├── emails ├── billing │ ├── billing.html │ └── text.ejs ├── confirm │ ├── html.ejs │ ├── style.css │ └── text.ejs ├── karibou-question │ ├── html.ejs │ └── text.ejs ├── order-billing │ ├── html.ejs │ ├── html.invoice_ejs │ └── text.ejs ├── order-cancel │ ├── html.ejs │ └── text.ejs ├── order-new │ ├── html.ejs │ └── text.ejs ├── order-prepare │ ├── html.ejs │ └── text.ejs ├── order-refund │ ├── html.ejs │ └── text.ejs ├── order-reminder │ └── html.ejs ├── password │ ├── html.ejs │ ├── style.css │ └── text.ejs ├── shop-status │ ├── html.ejs │ └── text.ejs ├── simple │ ├── html.ejs │ ├── style.css │ └── text.ejs └── wallet-new │ ├── html.ejs │ └── text.ejs ├── karibou.sublime-project ├── maintain.js ├── maintain ├── 0001.mongo_script.js ├── 0002.product_photo_to_object.js ├── 0003.shop_options_to_details.js ├── 0004.shop_address_localtion_to_geo.js ├── 0005.shop_localtion_to_address_location.js ├── 0006.product_category_is_no_more_an_array.js ├── 0007.users_address_region_ge_to_geneve.js ├── 0008.order_drop_unknow_index_ac.js ├── 0009.users_login_date.js ├── 0010.orders_close_all_that_are_cancel.js ├── 0011.orders_close_all_that_are_failures.js ├── 0012.users_address_region_has_new_enum.js ├── 0013.shops_address_region_has_new_enum.js ├── 0014.convert_user_likes_from_object_type_to_sku.js ├── 0015.users_payment_rename_issuer.js ├── 0016.product_photo_https_url.js ├── 0017.category_photo_https_url.js ├── 0018.shop_photo_https_url.js ├── 0019.category_photo_to_uploadcare.js ├── 0020.shop_photo_https_url.js ├── 0021.product_photo_to_uploadcare.js ├── 0022.shop_available_weekdays.js ├── 0023.shop_account_bm.js ├── 0024.orders_add_vendor_bm.js ├── 0025.orders_add_shipping_fees.js ├── 0026.orders_add_payment_provider.js ├── 0027.product_slug_title.js ├── 0028.default_activity_for_products.js ├── 0029.users_payment_wallet.js ├── 0030.i18n-documents.js ├── 0031.i18n-config.js ├── 0032.orders_fees_charge.js └── includes │ └── uploadcare.js ├── models ├── activities.js ├── categories.js ├── config.js ├── db.maintain.js ├── documents.js ├── emails.js ├── lib │ ├── order.core.js │ ├── order.finds.js │ ├── order.format.js │ ├── order.stats.js │ └── order.utils.js ├── order.js ├── products.js ├── sequences.js ├── shops.js └── users.js ├── newrelic.js ├── package.json ├── public ├── css │ ├── bootstrap-responsive.min.css │ ├── bootstrap-white.min.css │ ├── bootstrap.min.css │ ├── bootswatch.css │ └── style.css ├── img │ ├── anais.jpg │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ ├── hello-home.jpg │ └── karibou.jpg └── js │ ├── bootstrap.min.js │ └── jquery-1.9.0.min.js ├── script └── node-continuous.sh ├── test ├── api.category.find.js ├── api.category.js ├── api.documents.js ├── api.emails.validation.js ├── api.orders.create.js ├── api.orders.email.js ├── api.orders.find.js ├── api.orders.payment.capture.js ├── api.orders.payment.js ├── api.orders.report.js ├── api.orders.security.js ├── api.orders.update.js ├── api.products.error.js ├── api.products.find.js ├── api.products.find.sort.js ├── api.products.js ├── api.products.status.find.js ├── api.seo.js ├── api.shops.admin.js ├── api.shops.js ├── api.users.addresses.js ├── api.users.create.js ├── api.users.js ├── api.users.likes.js ├── api.users.payment.js ├── api.users.payment.wallets.js ├── api.users.payment.webhook.js ├── api.users.phones.js ├── api.users.status.js ├── api.users.update.secure.admin.js ├── api.users.update.secure.js ├── category.js ├── db.maintain.js ├── fixtures.js ├── fixtures │ ├── Categories.js │ ├── Documents.js │ ├── Orders.find.js │ ├── Orders.js │ ├── Orders.logistic.js │ ├── Orders.payment.capture.js │ ├── Orders.payment.js │ ├── Orders.price.js │ ├── Orders.price.merchant.js │ ├── Orders.report.022016.js │ ├── Orders.report.discount.js │ ├── Orders.report.js │ ├── Orders.update.js │ ├── Orders.validate.js │ ├── Products.disabled.js │ ├── Products.js │ ├── Products.more.js │ ├── Products.sort.js │ ├── Shops.available.js │ ├── Shops.js │ ├── Users.js │ ├── Users.reminder.js │ ├── Wallets.js │ └── dbtools.js ├── mocha.opts ├── order.create.js ├── order.create.race.js ├── order.create.success.js ├── order.create.success.withdiscount.js ├── order.date.js ├── order.find.admin.js ├── order.find.product.js ├── order.find.shop.js ├── order.find.user.js ├── order.logistic.js ├── order.payment.js ├── order.report.includeDiscount.js ├── order.update.js ├── order.validate.item.js ├── order.validate.price.js ├── order.validate.price.merchant.js ├── order.validate.report.022016.js ├── order.validate.report.js ├── products.create.js ├── products.find.disabled.js ├── products.find.js ├── products.find.more.js ├── products.js ├── sequences.js ├── shops.find.available.js ├── shops.js ├── system.date.js ├── system.js ├── users.js ├── users.likes.js └── users.reminder.js ├── update-gh-pages.sh └── views ├── 500.jade ├── cookie.jade ├── document.jade ├── home.jade ├── homeseo.jade ├── index.jade ├── layout.jade ├── login.jade ├── product.jade ├── products.jade ├── pspstd.jade ├── pspsuccess.jade ├── register.jade ├── shop.jade ├── shops.jade └── welcome.jade /.gitignore: -------------------------------------------------------------------------------- 1 | config/config-production.js 2 | node_modules 3 | node_modules.* 4 | tips 5 | backups 6 | karibou.sublime-workspace 7 | config-production.js 8 | tmp 9 | docs 10 | 11 | # Compiled source # 12 | ################### 13 | *.com 14 | *.class 15 | *.dll 16 | *.exe 17 | *.o 18 | *.so 19 | 20 | # Packages # 21 | ############ 22 | # it's better to unpack these files and commit the raw source 23 | # git has its own built in compression methods 24 | *.7z 25 | *.dmg 26 | *.gz 27 | *.iso 28 | *.jar 29 | *.rar 30 | *.tar 31 | *.zip 32 | 33 | # Logs and databases # 34 | ###################### 35 | *.log 36 | *.sql 37 | *.sqlite 38 | 39 | # OS generated files # 40 | ###################### 41 | .DS_Store 42 | .DS_Store? 43 | ._* 44 | .Spotlight-V100 45 | .Trashes 46 | Icon? 47 | ehthumbs.db 48 | 49 | 50 | # Ignore list for Eagle, a PCB layout tool 51 | 52 | # Backup files 53 | *.s#? 54 | *.b#? 55 | *.l#? 56 | 57 | # Eagle project file 58 | # It contains a serial number and references to the file structure 59 | # on your computer. 60 | # comment the following line if you want to have your project file included. 61 | eagle.epf 62 | 63 | # CAM files 64 | *.$$$ 65 | *.cmp 66 | *.ly2 67 | *.l15 68 | *.sol 69 | *.plc 70 | *.stc 71 | *.sts 72 | *.crc 73 | *.crs 74 | 75 | *.dri 76 | *.drl 77 | *.gpi 78 | *.pls 79 | 80 | *.drd 81 | *.drd.* 82 | 83 | *.info 84 | 85 | *.eps 86 | 87 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.2.6" 4 | services: 5 | - mongodb 6 | 7 | sudo: false 8 | 9 | addons: 10 | apt: 11 | sources: 12 | - mongodb-upstart 13 | - mongodb-3.0-precise 14 | packages: 15 | - mongodb-org-server 16 | - mongodb-org-shell 17 | 18 | env: 19 | - NODE_ENV="test" 20 | 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | REPORTER ?= dot 3 | SRC = $(shell find lib -name "*.js" -type f | sort) 4 | SUPPORT = $(wildcard support/*.js) 5 | 6 | 7 | test: test-unit 8 | 9 | test-all: test-unit 10 | 11 | 12 | test-unit: 13 | @NODE_ENV=test ./node_modules/.bin/mocha \ 14 | --reporter $(REPORTER) \ 15 | test/*.js 16 | 17 | clean: 18 | rm -fr lib-cov 19 | rm -f coverage.html 20 | 21 | test-cov: lib-cov 22 | @COV=1 $(MAKE) test REPORTER=html-cov > coverage.html 23 | 24 | lib-cov: 25 | @rm -fr ./$@ 26 | @jscoverage models $@ 27 | 28 | coverage: 29 | jscoverage --no-highlight lib lib-cov 30 | @NODE_ENV=test EXAMPLE_COV=1 ./node_modules/.bin/mocha -R html-cov > coverage.html 31 | rm -rf lib-cov 32 | 33 | .PHONY: test-cov test test-all test-unit clean test-cov lib-cov 34 | -------------------------------------------------------------------------------- /app/bus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Karibou Marketplace, Olivier Evalet 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License, version 3, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU Affero General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU Affero General Public License 14 | * along with this program. If not, see . 15 | * 16 | * As a special exception, the copyright holders give permission to link the 17 | * code of portions of this program with the OpenSSL library under certain 18 | * conditions as described in each individual source file and distribute 19 | * linked combinations including the program with the OpenSSL library. You 20 | * must comply with the GNU Affero General Public License in all respects for 21 | * all of the code used other than as permitted herein. If you modify file(s) 22 | * with this exception, you may extend this exception to your version of the 23 | * file(s), but you are not obligated to do so. If you do not wish to do so, 24 | * delete this exception statement from your version. If you delete this 25 | * exception statement from all source files in the program, then also delete 26 | * it in the license file. 27 | */ 28 | 29 | var util = require("util"); 30 | var events = require("events"); 31 | var Q = require('q'); 32 | 33 | 34 | 35 | var Bus=function(){ 36 | events.EventEmitter.call(this); 37 | } 38 | 39 | 40 | 41 | Bus.prototype.listeners=function(event){ 42 | events.EventEmitter.listenerCount(Bus, event) 43 | } 44 | 45 | util.inherits(Bus, events.EventEmitter); 46 | 47 | var bus=new Bus() 48 | 49 | // hooking 50 | /** 51 | var _emit = Bus.prototype.emit; 52 | var _on = Bus.prototype.on; 53 | 54 | // 55 | // make this Bus promise when no callback is used 56 | // This is a simple hook, 57 | bus.emit = function (name) { 58 | var deferred = Q.defer(); 59 | var args = Array.prototype.slice.call(arguments); 60 | var hasCallback=(typeof arguments[arguments.length-1]==='function'); 61 | function emitCallback () { 62 | var args = Array.prototype.slice.call(arguments); 63 | if(args[0]){ 64 | return deferred.reject(args[0]); 65 | } 66 | deferred.resolve(Array.prototype.slice.call(arguments,1)); 67 | return; 68 | } 69 | // 70 | // if no callback we use promise 71 | if(!hasCallback){ 72 | args.push(emitCallback) 73 | } 74 | _emit.apply(bus, args); 75 | return deferred.promise; 76 | }; 77 | 78 | 79 | 80 | 81 | bus.on = function (name, fn) { 82 | _on.call(bus, name,fn); 83 | }; 84 | */ 85 | 86 | module.exports=bus; -------------------------------------------------------------------------------- /app/bus.routes.activity.js: -------------------------------------------------------------------------------- 1 | var Activity = require('mongoose').model('Activities'), 2 | Q=require('q'); 3 | 4 | var log=exports.log=function(who, what, content, cb) { 5 | var deferred = Q.defer(); 6 | 7 | Activity.create(who,what,content,function (err,result) { 8 | 9 | if(err){ 10 | if(cb){return cb(err);} 11 | return deferred.reject(err); 12 | } 13 | if(cb){ 14 | return cb(0,result) 15 | } 16 | deferred.resolve(result); 17 | }) 18 | 19 | // 20 | // flexible use of cb or promise 21 | return deferred.promise; 22 | } 23 | 24 | exports.create=function(who, what, content, cb) { 25 | what.action='create'; 26 | return log(who,what,content,cb); 27 | }; 28 | 29 | exports.update=function(who, what, content, cb) { 30 | what.action='update'; 31 | return log(who,what,content,cb); 32 | }; 33 | 34 | exports.delete=function(who, what, content, cb) { 35 | what.action='delete'; 36 | return log(who,what,content,cb); 37 | }; 38 | 39 | 40 | exports.error=function(who, what, content, cb) { 41 | what.action='error'; 42 | return log(who,what,content,cb); 43 | }; 44 | 45 | -------------------------------------------------------------------------------- /app/bus.routes.cron.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/ncb000gt/node-cron 3 | */ 4 | module.exports =function(bus) { 5 | var CronJob=new require('cron').CronJob, 6 | jobs=[], 7 | Q=require('q'); 8 | 9 | 10 | if(config.cron&&config.cron.length){ 11 | config.cron.forEach(function(cron) { 12 | var job=new CronJob(cron.time, function() { 13 | bus.emit(cron.task,cron,bus); 14 | }, null, true, config.timezone); 15 | 16 | console.log('DEBUG cronjob: install task',cron) 17 | jobs.push(job); 18 | 19 | }); 20 | 21 | } 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /app/bus.routes.js: -------------------------------------------------------------------------------- 1 | var bus = require("./bus"), 2 | mailchimp =require("./bus.routes.mailchimp"), 3 | activity =require("./bus.routes.activity"), 4 | users =require("./bus.routes.users"), 5 | cron =require("./bus.routes.cron")(bus), 6 | dummy=function(){}, 7 | onTrace, onMessage; 8 | 9 | 10 | 11 | onTrace=function(token,error,user){ 12 | var msg=JSON.stringify(error,null,2); 13 | 14 | bus.emit( "sendmail", 15 | "evaleto@gmail.com","[karibou-ui] : "+error.name, 16 | {content:msg}, "simple"); 17 | 18 | }; 19 | 20 | onMessage=function(title,content){ 21 | var msg=JSON.stringify(content,null,2); 22 | bus.emit( "sendmail", 23 | "info@karibou.ch",title, 24 | {content:msg}, "simple"); 25 | 26 | }; 27 | 28 | onPush=function(event,git){ 29 | console.log("github ----------------",sig,id,event,req.body) 30 | } 31 | 32 | 33 | 34 | // 35 | // bus message for system 36 | bus.on('sendmail',dummy) 37 | bus.on('cron.day',dummy) 38 | bus.on('cron.day.19',dummy) 39 | bus.on('cron.week',dummy) 40 | bus.on('trace.error',onTrace) //signature(key, error) 41 | bus.on('system.message',onMessage) 42 | bus.on('github.push',onPush) 43 | 44 | // 45 | // bus message for orders 46 | bus.on('order.create',dummy) 47 | bus.on('order.rollback',dummy) 48 | bus.on('order.update.items',dummy) 49 | bus.on('order.cancel',dummy) 50 | bus.on('order.mail.reminder',users.reminder) 51 | 52 | // 53 | // bus message for users 54 | bus.on('err.user.login',dummy) 55 | bus.on('err.user.register',dummy) 56 | bus.on('user.send.password',dummy) 57 | 58 | bus.on('mailchimp.subscribe',mailchimp.subscribe) 59 | 60 | bus.on('activity.create',activity.create); 61 | bus.on('activity.update',activity.update); 62 | bus.on('activity.delete',activity.delete); 63 | bus.on('activity.error',dummy); 64 | 65 | // 66 | // bus.on('',function(mail,cb){}) 67 | //module.exports=new Bus() 68 | -------------------------------------------------------------------------------- /app/bus.routes.mailchimp.js: -------------------------------------------------------------------------------- 1 | var MailChimpAPI = require('mailchimp').MailChimpAPI; 2 | 3 | // 4 | // subscribe to mailchimp helper 5 | // content should contains: 6 | // email,fname,lname,id 7 | exports.subscribe=function(content,cb) { 8 | if(!cb)cb=function(err){}; 9 | try { 10 | var api = new MailChimpAPI(config.auth.mailchimp.key, { version : '2.0' }); 11 | var merge_vars = { 12 | EMAIL: content.email, 13 | FNAME: content.fname, 14 | LNAME: content.lname 15 | }; 16 | 17 | for (var property in content.tags) { 18 | if (content.tags.hasOwnProperty(property)) { 19 | merge_vars[property]=content.tags[property]; 20 | } 21 | } 22 | // thks mailchimp 23 | api.call('lists', 'subscribe', 24 | { id: content.id, 25 | email:{email:content.email}, 26 | merge_vars:merge_vars, 27 | double_optin:false 28 | }, cb); 29 | 30 | } catch (error) { 31 | return cb(error) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/bus.routes.users.js: -------------------------------------------------------------------------------- 1 | var Users = require('mongoose').model('Users'), 2 | Products=require('mongoose').model('Products'), 3 | Q=require('q'), 4 | debug = require('debug')('reminder'); 5 | 6 | exports.reminder=function(cron,bus) { 7 | var now=new Date(), reminder={ 8 | weekdays:[now.getDay()], 9 | time:now.getHours() 10 | }; 11 | 12 | // 13 | // list all users that subscribe to a reminder! 14 | Users.findByReminder(reminder).then(function(users) { 15 | debug("reminder: users %d ",users.length) 16 | 17 | if(!users||!users.length){ 18 | return; 19 | } 20 | 21 | // list discount products 22 | Products.findByCriteria({ 23 | status:true,instock:true,available:true,discount:true 24 | }).then(function (products) { 25 | 26 | // context 27 | var promises=users.map(function(user) { 28 | var defer=Q.defer(), mail={ 29 | user:user, 30 | origin:config.mail.origin, 31 | noCC:true, 32 | products:products.slice(0,4), 33 | withHtml:true 34 | }; 35 | 36 | // 37 | // send email 38 | bus.emit('sendmail',user.email.address, 39 | "Psst, c'est peut être le moment de préparer une commande", mail,"order-reminder", 40 | function (err,res) { 41 | if(err){return defer.reject(err);} 42 | defer.resolve(res); 43 | }); 44 | return defer.promise; 45 | }); 46 | 47 | 48 | }); 49 | 50 | 51 | }) 52 | }; 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | //var pkgname = require('../package').name; 4 | var debug = require('debug')('config'); 5 | 6 | try { 7 | var _= require('underscore') 8 | , env = (process.env.NODE_ENV || 'development') 9 | , path = require('path') 10 | , rootPath = path.normalize(__dirname + '/..') 11 | , config 12 | , test=(env==='test')?'-test':'' 13 | 14 | // try load environment specific config 15 | if(env==='production'){ 16 | config = '../config-production'; 17 | }else{ 18 | config = '../config/config-' + env; 19 | } 20 | 21 | // 22 | // make the configuration visible 23 | global.config= require(config); 24 | global.config.root=rootPath; 25 | global.config.shared=require('../config/config-shared'+test); 26 | 27 | module.exports=global.config; 28 | 29 | debug('environment: ' + env); 30 | } catch (err) { 31 | throw new Error('Config: Failed to load \'' + config + '\': ' + err); 32 | } 33 | -------------------------------------------------------------------------------- /app/db.maintain.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var async = require("async"); 3 | 4 | 5 | /** 6 | * Maintain mongo database 7 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * 22 | */ 23 | 24 | 25 | exports.update = function(db, callback){ 26 | // get sync list of scripts 27 | var scripts = fs.readdirSync('./maintain'); 28 | 29 | var versionDone; 30 | var maintain=db.collection('dbmaintains'); 31 | 32 | maintain.find().sort({version:-1}).toArray(function(err, versionColl){ 33 | 34 | if(err){ 35 | return callback(err); 36 | } 37 | 38 | versionDone = (versionColl[0])?(versionColl[0].version):(0); 39 | var logs=[]; 40 | 41 | 42 | require('async').eachSeries(scripts, function(script, eachcb){ 43 | 44 | // 45 | // get version of the script 46 | var match=script.match(new RegExp(/(\d+)\..+/)) 47 | if(!match||!match.length){ 48 | return eachcb(); 49 | } 50 | var versionScript = parseInt(match[1]); 51 | 52 | // continue if there are no more recent scripts 53 | if(versionDone >= versionScript){ 54 | return eachcb(); 55 | } 56 | 57 | versionDone++; 58 | 59 | require('../maintain/' + script).execute(db, script, function(err, log){ 60 | // 61 | // not save on error 62 | if(err){ 63 | return eachcb(script+" ERROR: "+err); 64 | } 65 | console.log(script,log) 66 | 67 | // save script-info in db 68 | maintain.save({version: versionScript, log: log}, function(err, log){ 69 | logs.push(log); 70 | eachcb(err); 71 | }); 72 | 73 | }); 74 | },function(err){ 75 | callback(err, logs); 76 | }); 77 | }); 78 | 79 | }; 80 | 81 | -------------------------------------------------------------------------------- /app/locales/en.js: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/locales/fr.js: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /app/queue.js: -------------------------------------------------------------------------------- 1 | module.exports = function(p, c) { 2 | var slice = [].slice; 3 | var bus=require('./bus'); 4 | 5 | function noop(){} 6 | 7 | var callback=function(q, i, originalCallback) { 8 | return function(e, r) { 9 | --q.active; 10 | if (!q.continueOnError&&q.error != null) return; 11 | if (!q.continueOnError && e != null) { 12 | q.error = e; // ignore new tasks and squelch active callbacks 13 | q.started = q.remaining = NaN; // stop queued tasks from starting 14 | q.notify(); 15 | } else { 16 | // original callback 17 | originalCallback&&originalCallback(e,r); 18 | q.tasks[i] = r; 19 | if (--q.remaining) q.popping || q.pop(); 20 | else q.notify(); 21 | } 22 | }; 23 | } 24 | 25 | // 26 | // 27 | var Queue= function (parallelism, continueOnError) { 28 | this.q; 29 | this.tasks = []; 30 | this.started = 0; // number of tasks that have been started (and perhaps finished) 31 | this.active = 0; // number of tasks currently being executed (started but not finished) 32 | this.remaining = 0; // number of tasks not yet finished 33 | this.popping; // inside a synchronous task callback? 34 | this.error = null; 35 | this.await = noop; 36 | this.parallelism=parallelism||1; 37 | this.continueOnError=continueOnError||true; 38 | 39 | this.all; 40 | 41 | // if (!this.parallelism) this.parallelism = Infinity; 42 | } 43 | 44 | 45 | Queue.prototype.pop=function() { 46 | while (this.popping = this.started < this.tasks.length && this.active < this.parallelism) { 47 | var i = this.started++, 48 | t = this.tasks[i], 49 | a = slice.call(t, 1), 50 | lst=a[a.length-1]; 51 | 52 | // 53 | // if callback exist, proxying it 54 | if(this.continueOnError && typeof lst ==='function'){ 55 | a[a.length-1]=callback(this, i,lst) 56 | // 57 | // if last arg is a responce 58 | }else if(lst["req"] && lst["connection"]){ 59 | lst.on("finish",callback(this,i)) 60 | }else a.push(callback(this,i)); 61 | 62 | ++this.active; 63 | t[0].apply(null, a); 64 | 65 | } 66 | } 67 | 68 | Queue.prototype.notify=function() { 69 | var me=this; 70 | setTimeout(function(){ 71 | if (me.error != null) me.await(me.error); 72 | else if (me.all) me.await(me.error, me.tasks); 73 | else me.await.apply(null, [me.error].concat(me.tasks)); 74 | },0) 75 | } 76 | 77 | 78 | /** 79 | */ 80 | Queue.prototype.defer=function() { 81 | if (!this.error) { 82 | this.tasks.push(arguments); 83 | ++this.remaining; 84 | this.pop(); 85 | } 86 | return this.q; 87 | } 88 | Queue.prototype.await=function(f) { 89 | this.await = f; 90 | this.all = false; 91 | if (!this.remaining) this.notify(); 92 | return this.q; 93 | } 94 | Queue.prototype.awaitAll=function(f) { 95 | this.await = f; 96 | this.all = true; 97 | if (!this.remaining) this.notify(); 98 | return this.q; 99 | } 100 | Queue.prototype.empty=function(f) { 101 | 102 | this.await = f; 103 | this.all = true; 104 | return this.q; 105 | } 106 | 107 | return new Queue(p,c) 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /app/validator.js: -------------------------------------------------------------------------------- 1 | 2 | var debug = require('debug')('validate'); 3 | var assert = require("assert"); 4 | 5 | 6 | var validator=require('validator'), 7 | check = validator.check, 8 | sanitize = validator.sanitize; 9 | 10 | 11 | 12 | var text=/^[a-zA-ZÀ-ÿ0-9',:;.!?$+"*ç%&°~#€^|\/\(\)=?`{}\[\]_\-\s\S]*$/, 13 | slug=/^[a-z0-9-]+$/; 14 | 15 | 16 | // 17 | // validate text 18 | validator.extend('isText', function (str) { 19 | return text.test(str) 20 | }); 21 | 22 | // 23 | // validate boolean 24 | validator.extend('isBoolean', function (str) { 25 | return /^(true|false)$/.test(str) 26 | }); 27 | 28 | // 29 | // validate slug 30 | validator.extend('isSlug', function (str) { 31 | return slug.test(str) 32 | }); 33 | 34 | // 35 | // validate image url 36 | validator.extend('isImgUrl', function (str) { 37 | // var start=str.indexOf('http') 38 | return validator.isText(str) 39 | }); 40 | 41 | /** 42 | * wrapp validator function in object 43 | * https://github.com/ctavan/express-validator 44 | */ 45 | var Wrapper = function() {} 46 | 47 | Wrapper.prototype.check = function(str, fail_msg) { 48 | if(typeof( str ) === 'undefined' || str === null || (isNaN(str) && str.length === undefined)){ 49 | this.error(fail_msg) 50 | } 51 | this.skip=false; 52 | this.str = str+''; 53 | this.msg = fail_msg; 54 | this._errors = this._errors || []; 55 | return this; 56 | } 57 | 58 | Wrapper.prototype.ifCheck = function(str, fail_msg) { 59 | if(typeof( str ) === 'undefined' || str === null || (isNaN(str) && str.length === undefined)){ 60 | this.skip=true; 61 | return this; 62 | } 63 | return this.check(str,fail_msg) 64 | } 65 | 66 | // check for old version here https://github.com/chriso/validator.js/blob/0c5ced087434ed0b5623700955be18f24980dea0/validator.js 67 | 68 | Wrapper.prototype.error = function(msg) { 69 | throw new Error(msg); 70 | } 71 | 72 | constructWrapper=function(name,defmsg){ 73 | return function(){ 74 | var args=[this.str];for(var i in arguments){ 75 | args.push(arguments[i]) 76 | } 77 | if (!this.skip&&!validator[name].apply(this,args)) { 78 | //if(defmsg)this.msg=this.msg.replace('%msg',defmsg) 79 | return this.error(this.msg); 80 | } 81 | return this; 82 | } 83 | } 84 | 85 | Wrapper.prototype.isEmail = constructWrapper('isEmail'); 86 | Wrapper.prototype.isDate = constructWrapper('isDate'); 87 | Wrapper.prototype.isText = constructWrapper('isText'); 88 | Wrapper.prototype.isAlpha = constructWrapper('isAlpha'); 89 | Wrapper.prototype.isHexadecimal = constructWrapper('isHexadecimal'); 90 | Wrapper.prototype.isNumeric = constructWrapper('isNumeric'); 91 | Wrapper.prototype.isFloat = constructWrapper('isFloat'); 92 | Wrapper.prototype.isInt = constructWrapper('isInt'); 93 | Wrapper.prototype.isBoolean = constructWrapper('isBoolean'); 94 | Wrapper.prototype.isUrl = constructWrapper('isURL'); 95 | Wrapper.prototype.isImgUrl = constructWrapper('isImgUrl'); 96 | Wrapper.prototype.isSlug = constructWrapper('isSlug'); 97 | Wrapper.prototype.isAlphanumeric=constructWrapper('isAlphanumeric'); 98 | Wrapper.prototype.is=constructWrapper('matches'); 99 | Wrapper.prototype.len=constructWrapper('isLength'); 100 | 101 | 102 | var inline = new Wrapper(); 103 | 104 | 105 | // export check 106 | exports.check = function(str, fail_msg) { 107 | return inline.check(str, fail_msg); 108 | } 109 | 110 | // export conditional check 111 | exports.ifCheck = function(str, fail_msg) { 112 | return inline.ifCheck(str, fail_msg); 113 | } 114 | 115 | // export validator 116 | exports.validator = validator; 117 | 118 | 119 | -------------------------------------------------------------------------------- /controllers/emails.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * home 4 | */ 5 | 6 | 7 | var db = require('mongoose'); 8 | var Emails = db.model('Emails'); 9 | 10 | var check = require('../app/validator').check, 11 | sanitize = require('../app/validator').sanitize, 12 | errorHelper = require('mongoose-error-helper').errorHelper, 13 | bus=require('../app/bus'), 14 | _=require('underscore'); 15 | 16 | 17 | 18 | exports.create=function (req, res) { 19 | 20 | try{ 21 | check(req.user.email.address, "Vous devez avoir une adresse email valide").len(3, 34).isEmail(); 22 | }catch(err){ 23 | return res.status(400).send( err.message); 24 | } 25 | 26 | 27 | db.model('Emails').create(req.user, function(err,validate){ 28 | if(err){ 29 | return res.status(400).send(err); 30 | } 31 | 32 | var content=req.user.toObject(); 33 | content.validate=validate; 34 | content.origin=req.header('Origin')||config.mail.origin; 35 | console.log(content.origin+'/validate/'+validate.uid+'/'+validate.email) 36 | bus.emit('sendmail',req.user.email.address, 37 | "Confirmation de votre adresse e-mail", 38 | content, 39 | "confirm", function(err, status){ 40 | if(err){ 41 | console.log(err,status) 42 | return validate.remove(function(){ 43 | return res.status(400).send('Oops, quelque chose est allé de travers avec la messagerie: '+err.message); 44 | }); 45 | 46 | } 47 | 48 | res.json(validate); 49 | }) 50 | }); 51 | }; 52 | 53 | 54 | exports.validate=function (req, res) { 55 | // 56 | // check email owner 0 57 | try{ 58 | check(req.params.uid).len(40).isAlphanumeric(); 59 | check(req.params.email).len(3,40).isEmail(); 60 | }catch(err){ 61 | return res.status(400).send( err.message); 62 | } 63 | 64 | Emails.validate(req.params.uid,req.params.email,function (err,user){ 65 | 66 | if (err){ 67 | return res.status(400).send(err); 68 | } 69 | 70 | 71 | return res.json(user.toObject()); 72 | }); 73 | }; 74 | 75 | 76 | 77 | exports.list=function (req, res) { 78 | Emails.find({})/*.where("status",true)*/.exec(function (err,emails){ 79 | if (err){ 80 | return res.status(400).send(errorHelper(err)); 81 | } 82 | 83 | 84 | return res.json(emails); 85 | }); 86 | 87 | }; 88 | -------------------------------------------------------------------------------- /controllers/home.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * home 4 | */ 5 | 6 | var db = require('mongoose'), 7 | Shops = db.model('Shops'), 8 | Products = db.model('Products'), 9 | validate = require('./validate/validate'), 10 | _=require('underscore'), 11 | errorHelper = require('mongoose-error-helper').errorHelper; 12 | 13 | exports.index = function(app){ 14 | return function(req, res) { 15 | var model={ 16 | api: app._router.stack, 17 | user: req.user, 18 | _:_, 19 | filter:function(api){ 20 | return _.filter(api, function(route){return route.path.indexOf("/v1")==-1;}); 21 | } 22 | }; 23 | res.render('home', model); 24 | } 25 | }; 26 | 27 | exports.welcome = function(req,res){ 28 | res.render('welcome'); 29 | }; 30 | 31 | 32 | exports.SEO = function(req,res){ 33 | var lang=req.session.lang||config.shared.i18n.defaultLocale; 34 | 35 | // 36 | // get the list of cats 37 | db.model('Categories').find({},function (err,cats) { 38 | // 39 | // setup the model 40 | var model={ 41 | categories:cats, 42 | user: req.user, 43 | _:_, 44 | getLocal:function(item){ 45 | if(item) return item[lang];return item; 46 | }, 47 | prependUrlImage:function (url) { 48 | if(url&&url.indexOf('//')===0){ 49 | url='https:'+url; 50 | } 51 | return url; 52 | } 53 | }; 54 | 55 | return res.render('homeseo', model); 56 | }) 57 | 58 | }; 59 | 60 | -------------------------------------------------------------------------------- /controllers/psp.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * API introspection 4 | */ 5 | var _ = require('underscore'), 6 | bus=require('../app/bus'), 7 | sm = require('sitemap'), 8 | db = require('mongoose'), 9 | http = require('http'), 10 | validate = require('./validate/validate'), 11 | postfinance =require('node-postfinance'), 12 | payment = require('../app/payment'), 13 | debug = require('debug')('api'), 14 | errorHelper = require('mongoose-error-helper').errorHelper; 15 | 16 | 17 | 18 | // 19 | // PSP callback /v1/psp/:token/webhook 20 | // only used for online alias creation 21 | // we can save POstfinance Card payment method one we get a valid webhook 22 | // FIXME this should be in the postfinance module 23 | exports.webhook=function(req,res){ 24 | 25 | // 26 | // checks webhook config 27 | if(!config.admin.webhook||!config.admin.webhook.secret){ 28 | return res.send(401); 29 | } 30 | 31 | // 32 | // check webhook secret 33 | if(config.admin.webhook.secret!==req.params.token){ 34 | return res.send(401); 35 | } 36 | 37 | debug("webhook payload ",req.body); 38 | 39 | // check action is createAlias 40 | if(!req.body.createAlias ||!req.body.ALIAS){ 41 | return res.send(200); 42 | } 43 | 44 | 45 | // 46 | // validate SHA 47 | if(!payment.for(req.body.BRAND).isValidSha(req.body)){ 48 | return res.status(400).send("The calculated hash values and the transmitted hash values do not coincide."); 49 | } 50 | 51 | var alias=(req.body.user+req.body.BRAND.toLowerCase()).hash(), safePayment={} 52 | var month=parseInt(req.body.ED.substring(0,2),10) 53 | var year=parseInt(req.body.ED.substring(2,4))+2000; 54 | safePayment.alias=req.body.ALIAS.crypt(); 55 | safePayment.type=req.body.BRAND.toLowerCase(); 56 | safePayment.name=req.body.CN; 57 | safePayment.number=req.body.CARDNO; 58 | safePayment.expiry=month+'/'+year; 59 | safePayment.updated=Date.now(); 60 | 61 | // add this payment in the order 62 | 63 | // Users.findOne({id: req.body.user}, function(err,user){ 64 | // if(err){ 65 | // return res.status(400).send(errorHelper(err)) 66 | // } 67 | // if(!user){ 68 | // return res.status(400).send( "Utilisateur inconnu"); 69 | // } 70 | 71 | // return user.addAndSavePayment(safePayment,function (err) { 72 | // res.render('pspsuccess'); 73 | // }) 74 | // }); 75 | 76 | 77 | 78 | } 79 | 80 | 81 | exports.pspCharge=function (req,res) { 82 | var user=req.user, 83 | amount=parseFloat(req.body.amount); 84 | 85 | if(/*!amount||*/!user.id||!user.email||!user.email.status){ 86 | return res.status(400).send('psp format error!') 87 | } 88 | 89 | // 90 | // prepare the card 91 | var postfinanceCard = { 92 | paymentMethod: 'postfinance card', 93 | email:user.email.address, 94 | firstName: user.name.givenName, 95 | lastName: user.name.familyName, 96 | inline:true 97 | }; 98 | 99 | var card = new postfinance.Card(postfinanceCard); 100 | 101 | 102 | transaction = new postfinance.Transaction({ 103 | operation: 'capture', 104 | amount:amount||100, 105 | orderId: 'TX'+Date.now(), 106 | email:user.email.address, 107 | inline:{ 108 | bgcolor:'#F2F4F2', 109 | tp:config.payment.postfinance.tp, 110 | paramplus:'transferWallet=true&user='+user.id, 111 | } 112 | }); 113 | 114 | 115 | transaction.process(card, function(err,form){ 116 | if(err){ 117 | return res.status(400).send(errorHelper(err)) 118 | } 119 | res.json(form) 120 | }); 121 | 122 | } 123 | 124 | 125 | // 126 | // empty PSP template 127 | exports.pspStd=function(req,res){ 128 | res.render('pspstd'); 129 | } -------------------------------------------------------------------------------- /controllers/stats.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * API introspection 4 | */ 5 | var _ = require('underscore'), 6 | bus=require('../app/bus'), 7 | sm = require('sitemap'), 8 | db = require('mongoose'), 9 | http = require('http'), 10 | validate = require('./validate/validate'), 11 | payment = require('../app/payment'), 12 | debug = require('debug')('api'), 13 | errorHelper = require('mongoose-error-helper').errorHelper; 14 | 15 | 16 | 17 | /** 18 | * get matrix products vs users 19 | */ 20 | exports.favoriteProductsVsUsers = function(req,res){ 21 | db.model('Orders').favoriteProductsVsUsers(function (err, stats) { 22 | if(err){ 23 | return res.status(400).send( errorHelper(err.message||err)); 24 | } 25 | res.json(stats); 26 | }); 27 | }; 28 | 29 | 30 | /** 31 | * get user grouped by postalCode VS. orders by postalCode 32 | */ 33 | exports.ordersByPostalVsUsersByPostal=function (req,res) { 34 | db.model('Orders').ordersByPostalVsUsersByPostal({},function (err,stats) { 35 | if(err){ 36 | return res.status(400).send( errorHelper(err.message||err)); 37 | } 38 | res.json(stats); 39 | }); 40 | }; 41 | 42 | /** 43 | * count orders by users 44 | */ 45 | exports.ordersByUsers=function (req,res) { 46 | db.model('Orders').ordersByUsers({},function (err,stats) { 47 | if(err){ 48 | return res.status(400).send( errorHelper(err.message||err)); 49 | } 50 | res.json(stats); 51 | }); 52 | }; 53 | 54 | /** 55 | * Compute CA grouped by Year and Week 56 | */ 57 | exports.getSellValueByYearAndWeek = function(req,res){ 58 | db.model('Orders').getSellValueByYearAndWeek({},function (err, stats) { 59 | if(err){ 60 | return res.status(400).send( errorHelper(err.message||err)); 61 | } 62 | res.json(stats); 63 | }); 64 | }; 65 | 66 | /** 67 | * Compute CA grouped by Year and Week 68 | */ 69 | exports.getCAByYearMonthAndVendor = function(req,res){ 70 | var filter={}; 71 | if(req.params.shopname){ 72 | filter['items.vendor']=req.params.shopname; 73 | } 74 | if(req.query.month){ 75 | filter['month']=req.query.month; 76 | } 77 | if(req.query.year){ 78 | filter['year']=req.query.year; 79 | } 80 | 81 | db.model('Orders').getCAByYearMonthAndVendor(filter,function (err, stats) { 82 | if(err){ 83 | return res.status(400).send( errorHelper(err.message||err)); 84 | } 85 | res.json(stats); 86 | }); 87 | }; 88 | -------------------------------------------------------------------------------- /docs/backup.md: -------------------------------------------------------------------------------- 1 | ### Mongodb backup and restor 2 | * dump database ```mongodump -h :31608 -d karibou-devel -u -p -o .``` 3 | * restore db ```mongorestore --drop -h localhost -d karibou-deve``` 4 | -------------------------------------------------------------------------------- /docs/data/cart.json: -------------------------------------------------------------------------------- 1 | { 2 | '_id': 'objectid', 3 | 'user_id': 'objectid', 4 | 'state': 'cart', 5 | 6 | 'line_items': [ 7 | {'sku': 'jc-432', 8 | 'name': 'John Coltrane: A Love Supreme', 9 | 'retail_price': 1099 10 | }, 11 | 12 | {'sku': 'ly-211', 13 | 'name': 'Larry Young: Unity', 14 | 'retail_price': 1199 15 | }, 16 | ], 17 | 18 | 'shipping_address': { 19 | 'street': '3333 Greene Ave.', 20 | 'city': 'Brooklyn', 21 | 'state': 'NY', 22 | 'zip': '11216' 23 | }, 24 | 25 | 'subtotal': 2199 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/data/cors-middleware-express-with-proxy.js: -------------------------------------------------------------------------------- 1 | // node.js proxy server example for adding CORS headers to any existing http services. 2 | // yes, i know this is super basic, that's why it's here. use this to help understand how http-proxy works with express if you need future routing capabilities 3 | 4 | var httpProxy = require('http-proxy'), 5 | express = require('express'); 6 | 7 | var proxy = new httpProxy.RoutingProxy(); 8 | 9 | var proxyOptions = { 10 | host: '192.168.3.11', 11 | port: 8080 12 | }; 13 | 14 | var app = express.createServer(); 15 | 16 | var allowCrossDomain = function(req, res, next) { 17 | console.log('allowingCrossDomain'); 18 | res.header('Access-Control-Allow-Origin', '*'); 19 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 20 | res.header('Access-Control-Allow-Headers', 'X-Requested-With, Accept, Origin, Referer, User-Agent, Content-Type, Authorization, X-Mindflash-SessionID'); 21 | 22 | // intercept OPTIONS method 23 | if ('OPTIONS' == req.method) { 24 | res.send(200); 25 | } 26 | else { 27 | next(); 28 | } 29 | }; 30 | 31 | app.configure(function() { 32 | app.use(allowCrossDomain); 33 | }); 34 | 35 | app.all('/*', function (req, res) { 36 | return proxy.proxyRequest(req, res, proxyOptions); 37 | }); 38 | 39 | app.listen(9000); 40 | 41 | console.log('#########\nListening on 9000\n##########'); -------------------------------------------------------------------------------- /docs/data/product-coop.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Naturaplan Bio Rumsteak de boeuf 2 pièces 5 | 6 |
7 |
8 |

12.75

9 |
10 |
11 |
12 | 13 | x 14 | + 15 | - 16 |
17 |
18 |
19 |
20 |
21 | 22 | Naturaplan 23 | 24 |
25 |
26 |

 

27 | 28 | Naturaplan Bio Rumsteak de boeuf 2 pièces 29 | 30 |
31 |

170g

32 |

7.50 / 100g

33 |
34 | 37 | 40 |
41 |
42 | -------------------------------------------------------------------------------- /docs/data/products-a.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "My Awesome T-shirt", 4 | "description": "All about the details. Of course it's black.", 5 | 6 | "image": { 7 | "source": "picture.io", 8 | "url": "images/products/1234/main.jpg" 9 | }, 10 | 11 | 12 | "categories": [ 13 | { "name": "Clothes" }, 14 | { "name": "Shirts" } 15 | ], 16 | "style": "1234", 17 | "variants": [ 18 | { 19 | "color": "Black", 20 | "images": [ 21 | { 22 | "kind": "thumbnail", 23 | "url": "images/products/1234/thumbnail.jpg" 24 | }, 25 | { 26 | "kind": "catalog", 27 | "url": "images/products/1234/black.jpg" 28 | } 29 | ], 30 | "sizes": [ 31 | { 32 | "size": "S", 33 | "available": 10, 34 | "sku": "CAT-1234-Blk-S", 35 | "price": 99.99 36 | }, 37 | { 38 | "size": "M", 39 | "available": 7, 40 | "sku": "CAT-1234-Blk-M", 41 | "price": 109.99 42 | } 43 | ] 44 | } 45 | ], 46 | "catalogs": [ 47 | { "name": "Apparel" } 48 | ] 49 | } 50 | ] 51 | 52 | -------------------------------------------------------------------------------- /docs/data/products-b.json: -------------------------------------------------------------------------------- 1 | 2 | // db.products.find({'_id': ObjectID("4bd87bd8277d094c458d2fa5")}); 3 | 4 | [ 5 | { 6 | title: "Pâtes complètes à l'épeautre ''bio reconversion'' 500g", 7 | 8 | 9 | 10 | /* produits */ 11 | details:{ 12 | description:"Cultivé depuis l’Egypte Antique, l’épeautre a peu....", 13 | remarque:"Garder au sec et surveiller le temps de cuisso...", 14 | conservation:{"3 semaines après la date de livraison", isotherme:true}, 15 | }, 16 | attributs:{ bio:Boolean, promote:Boolean}, 17 | 18 | producteur:[Producteur], 19 | 20 | /* panier */ 21 | details:{ 22 | }, 23 | 24 | /* audio */ 25 | details: { 26 | number_of_discs: 1, 27 | label: "Impulse Records", 28 | issue_date: "December 9, 1964", 29 | average_customer_review: 4.95, 30 | asin: "B0000A118M" 31 | }, 32 | 33 | bio:true, 34 | promo:true, 35 | 36 | 37 | "vendor":ObjectId, 38 | 39 | 40 | "pricing": { 41 | "list": 1198, 42 | "retail": 1099, 43 | "savings": 99, 44 | "pct_savings": 8 45 | }, 46 | 47 | "categories": [ 48 | ObjectID, 49 | ObjectID, 50 | ObjectID 51 | ] 52 | } 53 | ] 54 | -------------------------------------------------------------------------------- /docs/data/user.json: -------------------------------------------------------------------------------- 1 | http://portablecontacts.net/draft-spec.html#schema 2 | 3 | { 4 | "id": "703887", 5 | "displayName": "Mork Hashimoto", 6 | "name": { 7 | "familyName": "Hashimoto", 8 | "givenName": "Mork" 9 | }, 10 | "birthday": "0000-01-16", 11 | "gender": "male", 12 | "drinker": "heavily", 13 | "tags": [ 14 | "plaxo guy", 15 | "favorite" 16 | ], 17 | "emails": [ 18 | { 19 | "value": "mhashimoto-04@plaxo.com", 20 | "type": "work", 21 | "primary": "true" 22 | }, 23 | { 24 | "value": "mhashimoto-04@plaxo.com", 25 | "type": "home" 26 | }, 27 | { 28 | "value": "mhashimoto@plaxo.com", 29 | "type": "home" 30 | } 31 | ], 32 | "urls": [ 33 | { 34 | "value": "http://www.seeyellow.com", 35 | "type": "work" 36 | }, 37 | { 38 | "value": "http://www.angryalien.com", 39 | "type": "home" 40 | } 41 | ], 42 | "phoneNumbers": [ 43 | { 44 | "value": "KLONDIKE5", 45 | "type": "work" 46 | }, 47 | { 48 | "value": "650-123-4567", 49 | "type": "mobile" 50 | } 51 | ], 52 | "photos": [ 53 | { 54 | "value": "http://sample.site.org/photos/12345.jpg", 55 | "type": "thumbnail" 56 | } 57 | ], 58 | "ims": [ 59 | { 60 | "value": "plaxodev8", 61 | "type": "aim" 62 | } 63 | ], 64 | "addresses": [ 65 | { 66 | "type": "home", 67 | "streetAddress": "742 Evergreen Terrace\nSuite 123", 68 | "locality": "Springfield", 69 | "region": "VT", 70 | "postalCode": "12345", 71 | "country": "USA", 72 | "formatted": "742 Evergreen Terrace\nSuite 123\nSpringfield, VT 12345 USA" 73 | } 74 | ], 75 | "organizations": [ 76 | { 77 | "name": "Burns Worldwide", 78 | "title": "Head Bee Guy" 79 | } 80 | ], 81 | "shop": [ 82 | "shop": ObjectID, 83 | "promary": true 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /emails/billing/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour Olivier, 2 | 3 | Nous vous remercions pour votre commande du XX.2014. 4 | Vous trouverez ci-dessous le détail de votre commande 5 | ===================================================== 6 | * 1x Chaussons aux poire (250gr) 3.00 CHF 7 | * 2x Bananes (bio) 5.00 CHF 8 | * 1x riz basmati blanc (500gr) 5.00 CHF 9 | * 1x Huile d'olive extra vierge (1L) 12.00 CHF 10 | * -------------------------------------------- 11 | * Sous total 132.50 CHF 12 | * Frais de livraison 10.00 CHF 13 | * Frais de paiement 0.00 CHF 14 | * -------------------------------------------- 15 | * Montant total (provisoire) 123.50 CHF 16 | 17 | 18 | RÉCAPITULATIF 19 | ============= 20 | Numéro de commande: TX000000001 21 | Méthode de paiement: Bulletin de versement 22 | Montant total provisiore: 123.50 CHF 23 | Date de livraison: mercredi 12 décembre entre 16:00 et 18:00 24 | Adresse de livraison: 25 | Olivier Evalet 26 | 34, route de Chene 27 | 1208,Genève 28 | 29 | PAIEMENT ET MONTANT 30 | =================== 31 | Vous recevrez une facture détaillée par e-mail avant la livraison. 32 | Le total de la commande peut être différent de la facture finale à régler 33 | * Il prend en compte le prix moyen des produits à poids variable 34 | * Si un produit devait être en rupture de stock, il sera déduit de la facture finale. 35 | 36 | 37 | 38 | MERCI, 39 | 40 | | |/ / (_) | 41 | | ' / __ _ _ __ _| |__ ___ _ _ 42 | | < / _` | '__| | '_ \ / _ \| | | | 43 | | . \ (_| | | | | |_) | (_) | |_| | 44 | |_|\_\__,_|_| |_|_.__/ \___/ \__,_| 45 | 46 | -------------------------------------------------------------------------------- /emails/confirm/html.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= name.givenName %>, 2 | 3 | Afin de valider votre compte, merci de bien vouloir confirmer votre adresse e-mail en cliquant sur le lien suivant: 4 | <%= origin %>/validate/<%= validate.uid %>/<%= validate.email %> 5 | 6 | A bientôt, 7 | 8 | -- 9 | Olivier Evalet 10 | de l'équipe Karibou. 11 | 12 | -------------------------------------------------------------------------------- /emails/confirm/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= name.givenName %>, 2 | 3 | Afin de valider votre compte, merci de bien vouloir confirmer votre adresse e-mail en cliquant sur le lien suivant: 4 | <%= origin %>/validate/<%= validate.uid %>/<%= validate.email %> 5 | 6 | A bientot, 7 | 8 | -- 9 | Olivier Evalet 10 | de l'équipe Karibou. 11 | 12 | -------------------------------------------------------------------------------- /emails/karibou-question/html.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 |
TEST -- TEST -- TEST
3 | <% } %>Bonjour,
4 | L'utilisateur <%= user %> à une question :
5 | <% if(typeof product !== 'undefined') { %> 6 | Pour le produit: <%= product %>
7 | <% } %><% if(typeof mood !== 'undefined') { %> 8 | Concerne: <%= mood %>
9 | <% } %> 10 | 11 | <%= text %>
12 | 13 | Avec nos meilleurs messages,
14 |
15 |  | |/ /         (_) |                
16 |  | ' / __ _ _ __ _| |__   ___  _   _ 
17 |  |  < / _` | '__| | '_ \ / _ \| | | |
18 |  | . \ (_| | |  | | |_) | (_) | |_| |
19 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
20 | 
-------------------------------------------------------------------------------- /emails/karibou-question/text.ejs: -------------------------------------------------------------------------------- 1 | Hello, 2 | L'utilisateur <%= user %> (<%= email %>) à une question : 3 | <% if(typeof product !== 'undefined') { %> 4 | Pour le produit: <%= product %> 5 | <% } %><% if(typeof mood !== 'undefined') { %> 6 | Concerne: <%= mood %> 7 | <% } %> 8 | 9 | <%= text %> 10 | 11 | Avec nos meilleurs messages, 12 |
13 |  | |/ /         (_) |                
14 |  | ' / __ _ _ __ _| |__   ___  _   _ 
15 |  |  < / _` | '__| | '_ \ / _ \| | | |
16 |  | . \ (_| | |  | | |_) | (_) | |_| |
17 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
18 | 
-------------------------------------------------------------------------------- /emails/order-billing/html.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 |
TEST -- TEST -- TEST
3 | <% } %>Bonjour <%= order.customer.name.givenName %>,

4 | 5 | Veuillez trouver votre facture Karibou pour votre commande <%= order.oid %> du <%= created %>.
6 | 7 |
8 | *<% for(var i=0; i 9 | * <%if (order.items[i].fulfillment.status==='failure') {%><%}%><%= order.items[i].quantity %>x <%- order.items[i].title %> (<%- order.items[i].part %>) <%= order.items[i].finalprice.toFixed(2) %> CHF <%if (order.items[i].fulfillment.status==='failure') {%><%}%><%if (order.items[i].variant&&order.items[i].variant.title) {%> 10 | * option: <%= order.items[i].variant.title %><% } %><% } %> 11 | * ------------------ 12 | * Sous total <%= subTotal %> CHF 13 | * Frais de livraison <%= shippingFees %> CHF 14 | * Frais de paiement <%= paymentFees %> CHF<%if (extraDiscount>0) {%> 15 | * Bonus commerçants -<%= extraDiscount %> CHF<% } %> 16 | * ------------------ 17 | * Montant total <%= totalWithFees %> CHF<%if (totalDiscount>0) {%> 18 | * Rabais commerçants <%= totalDiscount %> CHF<% } %> 19 | * 20 |
21 | <%if (order.payment.issuer==='invoice') {%> 22 |
23 |
24 | Veuillez effectuer le virement bancaire (BVR) dans les 10 jours:
25 | (1) - Compte: 14-615643-8 ou CH76 0900 0000 1461 5643 8 / BIC: POFICHBEXXX
26 | (2) - Montant à payer, <%= totalWithFees %> CHF
27 | (3) - Motif versement, <%= order.customer.id %><%= order.oid %>
28 | (4) - Versement pour: Karibou Delphine Cluzel Evalet et Olivier Evalet, CH-1208 Genève
29 |
30 |
31 | <% } %> 32 | 33 | Pour toute question, veuillez répondre à ce mail.
34 | Avec nos meilleurs messages,
35 |
36 |  | |/ /         (_) |                
37 |  | ' / __ _ _ __ _| |__   ___  _   _ 
38 |  |  < / _` | '__| | '_ \ / _ \| | | |
39 |  | . \ (_| | |  | | |_) | (_) | |_| |
40 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|.ch
41 | 
42 | 43 | ----
44 | Toutes vos commandes <%= origin %>/account/orders 45 | -------------------------------------------------------------------------------- /emails/order-billing/html.invoice_ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= order.customer.name.givenName %>,

2 | 3 | Veuillez trouver votre facture Karibou pour votre commande <%= order.oid %> du <%= created %>.
4 | Veuillez utiliser cette référence pour le paiement avant le 30.12.2014: 5 | (1) - Compte: CH76 0900 0000 1461 5643 8 / BIC: POFICHBEXXX
6 | (2) - Montant à payer, <%= totalWithFees %> CHF
7 | (4) - Versement pour: Karibou Delphine Cluzel Evalet et Olivier Evalet, CH-1208 Genève
8 | 9 |
10 | *<% for(var i=0; i
11 | * <%= order.items[i].quantity %>x <%- order.items[i].title %> (<%- order.items[i].part %>) <%= order.items[i].finalprice.toFixed(2) %> CHF <% } %>
12 | * ------------------
13 | * Sous total  <%= subTotal %> CHF
14 | * Frais de livraison <%= shippingFees %> CHF
15 | * Frais de paiement <%= paymentFees %> CHF
16 | * ------------------
17 | * Montant total <%= totalWithFees %> CHF
18 | *
19 | 
20 | 21 | Pour toute question, veuillez répondre à ce mail.
22 | Avec nos meilleurs messages,
23 |
24 |  | |/ /         (_) |                
25 |  | ' / __ _ _ __ _| |__   ___  _   _ 
26 |  |  < / _` | '__| | '_ \ / _ \| | | |
27 |  | . \ (_| | |  | | |_) | (_) | |_| |
28 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
29 | 
30 | 31 | ----
32 | Toutes vos commandes <%= origin %>/account/orders 33 | -------------------------------------------------------------------------------- /emails/order-billing/text.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 | *TEST -- TEST -- TEST 3 | <% } %>Bonjour <%= order.customer.name.givenName %>, 4 | 5 | Veuillez trouver votre facture Karibou pour votre commande <%= order.oid %> du <%= created %>. 6 | 7 | 8 | *<% for(var i=0; i 9 | * <%if (order.items[i].fulfillment.status==='failure') {%><%}%><%= order.items[i].quantity %>x <%- order.items[i].title %> (<%- order.items[i].part %>) <%= order.items[i].finalprice.toFixed(2) %> CHF <%if (order.items[i].fulfillment.status==='failure') {%><%}%><%if (order.items[i].variant&&order.items[i].variant.title) {%> 10 | * option: <%= order.items[i].variant.title %><% } %><% } %> 11 | * ------------------ 12 | * Sous total <%= subTotal %> CHF 13 | * Frais de livraison <%= shippingFees %> CHF 14 | * Frais de paiement <%= paymentFees %> CHF 15 | * ------------------ 16 | * Montant total <%= totalWithFees %> CHF 17 | * 18 | 19 | <%if (order.payment.issuer==='invoice') {%> 20 | 21 | 22 | * virement bancaire dans les 1 23 | (1) - Compte: 14-615643-8 ou CH76 0900 0000 1461 5643 8 / BIC: POFICHBEXXX 24 | (2) - Montant à payer, <%= totalWithFees %> CHF 25 | (3) - Motif versement, <%= order.customer.id %><%= order.oid %> 26 | (4) - Versement pour: Karibou Delphine Cluzel Evalet et Olivier Evalet, CH-1208 Genève 27 | 28 | <% } %> 29 | 30 | Pour toute question, veuillez répondre à ce mail. 31 | Avec nos meilleurs messages, 32 | | |/ / (_) | 33 | | ' / __ _ _ __ _| |__ ___ _ _ 34 | | < / _` | '__| | '_ \ / _ \| | | | 35 | | . \ (_| | | | | |_) | (_) | |_| | 36 | |_|\_\__,_|_| |_|_.__/ \___/ \__,_| 37 | 38 | ---- 39 | Toutes vos commandes <%= origin %>/account/orders 40 | -------------------------------------------------------------------------------- /emails/order-cancel/html.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 |
TEST -- TEST -- TEST
3 | <% } %>Bonjour <%= order.customer.name.givenName %>,
4 |
5 | Votre commande <%= order.oid %> du <%= created %> à bien été annulée.
6 | Avec nos meilleurs messages,
7 |
 8 |  | |/ /         (_) |                
 9 |  | ' / __ _ _ __ _| |__   ___  _   _ 
10 |  |  < / _` | '__| | '_ \ / _ \| | | |
11 |  | . \ (_| | |  | | |_) | (_) | |_| |
12 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
13 | 
14 |
15 | ----
16 | vos commandes <%= origin %>/account/orders
17 | -------------------------------------------------------------------------------- /emails/order-cancel/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= order.customer.name.givenName %>, 2 | 3 | Votre commande <%= order.oid %> du <%= created %> à bien été annulée. 4 | Avec nos meilleurs messages, 5 | 6 | | |/ / (_) | 7 | | ' / __ _ _ __ _| |__ ___ _ _ 8 | | < / _` | '__| | '_ \ / _ \| | | | 9 | | . \ (_| | | | | |_) | (_) | |_| | 10 | |_|\_\__,_|_| |_|_.__/ \___/ \__,_| 11 | 12 | 13 | ---- 14 | vos commandes <%= origin %>/account/orders 15 | -------------------------------------------------------------------------------- /emails/order-new/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= order.customer.name.givenName %>, 2 | 3 | Nous vous remercions pour votre commande du <%= created %>. 4 | Vous trouverez ci-dessous le détail de votre commande 5 | *<% for(var i=0; i 6 | * <%= order.items[i].quantity %>x <%- order.items[i].title %> (<%- order.items[i].part %>) <%= order.items[i].finalprice.toFixed(2) %> CHF <% } %> 7 | * ------------------ 8 | * Sous total <%= subTotal %> CHF 9 | * Frais de livraison <%= shippingFees %> CHF 10 | * Frais de paiement <%= paymentFees %> CHF 11 | * ------------------ 12 | * Montant total provisoire <%= totalWithFees %> CHF 13 | * 14 | 15 | Récapitulatif 16 | ------------- 17 | Numéro de commande: <%= order.oid %> 18 | Méthode de paiement: <%= order.payment.issuer %> 19 | Montant total provisoire: <%= totalWithFees %> CHF 20 | Date de livraison: Le <%= shippingWhen %> 21 | Adresse de livraison: 22 | # <%- order.shipping.name %> 23 | # <%- order.shipping.streetAdress %> 24 | # <%- order.shipping.postalCode %>, <%- order.shipping.region %> 25 | 26 | Paiement et montant 27 | ------------------- 28 | Vous recevrez une facture détaillée par e-mail avant la livraison. 29 | Le total de la commande peut être ajusté dans le cas où vous avez commandé des produits à poids variables dont le prix est connu le jour de la livraison. 30 | Aussi, si un produit devait être en rupture de stock, il sera déduit de la facture finale. 31 | 32 | Pour toute question, veuillez répondre à ce mail. 33 | Avec nos meilleurs messages, 34 | 35 | | |/ / (_) | 36 | | ' / __ _ _ __ _| |__ ___ _ _ 37 | | < / _` | '__| | '_ \ / _ \| | | | 38 | | . \ (_| | | | | |_) | (_) | |_| | 39 | |_|\_\__,_|_| |_|_.__/ \___/ \__,_| 40 | 41 | ---- 42 | Toutes vos commandes <%= origin %>/account/orders 43 | -------------------------------------------------------------------------------- /emails/order-prepare/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= shop.owner.name.givenName %>, 2 | 3 | 4 | Vous trouverez ci-dessous les commandes à préparer pour le <%= shippingWhen %>. La collecte s'effectuera entre 10h30 et 14h00 au plus tard. 5 | <% for(var i=0; i 6 | * [<%= items[i].rank %>, <%= items[i].name.familyName %>] <%= items[i].quantity %>x<%= items[i].title %> (<%= items[i].part %>) *<%= items[i].finalprice.toFixed(2) %>*<% } %> 7 | 8 | N'oubliez-pas d'inscrire le numéro de sac sur l'emballage. 9 | Vous pouvez suivre les commandes ici <%= origin %>/admin/orders 10 | Avec mes meilleurs messages, 11 | 12 | Delphine 13 |
14 |  | |/ /         (_) |                
15 |  | ' / __ _ _ __ _| |__   ___  _   _ 
16 |  |  < / _` | '__| | '_ \ / _ \| | | |
17 |  | . \ (_| | |  | | |_) | (_) | |_| |
18 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
19 | 
20 | tel : +4179.377.21.13 -------------------------------------------------------------------------------- /emails/order-refund/html.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 |
TEST -- TEST -- TEST
3 | <% } %>Bonjour <%= order.customer.name.givenName %>,
4 |
5 | Le montant de <%= totalWithFees %> CHF, de votre commande <%= order.oid %> du <%= created %> à bien été remboursé. 6 |

7 | 8 | Avec nos meilleurs messages,
9 |
10 |  | |/ /         (_) |                
11 |  | ' / __ _ _ __ _| |__   ___  _   _ 
12 |  |  < / _` | '__| | '_ \ / _ \| | | |
13 |  | . \ (_| | |  | | |_) | (_) | |_| |
14 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
15 | 
16 |
17 | ----
18 | vos commandes <%= origin %>/account/orders
19 | -------------------------------------------------------------------------------- /emails/order-refund/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= order.customer.name.givenName %>, 2 | 3 | Le montant de <%= totalWithFees %> CHF, de votre commande <%= order.oid %> du <%= created %> à bien été remboursé. 4 | Avec nos meilleurs messages, 5 | 6 | | |/ / (_) | 7 | | ' / __ _ _ __ _| |__ ___ _ _ 8 | | < / _` | '__| | '_ \ / _ \| | | | 9 | | . \ (_| | | | | |_) | (_) | |_| | 10 | |_|\_\__,_|_| |_|_.__/ \___/ \__,_| 11 | 12 | 13 | ---- 14 | vos commandes <%= origin %>/account/orders 15 | -------------------------------------------------------------------------------- /emails/order-reminder/html.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 |
TEST -- TEST -- TEST
3 | <% } %>Bonjour <%= user.name.givenName %>,
4 |
5 | C'est peut-être le moment de faire ses courses chez vos commerçants préférés? 6 |
7 | 8 | Avec nos meilleurs messages,
9 | L'équipe de Karibou.ch 10 |
11 | ----
12 | Modifiez votre alarme <%= origin %>/account/reminder
13 | -------------------------------------------------------------------------------- /emails/password/html.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= name.givenName %>, 2 | 3 | Votre nouveau mot de passe à été créé avec succès. 4 | utilisateur: <%= email.address %> 5 | mot de passe: <%= password %> 6 | <%= origin %>/login 7 | 8 | A bientôt, 9 | 10 | -- 11 | James 12 | de l'équipe Karibou.ch 13 | -------------------------------------------------------------------------------- /emails/password/text.ejs: -------------------------------------------------------------------------------- 1 | Bonjour <%= name.givenName %>, 2 | 3 | Votre nouveau mot de passe à été créé avec succès. 4 | utilisateur: <%= email.address %> 5 | mot de passe: <%= password %> 6 | <%= origin %>/login 7 | 8 | A bientôt, 9 | 10 | -- 11 | James 12 | de l'équipe Karibou.ch 13 | 14 | -------------------------------------------------------------------------------- /emails/shop-status/html.ejs: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | L'utilisateur <%= name.givenName %>, <%= email.address %> 4 | (<%= email.status %>), demande d'activation de boutique: 5 | - <%= origin %>/shop/<%= shop.urlpath %> 6 | 7 | 8 | A bientot, 9 | 10 | -- 11 | James 12 | de l'équipe Karibou.ch 13 | 14 | -------------------------------------------------------------------------------- /emails/shop-status/text.ejs: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | L'utilisateur <%= name.givenName %>, <%= email.address %> demande d'activation de boutique: 4 | - <%= origin %>/shop/<%= shop.urlpath %> 5 | 6 | 7 | A bientot, 8 | 9 | -- 10 | James 11 | de l'équipe Karibou.ch 12 | 13 | -------------------------------------------------------------------------------- /emails/simple/html.ejs: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | <%- content %> 4 | 5 | A bientot, 6 | 7 | -- 8 | Olivier Evalet 9 | Ingénieur chez Karibou. 10 | P:(123) 456-7890 11 | 12 | -------------------------------------------------------------------------------- /emails/simple/text.ejs: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | <%- content %> 4 | 5 | A bientot, 6 | 7 | -- 8 | Olivier Evalet 9 | Ingénieur chez Karibou. 10 | P:(123) 456-7890 11 | 12 | -------------------------------------------------------------------------------- /emails/wallet-new/text.ejs: -------------------------------------------------------------------------------- 1 | <%if (develMode) {%> 2 |
TEST -- TEST -- TEST
3 | <% } %>Bonjour <%= user.name.givenName %>,
4 | Vous avez commandé une carte cadeau karibou pour un montant de <%= (wallet.balance/100).toFixed(2) %> fr. 5 | Voici le code de la carte : <%= wallet.card.number %> cette carte est valable juqu'au <%= wallet.card.expiry %>. 6 | 7 | Comment ça marche? Envoyé à la personne de votre choix, un petit mot avec la marche à suivre suivante: 8 | 1. Créer un compte chez karibou.ch, c'est essentiel pour utiliser votre carte cadeau ;) 9 | 2. Pour enregister votre nouvelle carte, allez dans votre compte personnel, et choisissez mes méthodes de paiement. 10 | 3. Ensuite, il suffit de cliquer sur le boutton [utiliser une carte cadeau]. 11 | 4. Finalement, enregistrez votre carte avec le numéro suivant <%= wallet.card.number %> . (ce code est valable jusqu'au <%= wallet.card.expiry %>) 12 | 13 | Voilà c'est fait, maintenant vous pouvez faire vos courses pour un montant de <%= (wallet.balance/100).toFixed(2) %> fr chez les commerçants de l'alimentation de Genève. 14 | 15 | 16 | Avec nos meilleurs messages, 17 |
18 |  | |/ /         (_) |                
19 |  | ' / __ _ _ __ _| |__   ___  _   _ 
20 |  |  < / _` | '__| | '_ \ / _ \| | | |
21 |  | . \ (_| | |  | | |_) | (_) | |_| |
22 |  |_|\_\__,_|_|  |_|_.__/ \___/ \__,_|
23 | 
24 |
25 | ----
26 | votre compte commercial <%= origin %>/account/wallet
27 | -------------------------------------------------------------------------------- /karibou.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "/home/evaleto/nodejs/git/karibou-api" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /maintain.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | 3 | // 4 | // 5 | var app = require('./app'); 6 | var maintain=require('./app/db.maintain'); 7 | //var mongoose=require('mongoose'); 8 | 9 | 10 | /** */ 11 | var MongoClient = require('mongodb').MongoClient; 12 | MongoClient.connect(config.mongo.name, function(err, db) { 13 | console.time("maintain is done"); 14 | maintain.update(db,function(err,log){ 15 | if(err){ 16 | console.log("ERROR",err) 17 | } 18 | console.timeEnd("maintain is done"); 19 | process.exit(1); 20 | }); 21 | 22 | }); 23 | 24 | /** 25 | mongoose.connection.on('open', function(db) { 26 | 27 | maintain.update(mongoose.connection.db,function(err,log){ 28 | console.log("maintain",err,log); 29 | process.exit(1); 30 | }); 31 | 32 | }); 33 | 34 | */ 35 | -------------------------------------------------------------------------------- /maintain/0001.mongo_script.js: -------------------------------------------------------------------------------- 1 | exports.execute = function(db, script, callback){ 2 | return callback(null, "hello world"); 3 | } 4 | -------------------------------------------------------------------------------- /maintain/0002.product_photo_to_object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all products.photo to products.photo.url"); 33 | var logs="", count=0; 34 | var products=db.collection('products'); 35 | 36 | products.find( {'photo':{$type:2 } }).toArray(function (err,p) { 37 | if (!p.length){ 38 | return callback(null, "0 product have been updated") 39 | } 40 | console.log(script,"migrating "+p.length +" products"); 41 | require('async').each(p, function(product, eachcb){ 42 | var url=product.photo; 43 | product.photo = {url:url}; 44 | products.save(product,function(err){ 45 | console.log(err, product.photo) 46 | eachcb(err); 47 | }); 48 | 49 | // products.update({_id: product._id}, {$set: {photo: {url:product.photo}}}, {w:1}, function(err) { 50 | // eachcb(err); 51 | // }); 52 | 53 | // product.save(function(err){ 54 | // assert.ok(typeof product.photo === 'object',"product.photo should be an object {url:string}") 55 | // eachcb(err); 56 | // }); 57 | }, 58 | function(err){ 59 | return callback(err, p.length+" photos on products have been updated"); 60 | }); 61 | }); 62 | 63 | } 64 | -------------------------------------------------------------------------------- /maintain/0003.shop_options_to_details.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Convert all shop.options to shop.details"); 32 | var logs="", count=0; 33 | var shops=db.collection('shops'); 34 | 35 | shops.find( {'options':{$type:3 } }).toArray(function (err,s) { 36 | if (!s.length){ 37 | return callback(null, "0 shop have been updated") 38 | } 39 | console.log(script,"migrating "+s.length +" shops"); 40 | shops.update({'options':{$type:3 } }, { $rename: { "options": "details" } }, {multi:true} ,function(err,count){ 41 | callback(err, count+" shops have been updated"); 42 | }) 43 | 44 | }); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /maintain/0004.shop_address_localtion_to_geo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | var logs="", count=0; 32 | var users=db.collection('users'); 33 | callback(null,"nothing todo") 34 | // users.find( {'address.location':{$type:3 } }).toArray(function (err,s) { 35 | // if (!s.length){ 36 | // return callback(null, "0 shop have been updated") 37 | // } 38 | // console.log(script,"migrating "+s.length +" users"); 39 | // users.update({}, { $rename: { "address.location": "address.geo" } } ,function(err){ 40 | // callback(err, s.length+" users have been updated"); 41 | // }) 42 | 43 | // }); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /maintain/0005.shop_localtion_to_address_location.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Convert all shop.location to shop.address.location"); 32 | var logs="", count=0; 33 | var shops=db.collection('shops'); 34 | 35 | shops.find( {'location':{$type:2 } }).toArray(function (err,s) { 36 | if (!s.length){ 37 | return callback(null, "0 shop have been updated") 38 | } 39 | console.log(script,"migrating "+s.length +" shops"); 40 | shops.update({'location':{$type:2 } }, { $rename: { "location": "address.location" } }, {multi:true} ,function(err,count){ 41 | callback(err, count+" shops have been updated"); 42 | }) 43 | 44 | }); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /maintain/0006.product_category_is_no_more_an_array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all products.categories to products.category"); 33 | var logs="", count=0; 34 | var products=db.collection('products'); 35 | 36 | products.find( {$where : "Array.isArray(this.categories)"}).toArray(function (err,p) { 37 | if (!p.length){ 38 | return callback(null, "0 product have been updated") 39 | } 40 | console.log(script,"migrating "+p.length +" products"); 41 | require('async').each(p, function(product, eachcb){ 42 | 43 | var cat=product.categories[0]; 44 | product.categories = cat; 45 | products.save(product,function(err){ 46 | console.log(err, product.categories) 47 | eachcb(err); 48 | }); 49 | 50 | // products.update({_id: product._id}, {$set: {photo: {url:product.photo}}}, {w:1}, function(err) { 51 | // eachcb(err); 52 | // }); 53 | 54 | // product.save(function(err){ 55 | // assert.ok(typeof product.photo === 'object',"product.photo should be an object {url:string}") 56 | // eachcb(err); 57 | // }); 58 | }, 59 | function(err){ 60 | return callback(err, p.length+" categories on products have been updated"); 61 | }); 62 | }); 63 | 64 | } 65 | -------------------------------------------------------------------------------- /maintain/0007.users_address_region_ge_to_geneve.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all user.addresses.region GE => Genève"); 33 | var logs="", count=0; 34 | var users=db.collection('users'); 35 | 36 | 37 | users.find( {"addresses.region":"GE"}).toArray(function (err,u) { 38 | if (!u.length){ 39 | return callback(null, "0 shop have been updated") 40 | } 41 | console.log(script,"updating region addresse: "+u.length ); 42 | users.update({"addresses.region":"GE"} , {$set: {"addresses.$.region": "Genève"}}, {multi:true} ,function(err,count){ 43 | callback(err, count+" users have been updated"); 44 | }) 45 | 46 | }); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /maintain/0008.order_drop_unknow_index_ac.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"drop unknow index ac"); 33 | var logs="", count=0; 34 | var orders=db.collection('orders'); 35 | // orders.getIndexes(function(e,index){ 36 | // console.log(e,index) 37 | // }) 38 | orders.dropIndex({'ac':1},function(e,idx){ 39 | var err; 40 | if(e&&e.errmsg&&e.errmsg.indexOf('find index with key')!=-1){ 41 | return callback(null,"ok") 42 | } 43 | 44 | callback(e,idx) 45 | }); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /maintain/0009.users_login_date.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Add user activities (date for login and update) on users"); 33 | var logs="", count=0, date=new Date('1972'); 34 | var users=db.collection('users'); 35 | 36 | 37 | users.find({ logged: { $exists: false}}).toArray(function (err,u) { 38 | if (!u.length){ 39 | return callback(null, "0 user have been updated") 40 | } 41 | console.log(script,"updating users activity(logged,updated) : "+u.length ); 42 | users.update({ logged: { $exists: false}} , {$set: {logged:date, updated:date}}, {multi:true},function(err){ 43 | callback(err, u.length+" users have been updated"); 44 | }) 45 | 46 | }); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /maintain/0010.orders_close_all_that_are_cancel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Close all orders that are canceled"); 33 | var logs="", count=0; 34 | var orders=db.collection('orders'); 35 | 36 | 37 | orders.find({'cancel.when':{$exists:true},closed:{$exists:false}}).toArray(function (err,o) { 38 | if (!o.length){ 39 | return callback(null, "0 orders have been updated") 40 | } 41 | console.log(script,"updating orders : "+o.length ); 42 | 43 | require('async').each(o, function(order, eachcb){ 44 | 45 | order.closed=order.cancel.when; 46 | orders.save(order,function(err){ 47 | eachcb(err); 48 | }); 49 | }, 50 | function(err){ 51 | return callback(err, o.length+" orders have been updated"); 52 | }); 53 | 54 | 55 | }); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /maintain/0011.orders_close_all_that_are_failures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Close all orders that are failure"); 33 | var logs="", count=0; 34 | var orders=db.collection('orders'); 35 | 36 | 37 | orders.find({'fulfillments.status':'failure',closed:{$exists:false}}).toArray(function (err,o) { 38 | if (!o.length){ 39 | return callback(null, "0 orders have been updated") 40 | } 41 | console.log(script,"updating orders : "+o.length ); 42 | 43 | require('async').each(o, function(order, eachcb){ 44 | 45 | order.closed=Date.now(); 46 | orders.save(order,function(err){ 47 | eachcb(err); 48 | }); 49 | }, 50 | function(err){ 51 | return callback(err, o.length+" orders have been updated"); 52 | }); 53 | 54 | 55 | }); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /maintain/0012.users_address_region_has_new_enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all user.addresses.region $ => .*GE$"); 33 | var logs="", count=0; 34 | var users=db.collection('users'); 35 | var tosave=false, errs=[],logs=[]; 36 | 37 | 38 | users.find( {"addresses.region":{$not:/GE$/}}).toArray(function (err,u) { 39 | if (!u.length){ 40 | return callback(null, "0 user have been updated") 41 | } 42 | console.log(script,"updating region addresse: "+u.length ); 43 | 44 | u.forEach(function(user){ 45 | tosave=false; 46 | user.addresses.forEach(function(add){ 47 | //console.log("user",user.id,add.region,/(Genène|France)/.test(add.region)) 48 | if(!/.*(Genève|France)$/.test(add.region)){ 49 | add.region=add.region+',GE' 50 | tosave=true; 51 | } 52 | }) 53 | if(tosave){ 54 | console.log(user) 55 | users.update({id:user.id},user,function(err){ 56 | if(errs)errs.push(err); 57 | }) 58 | } 59 | }) 60 | 61 | callback(errs.join(','), u.length+" users have been updated"); 62 | 63 | }); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /maintain/0013.shops_address_region_has_new_enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all shop.address.region $ => .*GE$"); 33 | var logs="", count=0; 34 | var Shops=db.collection('shops'); 35 | var tosave=false, errs=[],logs=[]; 36 | 37 | 38 | Shops.find( {"address.region":{$not:/GE$/}}).toArray(function (err,shops) { 39 | if (!shops.length){ 40 | return callback(null, "0 shops have been updated") 41 | } 42 | console.log(script,"updating region addresse: "+shops.length ); 43 | 44 | shops.forEach(function(shop){ 45 | if(!/.*(Genève|France)$/.test(shop.address.region)){ 46 | shop.address.region=shop.address.region+',GE' 47 | Shops.update({urlpath:shop.urlpath},shop,function(err){ 48 | if(err)errs.push(err); 49 | }) 50 | 51 | } 52 | 53 | }) 54 | 55 | callback(errs.join(','), shops.length+" shops have been updated"); 56 | 57 | }); 58 | 59 | } 60 | -------------------------------------------------------------------------------- /maintain/0014.convert_user_likes_from_object_type_to_sku.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all user likes from Product Object to SKU number"); 33 | var logs="", count=0,mapping={}; 34 | var Products=db.collection('products'); 35 | var Users=db.collection('users'); 36 | var tosave=false, errs=[],logs=[]; 37 | 38 | // find all 39 | db.collection('products').find({}).toArray(function (err,products) { 40 | products.forEach(function (product) { 41 | mapping[product._id]=product.sku 42 | }) 43 | 44 | // object id == 7 45 | Users.find( {"likes":{$type:7}}).toArray(function (err,users) { 46 | if (!users.length){ 47 | return callback(null, "0 user have been updated") 48 | } 49 | if(err){ 50 | errs.push(err) 51 | } 52 | console.log(script,"updating users likes: "+users.length ); 53 | 54 | users.forEach(function(user){ 55 | var likes=[]; 56 | user.likes.forEach(function (like) { 57 | likes.push(mapping[like]); 58 | }); 59 | // update data 60 | user.likes=likes; 61 | 62 | if(user.email)console.log('find:',user.email.address,user.likes); 63 | else console.log('find:',user.id,user.likes); 64 | Users.update({id:user.id},user,function (err) { 65 | if(err)errs.push(err); 66 | }) 67 | }) 68 | 69 | callback(errs.join(','), users.length+" users likes have been updated"); 70 | 71 | }); 72 | 73 | }); 74 | 75 | 76 | 77 | } 78 | -------------------------------------------------------------------------------- /maintain/0015.users_payment_rename_issuer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | // 32 | // { 33 | // "alias" : "8ff17caf9b429fa74d6f93a361fa2f8f44891e3ea22b41257857fdcf6be56caa0e0e0e0e", 34 | // "type" : "visa", 35 | // "name" : "oli evalet", 36 | // "number" : "40xxxxxxxxxx1881", 37 | // "csc" : "321", 38 | // "expiry" : "12/2015", 39 | // "updated" : 1413375339775 40 | // } 41 | 42 | 43 | exports.execute = function(db, script, callback){ 44 | console.log(script,"Convert all user.payments.type => payments.issuer"); 45 | var logs="", count=0; 46 | var Users=db.collection('users'); 47 | var tosave=false, errs=[],logs=[]; 48 | 49 | 50 | Users.find({'payments.type':{$exists:true}}).toArray(function (err,users) { 51 | if (!users.length){ 52 | return callback(null, "0 users have been updated") 53 | } 54 | console.log(script,"updating payment issuer: "+users.length ); 55 | 56 | for (var i = users.length - 1; i >= 0; i--) { 57 | users[i].payments.forEach(function (payment) { 58 | payment['issuer']=payment.type; 59 | delete payment.type; 60 | }) 61 | Users.update({id:users[i].id},users[i],function (err) { 62 | if(err)errs.push(err); 63 | }) 64 | }; 65 | 66 | 67 | callback(errs.join(','), users.length+" users have been updated"); 68 | 69 | }); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /maintain/0016.product_photo_https_url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | var aws={ 31 | in:"//karibou-filepicker.s3-website-eu-west-1.amazonaws.com/", 32 | out:"//s3-eu-west-1.amazonaws.com/karibou-filepicker/" 33 | }; 34 | 35 | exports.execute = function(db, script, callback){ 36 | console.log(script,"Convert all products.photo.url to be https complient"); 37 | var logs="", count=0; 38 | var products=db.collection('products'); 39 | 40 | products.find( {}).toArray(function (err,p) { 41 | if (!p.length){ 42 | return callback(null, "0 product have been updated") 43 | } 44 | console.log(script,"migrating "+p.length +" products"); 45 | 46 | // 47 | // convert url 48 | require('async').each(p, function(product, eachcb){ 49 | // 50 | if(!product.photo||!product.photo.url){ 51 | console.log('WARNING no photo available for product',product.sku) 52 | return eachcb(); 53 | } 54 | var url=product.photo.url.split(aws.in); 55 | if(url.length>1){ 56 | product.photo.url=aws.out+url[1] 57 | } 58 | console.log('rename',product.photo.url ) 59 | products.save(product,function(err){ 60 | eachcb(err); 61 | }); 62 | 63 | }, 64 | function(err){ 65 | return callback(err, p.length+" photos on products have been updated"); 66 | }); 67 | }); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /maintain/0017.category_photo_https_url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | var aws={ 31 | in:"//karibou-filepicker.s3-website-eu-west-1.amazonaws.com/", 32 | out:"//s3-eu-west-1.amazonaws.com/karibou-filepicker/" 33 | }; 34 | 35 | exports.execute = function(db, script, callback){ 36 | console.log(script,"Convert all Category.image to be https complient"); 37 | var logs="", count=0; 38 | var catgories=db.collection('categories'); 39 | 40 | catgories.find( {}).toArray(function (err,cats) { 41 | if (!cats.length){ 42 | return callback(null, "0 product have been updated") 43 | } 44 | console.log(script,"migrating "+cats.length +" catgories"); 45 | 46 | // 47 | // convert url 48 | require('async').each(cats, function(category, eachcb){ 49 | // 50 | if(!category.cover){ 51 | console.log('WARNING no photo available for category ',category.name) 52 | return eachcb(); 53 | } 54 | var url=category.cover.split(aws.in); 55 | if(url.length>1){ 56 | category.cover=aws.out+url[1] 57 | } 58 | console.log('rename ',category.cover ) 59 | catgories.save(category,function(err){ 60 | eachcb(err); 61 | }); 62 | 63 | }, 64 | function(err){ 65 | return callback(err, cats.length+" photos on catgories have been updated"); 66 | }); 67 | }); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /maintain/0018.shop_photo_https_url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | var aws={ 31 | in:"//karibou-filepicker.s3-website-eu-west-1.amazonaws.com/", 32 | out:"//s3-eu-west-1.amazonaws.com/karibou-filepicker/" 33 | }; 34 | 35 | exports.execute = function(db, script, callback){ 36 | console.log(script,"Convert all Category.image to be https complient"); 37 | var logs="", count=0; 38 | var shops=db.collection('shops'); 39 | 40 | shops.find({}).toArray(function (err,lst) { 41 | if (!lst.length){ 42 | return callback(null, "0 product have been updated") 43 | } 44 | console.log(script,"migrating "+lst.length +" shops"); 45 | 46 | // 47 | // convert url 48 | require('async').each(lst, function(shop, eachcb){ 49 | // 50 | if(!shop.photo){ 51 | console.log('WARNING no photo available for shop ',shop.name) 52 | return eachcb(); 53 | } 54 | if(shop.photo.owner) var owner=shop.photo.owner.split(aws.in); 55 | if(shop.photo.bg) var bg=shop.photo.bg.split(aws.in); 56 | if(shop.photo.fg) var fg=shop.photo.fg.split(aws.in); 57 | 58 | if(owner&&owner.length>1){ shop.photo.owner=aws.out+owner[1]; } 59 | if(bg&&bg.length>1){ shop.photo.bg=aws.out+bg[1]; } 60 | if(fg&&fg.length>1){ shop.photo.fg=aws.out+fg[1]; } 61 | console.log('rename ',shop.photo ) 62 | shops.save(shop,function(err){ 63 | eachcb(err); 64 | }); 65 | 66 | }, 67 | function(err){ 68 | return callback(err, lst.length+" photos on shops have been updated"); 69 | }); 70 | }); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /maintain/0019.category_photo_to_uploadcare.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | * migrate to uploadcare 29 | * “Uploadcare allowed us to get photo upload and cropping 30 | into our app within hours. More importantly, they are super 31 | responsive with support. We've been running this in production 32 | for months and our users love it.” 33 | Jeff Friesen, CTO SnuggPro 34 | 35 | */ 36 | 37 | var http=require('http'); 38 | 39 | 40 | exports.execute = function(db, script, callback){ 41 | console.log(script,"Convert all Category.image to uploadcare"); 42 | var logs="", count=0; 43 | var catgories=db.collection('categories'); 44 | var uploadcare=require('./includes/uploadcare')(config.auth.uploadcare.pub, config.auth.uploadcare.pk) 45 | 46 | // 47 | // 48 | // go and upload images 49 | catgories.find( {}).toArray(function (err,cats) { 50 | if (!cats.length){ 51 | return callback(null, "0 product have been updated") 52 | } 53 | console.log(script,"migrating "+cats.length +" catgories"); 54 | 55 | // 56 | // convert url 57 | require('async').each(cats, function(category, eachcb){ 58 | // 59 | if(!category.cover){ 60 | console.log('WARNING no photo available for category ',category.name) 61 | return eachcb(); 62 | } 63 | 64 | 65 | uploadcare.file.fromUrl(category.cover, function(err,res){ 66 | //Res should contain returned file ID 67 | // 'https://ucarecdn.com/'+res.token 68 | if(err){ 69 | return eachcb(err) 70 | } 71 | category.cover='//ucarecdn.com/'+res.uuid+'/'; 72 | catgories.save(category,function(err){ 73 | console.log('uploadcare ',category.cover ) 74 | eachcb(err); 75 | }); 76 | }) 77 | 78 | 79 | }, 80 | function(err){ 81 | return callback(err, cats.length+" photos on catgories have been updated"); 82 | }); 83 | }); 84 | 85 | 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /maintain/0020.shop_photo_https_url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Convert all shops images to uploadcare"); 33 | var logs="", count=0; 34 | var shops=db.collection('shops'); 35 | var uploadcare=require('./includes/uploadcare')(config.auth.uploadcare.pub, config.auth.uploadcare.pk) 36 | 37 | shops.find({}).toArray(function (err,lst) { 38 | if (!lst.length){ 39 | return callback(null, "0 product have been updated") 40 | } 41 | console.log(script,"migrating "+lst.length +" shops"); 42 | 43 | // 44 | // convert url 45 | require('async').each(lst, function(shop, eachcb){ 46 | // 47 | if(!shop.photo){ 48 | console.log('WARNING no photo available for shop ',shop.name) 49 | return eachcb(); 50 | } 51 | var series=[]; 52 | 53 | if(shop.photo.owner){ 54 | series.push(function (seriescb) { 55 | console.log('upload ow',shop.photo.owner) 56 | uploadcare.file.fromUrl(shop.photo.owner, function(err,res){ 57 | if(err){return seriescb(err)} 58 | shop.photo.owner='//ucarecdn.com/'+res.uuid+'/'; 59 | seriescb(); 60 | }); 61 | }); 62 | } 63 | 64 | if(shop.photo.bg){ 65 | series.push(function (seriescb) { 66 | console.log('upload bg',shop.photo.bg) 67 | uploadcare.file.fromUrl(shop.photo.bg, function(err,res){ 68 | if(err){return seriescb(err)} 69 | shop.photo.bg='//ucarecdn.com/'+res.uuid+'/'; 70 | seriescb(); 71 | }); 72 | }); 73 | } 74 | 75 | if(shop.photo.fg){ 76 | series.push(function (seriescb) { 77 | console.log('upload fg',shop.photo.fg) 78 | uploadcare.file.fromUrl(shop.photo.fg, function(err,res){ 79 | if(err){return seriescb(err)} 80 | shop.photo.fg='//ucarecdn.com/'+res.uuid+'/'; 81 | seriescb(); 82 | }); 83 | }); 84 | 85 | } 86 | require('async').series(series, 87 | function(err, results){ 88 | if(err){return eachcb(err);} 89 | shops.save(shop,function(err){ 90 | console.log('renamed ',shop.photo ) 91 | eachcb(err); 92 | }); 93 | }); 94 | 95 | 96 | }, 97 | function(err){ 98 | return callback(err, lst.length+" photos on shops have been updated"); 99 | }); 100 | }); 101 | 102 | } 103 | -------------------------------------------------------------------------------- /maintain/0021.product_photo_to_uploadcare.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Convert all products.photo.url to uploadcare"); 32 | var logs="", count=0; 33 | var products=db.collection('products'); 34 | var uploadcare=require('./includes/uploadcare')(config.auth.uploadcare.pub, config.auth.uploadcare.pk) 35 | 36 | products.find( {}).toArray(function (err,p) { 37 | if (!p.length){ 38 | return callback(null, "0 product have been updated") 39 | } 40 | console.log(script,"migrating "+p.length +" products"); 41 | 42 | // 43 | // convert url 44 | require('async').each(p, function(product, eachcb){ 45 | // 46 | if(!product.photo||!product.photo.url){ 47 | console.log('WARNING no photo available for product',product.sku) 48 | return eachcb(); 49 | } 50 | if(product.photo.url){ 51 | uploadcare.file.fromUrl(product.photo.url, function(err,res){ 52 | if(err){return eachcb(err)} 53 | product.photo.url='//ucarecdn.com/'+res.uuid+'/'; 54 | 55 | products.save(product,function(err){ 56 | console.log('renamed',product.photo.url ) 57 | eachcb(err); 58 | }); 59 | 60 | }); 61 | } 62 | 63 | }, 64 | function(err){ 65 | return callback(err, p.length+" photos on products have been updated"); 66 | }); 67 | }); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /maintain/0022.shop_available_weekdays.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Make shop.available.weekdays ready"); 32 | var logs="", count=0; 33 | var shops=db.collection('shops'); 34 | 35 | shops.find({}).toArray(function (err,s) { 36 | if (!s.length){ 37 | return callback(null, "0 shop have been updated") 38 | } 39 | console.log(script,"migrating "+s.length +" shops"); 40 | shops.update({}, { $set: { "available.weekdays": [2,5] } },{ multi: true } ,function(err,res){ 41 | callback(err, s.length+" shops have been updated"); 42 | }) 43 | 44 | }); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /maintain/0023.shop_account_bm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Make shop.account business model ready"); 32 | var logs="", count=0; 33 | var shops=db.collection('shops'); 34 | 35 | shops.update({}, { $set: { "account": {fees:.15,updated:Date.now()} } },{ multi: true } ,function(err,count){ 36 | callback(err, count+" shops have been updated"); 37 | }) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /maintain/0024.orders_add_vendor_bm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Add order.vendors business model default value of 15%"); 32 | var logs="", count=0; 33 | var orders=db.collection('orders'); 34 | 35 | orders.find({}).toArray(function (err,os) { 36 | if (!os.length){ 37 | return callback(null, "0 order have been updated") 38 | } 39 | console.log(script,"migrating "+os.length +" orders"); 40 | require('async').each(os, function(order, eachcb){ 41 | 42 | // 43 | // update BM on for each vendors 44 | order.vendors.forEach(function (vendor) { 45 | vendor.fees=0.15; 46 | }) 47 | 48 | orders.save(order,function(err){ 49 | eachcb(err); 50 | }); 51 | 52 | }, 53 | function(err){ 54 | return callback(err, os.length+" orders have been updated"); 55 | }); 56 | }); 57 | 58 | // this is not possible, waiting for $$ operator 59 | // see here https://jira.mongodb.org/browse/SERVER-1243 60 | // orders.update({'vendors.fees':{$ne:true}} , {$set: {"vendors.$$.fees": 0.15}},{multi:true}, function(err,count){ 61 | // callback(err, count+" orders have been updated"); 62 | // }) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /maintain/0025.orders_add_shipping_fees.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Store the shipping cost in order payment"); 32 | var logs="", count=0; 33 | var orders=db.collection('orders'); 34 | 35 | // this is not possible, waiting for $$ operator 36 | // see here https://jira.mongodb.org/browse/SERVER-1243 37 | orders.update({'payment.fees':{$ne:true}} , {$set: {"payment.fees": {shipping:10}}},{multi:true}, function(err,count){ 38 | callback(err, count+" orders have been updated"); 39 | }) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /maintain/0026.orders_add_payment_provider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all shop.options and rename it to shop.details 7 | * 8 | * Use case 9 | * 1) How to change the type of a field? 10 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 11 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 12 | * x.bad = new String(x.bad); // convert field to string 13 | * db.foo.save(x); 14 | * }); 15 | * 16 | * 2) How to rename a field 17 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 18 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 19 | * 20 | * $type: 21 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 22 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 23 | * Null 10, Regular Expression 11, JavaScript 13, 24 | * Symbol 14, JavaScript (with scope) 15, 25 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 26 | * 27 | */ 28 | 29 | 30 | exports.execute = function(db, script, callback){ 31 | console.log(script,"Store the payment provider in orders"); 32 | var logs="", count=0; 33 | var orders=db.collection('orders'); 34 | 35 | // this is not possible, waiting for $$ operator 36 | // see here https://jira.mongodb.org/browse/SERVER-1243 37 | orders.update({'payment.provider':{$ne:true}} , {$set: {"payment.provider": 'striper'}},{multi:true}, function(err,count){ 38 | callback(err, count+" orders have been updated"); 39 | }) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /maintain/0027.product_slug_title.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | * find all product where photo is a string 7 | * - convert the field photo:string => photo:{url:string} 8 | * 9 | * Use case 10 | * 1) How to change the type of a field? 11 | * see type here http://docs.mongodb.org/manual/reference/operator/type/#op._S_type 12 | * db.foo.find( { 'bad' : { $type : 1 } } ).forEach( function (x) { 13 | * x.bad = new String(x.bad); // convert field to string 14 | * db.foo.save(x); 15 | * }); 16 | * 17 | * 2) How to rename a field 18 | * db.students.update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } ) 19 | * db.students.update( { _id: 1 }, { $rename: { "name.last": "contact.lname" } } ) 20 | * 21 | * $type: 22 | * Double 1, String 2, Object 3, Array 4, Binary data 5, 23 | * Undefined (deprecated) 6, Object id 7, Boolean 8, Date 9, 24 | * Null 10, Regular Expression 11, JavaScript 13, 25 | * Symbol 14, JavaScript (with scope) 15, 26 | * 32-bit integer 16, Timestamp 17, 64-bit integer 18, Min key 255, Max key 127 27 | * 28 | */ 29 | 30 | 31 | exports.execute = function(db, script, callback){ 32 | console.log(script,"Create product.slug from products.title "); 33 | var logs="", count=0; 34 | var products=db.collection('products'); 35 | 36 | products.find( {slug:{$exists:false}}).toArray(function (err,p) { 37 | if (!p.length){ 38 | return callback(null, "0 product have been updated") 39 | } 40 | console.log(script,"migrating "+p.length +" products"); 41 | 42 | // 43 | // convert url 44 | require('async').each(p, function(product, eachcb){ 45 | // 46 | if(product.slug){ 47 | console.log('WARNING slug is available for this product',product.sku) 48 | return eachcb(); 49 | } 50 | product.slug=product.title.slug(); 51 | console.log('slug:',product.sku,product.slug ) 52 | products.save(product,function(err){ 53 | eachcb(err); 54 | }); 55 | 56 | }, 57 | function(err){ 58 | return callback(err, p.length+" slug on products have been updated"); 59 | }); 60 | }); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /maintain/0028.default_activity_for_products.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * 4 | */ 5 | 6 | 7 | exports.execute = function(db, script, callback){ 8 | console.log(script,"Init activity for all products"); 9 | var logs="", count=0; 10 | var products=db.collection('products'), activity=db.collection('activities'); 11 | 12 | products.find( {}).toArray(function (err,p) { 13 | if (!p.length){ 14 | return callback(null, "0 product have been updated") 15 | } 16 | console.log(script,"activities for "+p.length +" products"); 17 | require('async').each(p, function(product, eachcb){ 18 | 19 | var doc={ 20 | who:{id:1,email:'evaleto@gmail.com',name:'system'}, 21 | what:{type:'Products',key:'sku',id:product.sku+'',action:'create'}, 22 | content:{pricing:product.pricing,title:product.title}, 23 | when:new Date() 24 | }; 25 | activity.save(doc,function(err){ 26 | if(err){ 27 | console.log('ERROR',product.sku,err) 28 | } 29 | eachcb(err); 30 | }); 31 | 32 | }, 33 | function(err){ 34 | return callback(err, p.length+" activities have been created"); 35 | }); 36 | }); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /maintain/0030.i18n-documents.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | exports.execute = function(db, script, callback){ 4 | console.log(script,"Convert all documents for i18n"); 5 | var logs="", count=0; 6 | var documents=db.collection('documents'); 7 | 8 | documents.find( {}).toArray(function (err,p) { 9 | if (!p.length){ 10 | return callback(null, "0 doc have been updated") 11 | } 12 | console.log(script,"migrating "+p.length +" documents"); 13 | require('async').each(p, function(doc, eachcb){ 14 | 15 | // 16 | // convert title 17 | if(!doc.title.fr)doc.title={fr:doc.title}; 18 | 19 | // 20 | // convert content 21 | if(!doc.content.fr)doc.content={fr:doc.content}; 22 | 23 | // 24 | // convert header 25 | if(!doc.header.fr)doc.header={fr:doc.header}; 26 | 27 | // 28 | // convert slug 29 | if(!Array.isArray(doc.slug)&&doc.slug)doc.slug=[doc.slug]; 30 | 31 | documents.save(doc,function(err){ 32 | console.log(err, doc.slug) 33 | eachcb(err); 34 | }); 35 | }, 36 | function(err){ 37 | return callback(err, p.length+" documents have been updated"); 38 | }); 39 | }); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /maintain/0031.i18n-config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | exports.execute = function(db, script, callback){ 4 | console.log(script,"Convert all configs for i18n"); 5 | var logs="", count=0; 6 | var configs=db.collection('configs'); 7 | 8 | configs.find( {}).toArray(function (err,p) { 9 | if (!p.length){ 10 | return callback(null, "0 conf have been updated") 11 | } 12 | console.log(script,"migrating "+p.length +" configs"); 13 | require('async').each(p, function(conf, eachcb){ 14 | 15 | // 16 | // convert maintenance.reason 17 | if(!conf.maintenance.reason||!conf.maintenance.reason.fr){ 18 | conf.maintenance.reason={fr:conf.maintenance.reason}; 19 | } 20 | 21 | // 22 | // convert noshipping 23 | for (var i = conf.noshipping.length - 1; i >= 0; i--) { 24 | if(conf.noshipping[i].reason&&!conf.noshipping[i].reason.fr){ 25 | conf.noshipping[i].reason={fr:conf.noshipping[i].reason}; 26 | } 27 | }; 28 | 29 | if(!conf.menu){ 30 | conf.menu=[]; 31 | } 32 | 33 | if(!conf.home){ 34 | conf.home={}; 35 | } 36 | 37 | conf.home.views=[]; 38 | conf.home.siteName={}; 39 | conf.home.tagLine={h:{},p:{}}; 40 | conf.home.about={h:{},p:{}}; 41 | conf.home.footer={h:{},p:{}}; 42 | 43 | 44 | configs.save(conf,function(err){ 45 | console.log(err, conf.slug) 46 | eachcb(err); 47 | }); 48 | }, 49 | function(err){ 50 | return callback(err, p.length+" configs have been updated"); 51 | }); 52 | }); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /maintain/0032.orders_fees_charge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Maintain mongo database 3 | * http://docs.mongodb.org/manual/reference/operator/#AdvancedQueries-%24type 4 | * 5 | * 6 | */ 7 | 8 | 9 | exports.execute = function(db, script, callback){ 10 | console.log(script,"Add order.payment.fees.charge default value of 2.9%"); 11 | var logs="", count=0; 12 | var orders=db.collection('orders'); 13 | 14 | orders.update({'payment.fees.charge':{$exists:false}}, {$set: {'payment.fees.charge': 0.029}}, { multi: true }); 15 | 16 | return callback(0,"orders have been updated"); 17 | } 18 | -------------------------------------------------------------------------------- /models/activities.js: -------------------------------------------------------------------------------- 1 | 2 | var debug = require('debug')('activities') 3 | , bus = require('../app/bus') 4 | , Q = require('q') 5 | , assert = require("assert") 6 | , _ =require('underscore') 7 | , mongoose= require('mongoose') 8 | , Schema = mongoose.Schema 9 | , ObjectId= Schema.Types.ObjectId; 10 | 11 | var EnumAction="create update delete error".split(' '); 12 | 13 | 14 | var Activity = new Schema({ 15 | who:{ 16 | id:{ type: Number, required: true }, 17 | name:{ type: String, required: true }, 18 | email:{ type: String, required: true } 19 | }, 20 | 21 | what:{ 22 | type:{ type: String, required: true }, 23 | key:{ type: String, required: true }, 24 | id:{ type: String, required: true }, 25 | action:{ type: String, required: true, enum:EnumAction } 26 | }, 27 | 28 | content:{type:Schema.Types.Mixed, required:true}, 29 | 30 | when:{ type: Date, default: Date.now }, 31 | 32 | }); 33 | 34 | 35 | 36 | // 37 | // create a new activities 'p' for the shop 's' 38 | Activity.statics.create = function(who,what,content,callback){ 39 | assert(who); 40 | assert(what); 41 | assert(content); 42 | assert(callback); 43 | var Activities=this, doc={what:what,content:content}; 44 | 45 | // 46 | // set user info 47 | doc.who={id:who.id,email:who.email.address,name:who.name.familyName}; 48 | 49 | 50 | // 51 | // ready to create one product 52 | var myActivity =new Activities(doc); 53 | 54 | return myActivity.save(callback); 55 | }; 56 | 57 | 58 | Activity.statics.findByCrireria = function(criteria, callback){ 59 | var query={}, from=new Date(),to; 60 | 61 | 62 | if(criteria.type){ 63 | query['what.type']=criteria.type; 64 | } 65 | 66 | if(criteria.month){ 67 | from.setDate(1) 68 | from.setMonth(parseInt(criteria.month)-1) 69 | from.setHours(0,0,1,0) 70 | to=new Date(from); 71 | to.setDate(from.daysInMonth()) 72 | to.setHours(23,59,59,0) 73 | query['when']={"$gte": from, "$lte": to}; 74 | } 75 | 76 | if(criteria.when){ 77 | from=new Date(criteria.when); 78 | from.setHours(0,0,1,0); 79 | to=new Date(from); 80 | to.setHours(23,59,59,0) 81 | query['when']={"$gte": from, "$lte": to}; 82 | } 83 | 84 | // 85 | // findByUser 86 | if(criteria.uid){ 87 | query['who.id']=criteria.uid; 88 | } 89 | if(criteria.email){ 90 | query['who.email']=new RegExp('^.*'+criteria.email+'.*$', "i"); 91 | } 92 | 93 | // 94 | // findBy Content 95 | if(criteria.what){ 96 | query['what.type']=criteria.what; 97 | } 98 | 99 | 100 | if(callback) return this.find(query).exec(callback); 101 | return this.find(query); 102 | }; 103 | 104 | 105 | 106 | Activity.set('autoIndex', config.mongo.ensureIndex); 107 | exports.Activities = mongoose.model('Activities', Activity); 108 | 109 | 110 | -------------------------------------------------------------------------------- /models/db.maintain.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | , Schema = mongoose.Schema 3 | , validate = require('mongoose-validate') 4 | , ObjectId = Schema.ObjectId; 5 | 6 | 7 | var DbMaintain = new Schema({ 8 | version: { type: Number, required: false, unique:true}, 9 | log: { type: String, required: false}, 10 | date: {type:Date, default: Date.now} 11 | }); 12 | 13 | 14 | // a new DbMaintain is only created if there is not already an existing entry 15 | DbMaintain.statics.save = function(maintain, callback){ 16 | var Maintain = this.model('DbMaintain'); 17 | var dbm = Maintain(maintain); 18 | dbm.save(function (err, doc){ 19 | return callback(err,doc); 20 | }); 21 | }; 22 | 23 | 24 | DbMaintain.statics.findAll = function(callback){ 25 | this.model('DbMaintain').find('{}', function(err, maintain){ 26 | return callback(err, maintain); 27 | }); 28 | }; 29 | 30 | 31 | DbMaintain.statics.findLatestVersion = function(callback){ 32 | var Maintain=this.model('DbMaintain'); 33 | Maintain.find('{}', 'version', {limit: 1, sort:{_id:-1}}, function(err, versionColl){ 34 | var version = (versionColl[0])?(versionColl[0].version):(0); 35 | return callback(err, version); 36 | }); 37 | }; 38 | 39 | DbMaintain.set('autoIndex', config.mongo.ensureIndex); 40 | module.exports = mongoose.model('DbMaintain', DbMaintain); 41 | -------------------------------------------------------------------------------- /models/sequences.js: -------------------------------------------------------------------------------- 1 | 2 | var debug = require('debug')('sequences'); 3 | 4 | var mongoose = require('mongoose') 5 | , Schema = mongoose.Schema 6 | , ObjectId = Schema.ObjectId; 7 | 8 | 9 | // 10 | // wrap a request to a simple queuing system. 11 | // This should help to avoid race condition on product 12 | var queue=require('../app/queue')(1,true); 13 | var queued=function(f){ 14 | return function(req,res){ 15 | queue.defer(f,req,res) 16 | } 17 | } 18 | 19 | 20 | 21 | var Sequences = new Schema({ 22 | name:{type:String, unique:true}, 23 | seq:{type:Number,min:100000, default:1000000} 24 | }); 25 | 26 | // 27 | Sequences.statics.initNumber = function(name,value){ 28 | var init_Sequences={ 29 | sku:1000000, 30 | oid:2000000, 31 | uid:8000000 32 | } 33 | if(!init_Sequences[name]){ 34 | init_Sequences[name]=value; 35 | } 36 | 37 | return init_Sequences[name]; 38 | }; 39 | 40 | 41 | // 42 | // SEQUENCES API 43 | 44 | 45 | Sequences.statics.next = function(name, start, callback){ 46 | var promise = new mongoose.Promise; 47 | var Sequences=this.model('Sequences'); 48 | var newSeq; 49 | if(typeof start === 'function'){ 50 | callback=start; 51 | start=this.initNumber(name,10000000); 52 | } 53 | 54 | // 55 | // attach callback to promise 56 | if(callback){ 57 | promise.addBack(callback); 58 | } 59 | 60 | // FIXME race condition here : ,{'$setOnInsert':{name:name,seq:start},{upsert:false} 61 | Sequences.findOneAndUpdate({name:name},{$inc: {seq:1}}, {new:true }, function(err,counter){ 62 | if(err){ 63 | return promise.reject(err); 64 | } 65 | if(counter){ 66 | return promise.resolve(null,counter.seq); 67 | // return callback(err,counter.seq); 68 | } 69 | new Sequences({name:name,seq:start}).save(function(err,n){ 70 | return promise.resolve(null,n.seq); 71 | // callback(err,n.seq); 72 | }); 73 | }); 74 | return promise; 75 | }; 76 | 77 | 78 | 79 | // simple wrapper for SKU 80 | Sequences.statics.nextSku = function( callback){ 81 | return this.model('Sequences').next("sku",this.initNumber('sku'),callback); 82 | }; 83 | 84 | // simple wrapper for Order ID 85 | Sequences.statics.nextOrder = function( callback){ 86 | return this.model('Sequences').next("oid",this.initNumber('oid'),callback); 87 | }; 88 | 89 | // simple wrapper for Order ID 90 | Sequences.statics.nextUser = function( callback){ 91 | return this.model('Sequences').next("uid",this.initNumber('uid'),callback); 92 | }; 93 | 94 | Sequences.set('autoIndex', config.mongo.ensureIndex); 95 | module.exports =mongoose.model('Sequences', Sequences); 96 | 97 | 98 | -------------------------------------------------------------------------------- /newrelic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * New Relic agent configuration. 3 | * 4 | * See lib/config.defaults.js in the agent distribution for a more complete 5 | * description of configuration variables and their potential values. 6 | */ 7 | exports.config = { 8 | /** 9 | * Array of application names. 10 | */ 11 | app_name : ['karibou-api'], 12 | /** 13 | * Your New Relic license key. 14 | */ 15 | license_key : process.env.MEWRELIC_KEY, 16 | logging : { 17 | /** 18 | * Level at which to log. 'trace' is most useful to New Relic when diagnosing 19 | * issues with the agent, 'info' and higher will impose the least overhead on 20 | * production applications. 21 | */ 22 | level : 'trace' 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "karibou-api", 3 | "description": "Karibou.ch is an opensource project aim to help the creation of an online community marketplace for food distribution", 4 | "version": "1.2.0", 5 | "private": false, 6 | "engines": { 7 | "node": "4.2.x", 8 | "npm": "3.5.x" 9 | }, 10 | "dependencies": { 11 | "appdynamics": "^4.2.3", 12 | "async": "2.x", 13 | "body-parser": "^1.14.1", 14 | "compression": "^1.6.0", 15 | "connect-mongo": "1.x", 16 | "cookie-parser": "1.x", 17 | "cron": "^1.1.0", 18 | "debug": "2.x", 19 | "ejs": "2.x", 20 | "email-templates": "2.x", 21 | "errorhandler": "^1.4.2", 22 | "express": "4.x", 23 | "express-csv": "^0.6.0", 24 | "express-session": "1.x", 25 | "fnv-plus": "1.x", 26 | "helmet": "3.x", 27 | "i18n-2": "0.x", 28 | "jade": "1.x", 29 | "karibou-wallet": "0.x", 30 | "lru-cache": "^4.0.0", 31 | "mailchimp": "1.x", 32 | "method-override": "2.x", 33 | "moment": "2.x", 34 | "mongodb": "2.x", 35 | "mongoose": "4.x", 36 | "mongoose-error-helper": "0.x", 37 | "mongoose-validate": "x", 38 | "morgan": "1.x", 39 | "newrelic": "1.x", 40 | "node-postfinance": "0.x", 41 | "nodemailer": "1.x", 42 | "passport": "0.x", 43 | "passport-google-oauth": "1.x", 44 | "passport-local": "1.x", 45 | "passport-twitter": "1.x", 46 | "password-generator": "2.x", 47 | "q": "1.4.x", 48 | "queue-async": "1.x", 49 | "remarkable": "1.x", 50 | "serve-favicon": "2.x", 51 | "sitemap": "1.x", 52 | "stripe": "3.7.x", 53 | "underscore": "1.x", 54 | "validator": "3.x" 55 | }, 56 | "devDependencies": { 57 | "mocha": "3.x", 58 | "pow-mongoose-fixtures": "x", 59 | "should": "6.x", 60 | "should-http": "0.x", 61 | "supertest": "2.x" 62 | }, 63 | "scripts": { 64 | "start": "node app.js", 65 | "test": "./node_modules/.bin/mocha test", 66 | "maintain": "node maintain.js" 67 | }, 68 | "main": "app.js", 69 | "subdomain": "karibou-api" 70 | } 71 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 20px; 4 | padding-bottom: 20px; 5 | font-size: 18px; 6 | } 7 | 8 | /* Everything but the jumbotron gets side spacing for mobile first views */ 9 | .header, 10 | .marketing, 11 | .footer { 12 | padding-right: 15px; 13 | padding-left: 15px; 14 | } 15 | 16 | /* Custom page header */ 17 | .header { 18 | padding-bottom: 20px; 19 | border-bottom: 1px solid #e5e5e5; 20 | } 21 | /* Make the masthead heading the same height as the navigation */ 22 | .header h3 { 23 | margin-top: 0; 24 | margin-bottom: 0; 25 | line-height: 40px; 26 | } 27 | 28 | /* Custom page footer */ 29 | .footer { 30 | padding-top: 19px; 31 | color: #777; 32 | border-top: 1px solid #e5e5e5; 33 | } 34 | 35 | .list-group-item{ 36 | padding: 20px 15px; 37 | } 38 | 39 | /* Customize container */ 40 | @media (min-width: 768px) { 41 | .container { 42 | max-width: 730px; 43 | } 44 | 45 | .list-group-item{ 46 | padding: 10px 15px; 47 | } 48 | } 49 | .container-narrow > hr { 50 | margin: 30px 0; 51 | } 52 | 53 | /* Main marketing message and sign up button */ 54 | .jumbotron { 55 | text-align: center; 56 | border-bottom: 1px solid #e5e5e5; 57 | } 58 | .jumbotron .btn { 59 | padding: 14px 24px; 60 | font-size: 21px; 61 | } 62 | 63 | /* Supporting marketing content */ 64 | .marketing { 65 | margin: 40px 0; 66 | } 67 | .marketing p + h4 { 68 | margin-top: 28px; 69 | } 70 | 71 | /* Responsive: Portrait tablets and up */ 72 | @media screen and (min-width: 768px) { 73 | /* Remove the padding we set earlier */ 74 | .header, 75 | .marketing, 76 | .footer { 77 | padding-right: 0; 78 | padding-left: 0; 79 | } 80 | /* Space out the masthead */ 81 | .header { 82 | margin-bottom: 30px; 83 | } 84 | /* Remove the bottom border on the jumbotron for visual effect */ 85 | .jumbotron { 86 | border-bottom: 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /public/img/anais.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evaletolab/karibou-api/c02cef6e3f75123a188e197e20c3e4ae5c1c0c4c/public/img/anais.jpg -------------------------------------------------------------------------------- /public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evaletolab/karibou-api/c02cef6e3f75123a188e197e20c3e4ae5c1c0c4c/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evaletolab/karibou-api/c02cef6e3f75123a188e197e20c3e4ae5c1c0c4c/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/img/hello-home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evaletolab/karibou-api/c02cef6e3f75123a188e197e20c3e4ae5c1c0c4c/public/img/hello-home.jpg -------------------------------------------------------------------------------- /public/img/karibou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evaletolab/karibou-api/c02cef6e3f75123a188e197e20c3e4ae5c1c0c4c/public/img/karibou.jpg -------------------------------------------------------------------------------- /script/node-continuous.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | #read params: branch port 5 | [ -z "$2" ] && PORT=3000 || PORT=$2 6 | [ -z "$1" ] && { 7 | echo "usage:$0 " 8 | exit 1 9 | } 10 | # 11 | # check root directory 12 | [ -f app.js ] || { 13 | echo "wrong root directory" 14 | exit 1 15 | } 16 | sleep 2; 17 | echo "#git pull origin $1" 18 | git pull origin $1 19 | npm install 20 | 21 | 22 | echo "#restart server $1" 23 | #nohup bash -c 'sleep 1;node app >> $HOME/www/logs/node-kariboo.logs'& 24 | #fuser -k $PORT/tcp; 25 | #forever start --watchIgnore "*newrelic*" --spinSleepTime 10000 -f -w -o $HOME/www/logs/node-kariboo.logs app -------------------------------------------------------------------------------- /test/api.category.find.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js",'Products.js']); 8 | 9 | 10 | describe("api.categories", function(){ 11 | var request= require('supertest'); 12 | var _=require('underscore'); 13 | 14 | var cookie; 15 | 16 | before(function(done){ 17 | dbtools.clean(function(e){ 18 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Products.js"],db,function(err){ 19 | should.not.exist(err); 20 | done(); 21 | }); 22 | }); 23 | }); 24 | 25 | 26 | after(function(done){ 27 | dbtools.clean(function(){ 28 | done(); 29 | }); 30 | }); 31 | 32 | 33 | 34 | 35 | it('GET /v1/category?stats=true should return category usedBy ',function(done){ 36 | request(app) 37 | .get('/v1/category?stats=true') 38 | .end(function(err, res){ 39 | res.should.have.status(200); 40 | res.body.forEach(function(c){ 41 | should.exist(c.usedBy) 42 | }) 43 | done(); 44 | }); 45 | }); 46 | }); 47 | 48 | -------------------------------------------------------------------------------- /test/api.orders.report.js: -------------------------------------------------------------------------------- 1 | var app = require("../app"); 2 | 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var _ = require("underscore"); 8 | var request= require('supertest'); 9 | var data = dbtools.fixtures(["Users.js","Categories.js","Orders.find.js"]), 10 | Orders=db.model('Orders'); 11 | 12 | describe("api.orders.find", function(){ 13 | before(function(done){ 14 | 15 | dbtools.clean(function(e){ 16 | dbtools.load(["../fixtures/Users.js","../fixtures/Orders.report.js"],db,function(err){ 17 | should.not.exist(err); 18 | 19 | // Orders.printInfo() 20 | // Orders.find({}).exec(function(e,os){ 21 | // os.forEach(function(o){o.print()}) 22 | // }) 23 | 24 | done(); 25 | }); 26 | }); 27 | }); 28 | 29 | 30 | after(function(done){ 31 | dbtools.clean(function(){ 32 | done(); 33 | }); 34 | }); 35 | 36 | // 37 | // keep session 38 | var cookie; 39 | 40 | it("login",function(done){ 41 | request(app) 42 | .post('/login') 43 | .send({ email: "evaleto@gmail.com", password:'password',provider:'local' }) 44 | .end(function(e,res){ 45 | should.not.exist(e) 46 | cookie = res.headers['set-cookie']; 47 | done() 48 | }); 49 | }) 50 | 51 | it('GET /v1/orders should return 401 for anonymous',function(done){ 52 | request(app) 53 | .get('/v1/orders') 54 | .expect(401,done); 55 | }); 56 | 57 | 58 | it('GET /v1/orders/invoices/shops/12/2014 list all open orders for admin',function(done){ 59 | request(app) 60 | .get('/v1/orders/invoices/shops/12/2014') 61 | .set('cookie', cookie) 62 | .expect(200,function(err,res){ 63 | should.not.exist(err) 64 | //res.body.length.should.equal(3) 65 | done() 66 | }); 67 | }); 68 | 69 | 70 | it('GET /v1/orders/invoices/shops/12/2014?shops=crocorient,les-fromages-de-gaetan list all open orders for admin',function(done){ 71 | request(app) 72 | .get('/v1/orders/invoices/shops/12/2014?shops=crocorient,les-fromages-de-gaetan') 73 | .set('cookie', cookie) 74 | .expect(200,function(err,res){ 75 | should.not.exist(err) 76 | done() 77 | }); 78 | }); 79 | 80 | it('GET /v1/orders/invoices/shops/12/2014 list all open orders for admin',function(done){ 81 | request(app) 82 | .get('/v1/orders/invoices/shops/12/2014') 83 | .expect(401,function(err,res){ 84 | should.not.exist(err) 85 | //res.body.length.should.equal(3) 86 | done() 87 | }); 88 | }); 89 | 90 | 91 | 92 | 93 | }); 94 | 95 | -------------------------------------------------------------------------------- /test/api.products.find.sort.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js",'Products.more.js']); 8 | 9 | describe("DEPRECATED api.products.find.sort", function(){ 10 | var request= require('supertest'); 11 | 12 | var _=require('underscore'); 13 | 14 | 15 | before(function(done){ 16 | dbtools.clean(function(e){ 17 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js","../fixtures/Products.sort.js"],db,function(err){ 18 | should.not.exist(err); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | after(function(done){ 25 | dbtools.clean(function(){ 26 | done(); 27 | }); 28 | }); 29 | 30 | /** SORTING AND GROUPING */ 31 | it.skip("GET 200,/v1/products?sort=categories.weight", function(done){ 32 | request(app) 33 | .get("/v1/products?sort=categories.weight") 34 | .expect('Content-Type', /json/) 35 | .end(function(err, res){ 36 | res.should.have.status(200); 37 | var w=-1; 38 | res.body[0].vendor.should.be.an.instanceOf(Object) 39 | res.body.forEach(function(p){ 40 | p.categories.weight.should.be.above(w) 41 | w=p.categories.weight; 42 | }); 43 | done(); 44 | }); 45 | }); 46 | 47 | it.skip("GET 200,/v1/products?sort=categories.name", function(done){ 48 | request(app) 49 | .get("/v1/products?sort=categories.name") 50 | .expect('Content-Type', /json/) 51 | .end(function(err, res){ 52 | res.should.have.status(200); 53 | n=''; 54 | res.body[0].vendor.should.be.an.instanceOf(Object) 55 | res.body.forEach(function(p){ 56 | p.categories.name.should.be.above(n) 57 | n=p.categories.name; 58 | }); 59 | done(); 60 | }); 61 | }); 62 | 63 | it.skip("GET 200,/v1/products?group=categories.name&sort=categories.name", function(done){ 64 | request(app) 65 | .get("/v1/products?group=categories.name&sort=categories.name") 66 | .expect('Content-Type', /json/) 67 | .end(function(err, res){ 68 | res.should.have.status(200); 69 | var n=''; 70 | Object.keys(res.body).forEach(function(k){ 71 | k.should.be.above(n); 72 | n=k; 73 | }); 74 | 75 | //console.dir(res.body) 76 | done(); 77 | }); 78 | }); 79 | 80 | }); 81 | 82 | -------------------------------------------------------------------------------- /test/api.products.status.find.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js",'Products.more.js']); 8 | 9 | describe("api.products.find.status", function(){ 10 | var request= require('supertest'); 11 | var _=require('underscore'); 12 | 13 | var admin; 14 | 15 | 16 | before(function(done){ 17 | dbtools.clean(function(e){ 18 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js","../fixtures/Products.more.js"],db,function(err){ 19 | should.not.exist(err); 20 | done(); 21 | }); 22 | }); 23 | }); 24 | 25 | after(function(done){ 26 | dbtools.clean(function(){ 27 | done(); 28 | }); 29 | }); 30 | 31 | // user (_id:12345, email:gluck) 32 | // 2products ->shop[0](un-autre-shop, id:0004, status:true, owner:gluck) 33 | // 1product ->shop[1](mon-shop, id:0005, status:false, owner:gmail) 34 | // 0product ->shop[2](invalid-shop, id:0006, status:Date , owner:gluck) 35 | it('user.admin /login return 200',function(done){ 36 | request(app) 37 | .post('/login') 38 | .send({ email: "evaleto@gmail.com", password:'password', provider:'local' }) 39 | .end(function(err,res){ 40 | res.should.have.status(200); 41 | admin = res.headers['set-cookie']; 42 | done(); 43 | }); 44 | }); 45 | 46 | it('Change user status to FALSE /v1/users/:id/status should return 200 for admin only',function(done){ 47 | request(app) 48 | .post('/v1/users/12345/status') 49 | .set('cookie', admin) 50 | .send({status:false}) 51 | .end(function(err,res){ 52 | res.should.have.status(200); 53 | res.body.status.should.equal(false) 54 | done(); 55 | }); 56 | }); 57 | 58 | it("GET 200,/v1/shops/un-autre-shop/products/what-ever-filters should return 0 product", function(done){ 59 | request(app) 60 | .get("/v1/shops/un-autre-shop/products/category/"+data.Categories[3].slug+"/details/bio+ogm+gluten") 61 | .end(function(err, res){ 62 | res.should.have.status(200); 63 | // user gluck status=false => shop.status=false => products(gluck).size=0 64 | res.body.length.should.equal(0) 65 | done(); 66 | }); 67 | }); 68 | 69 | it("GET 200,/v1/shops/un-autre-shop/products should return 0 product", function(done){ 70 | request(app) 71 | .get("/v1/shops/un-autre-shop/products") 72 | .end(function(err, res){ 73 | //console.log(res.body) 74 | res.should.have.status(200); 75 | // user gluck status=false => shop.status=false => products(gluck).size=0 76 | res.body.length.should.equal(0) 77 | done(); 78 | }); 79 | }); 80 | 81 | 82 | it('users.post status FALSE /v1/users/:id/status should return 200 ',function(done){ 83 | request(app) 84 | .post('/v1/users/12345/status') 85 | .set('cookie', admin) 86 | .send({status:true}) 87 | .end(function(err,res){ 88 | res.should.have.status(200); 89 | res.body.status.should.equal(true) 90 | done(); 91 | }); 92 | }); 93 | 94 | 95 | }); 96 | 97 | -------------------------------------------------------------------------------- /test/api.seo.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | 8 | 9 | describe("api.categories", function(){ 10 | var request= require('supertest'); 11 | 12 | 13 | before(function(done){ 14 | 15 | dbtools.clean(function(e){ 16 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js","../fixtures/Products.more.js"],db,function(err){ 17 | should.not.exist(err); 18 | 19 | // Orders.printInfo() 20 | // Orders.find({}).exec(function(e,os){ 21 | // os.forEach(function(o){o.print()}) 22 | // }) 23 | 24 | done(); 25 | }); 26 | }); 27 | }); 28 | 29 | 30 | after(function(done){ 31 | dbtools.clean(function(e){ 32 | done() 33 | }); 34 | }); 35 | 36 | 37 | 38 | it('GET /seo/products/category/poissons',function(done){ 39 | request(app) 40 | .get('/seo/products/category/poissons') 41 | .expect(200,done); 42 | 43 | }); 44 | 45 | it('GET /seo/products',function(done){ 46 | request(app) 47 | .get('/seo/products') 48 | .expect(200,done); 49 | }); 50 | 51 | 52 | it('GET /seo/',function(done){ 53 | request(app) 54 | .get('/seo/') 55 | .expect(200,done); 56 | }); 57 | 58 | }); 59 | 60 | -------------------------------------------------------------------------------- /test/api.shops.admin.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js"]); 8 | 9 | 10 | 11 | describe("api.shops", function(){ 12 | var request= require('supertest'); 13 | 14 | var _=require('underscore'); 15 | 16 | var cookie; 17 | 18 | 19 | before(function(done){ 20 | dbtools.clean(function(e){ 21 | dbtools.load(["../fixtures/Users.js","../fixtures/Shops.js"],db,function(err){ 22 | should.not.exist(err); 23 | done(); 24 | }); 25 | }); 26 | }); 27 | 28 | after(function(done){ 29 | dbtools.clean(function(){ 30 | done(); 31 | }); 32 | }); 33 | 34 | 35 | 36 | 37 | it('GET /v1/shops/this-shop-doesnt-exist should return 400',function(done){ 38 | request(app) 39 | .get('/v1/shops/this-shop-doesnt-exist') 40 | .expect(400,done); 41 | }); 42 | 43 | it('POST /v1/shops/un-autre-shop should return 401 (you are anonymous)',function(done){ 44 | request(app) 45 | .post('/v1/shops/un-autre-shop') 46 | .expect(401,done); 47 | }); 48 | 49 | 50 | it('POST /login should return 200 ',function(done){ 51 | request(app) 52 | .post('/login') 53 | .send({ email: "evaleto@gmail.com", password:'password', provider:'local' }) 54 | .end(function(err,res){ 55 | res.should.have.status(200); 56 | res.body.roles.should.containEql('admin'); 57 | cookie = res.headers['set-cookie']; 58 | done(); 59 | }); 60 | }); 61 | 62 | it('GET /v1/shops/un-autre-shop should return 200 (SHOW ACCOUNT FEE)',function(done){ 63 | request(app) 64 | .get('/v1/shops/un-autre-shop') 65 | .set('cookie', cookie) 66 | .end(function(err,res){ 67 | res.should.have.status(200); 68 | should.exist(res.body.account.fees) 69 | done(); 70 | }); 71 | }); 72 | 73 | it('GET /v1/shops/un-autre-shop should return 200 (HIDE ACCOUNT FEE)',function(done){ 74 | request(app) 75 | .get('/v1/shops/un-autre-shop') 76 | .end(function(err,res){ 77 | res.should.have.status(200); 78 | should.not.exist(res.body.account.fees) 79 | done(); 80 | }); 81 | }); 82 | 83 | it('POST /v1/shops/un-autre-shop should return 200 (you are admin)',function(done){ 84 | var s=data.Shops[0] 85 | request(app) 86 | .post('/v1/shops/un-autre-shop') 87 | .send(s) 88 | .set('cookie', cookie) 89 | .end(function(err,res){ 90 | res.should.have.status(200); 91 | should.exist(res.body.account.fees) 92 | done(); 93 | }); 94 | }); 95 | 96 | it.skip('POST /v1/shops/un-autre-shop should return 200 (you are owner)',function(done){ 97 | var s=data.Shops[0] 98 | request(app) 99 | .post('/v1/shops/un-autre-shop') 100 | .send(s) 101 | .set('cookie', cookie) 102 | .end(function(err,res){ 103 | res.should.have.status(200); 104 | should.not.exist(res.body.account.fees) 105 | done(); 106 | }); 107 | }); 108 | 109 | it('GET /v1/users/me should return 200',function(done){ 110 | request(app) 111 | .get('/v1/users/me') 112 | .set('cookie', cookie) 113 | .expect(200,done); 114 | 115 | }); 116 | 117 | }); 118 | 119 | -------------------------------------------------------------------------------- /test/api.users.likes.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js",'Products.js']); 8 | 9 | 10 | describe("api.users.likes", function(){ 11 | var request= require('supertest'); 12 | 13 | var _=require('underscore'); 14 | 15 | var cookie, user; 16 | 17 | before(function(done){ 18 | dbtools.clean(function(e){ 19 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js","../fixtures/Products.js"],db,function(err){ 20 | should.not.exist(err); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | 27 | after(function(done){ 28 | dbtools.clean(function(){ 29 | done(); 30 | }); 31 | }); 32 | 33 | 34 | 35 | 36 | it('POST /login should return 200 ',function(done){ 37 | request(app) 38 | .post('/login') 39 | .send({ email: "evaleto@gmail.com", password:'password', provider:'local' }) 40 | .end(function(err,res){ 41 | res.should.have.status(200); 42 | res.body.roles.should.containEql('admin'); 43 | cookie = res.headers['set-cookie']; 44 | user=res.body; 45 | done(); 46 | }); 47 | }); 48 | 49 | 50 | it('GET /v1/users/me should return 200',function(done){ 51 | request(app) 52 | .get('/v1/users/me') 53 | .set('cookie', cookie) 54 | .end(function(err,res){ 55 | res.should.have.status(200); 56 | res.body.id.should.equal(user.id) 57 | done() 58 | }); 59 | 60 | }); 61 | 62 | it('user like wrong product should return 400',function(done){ 63 | request(app) 64 | .post('/v1/users/'+user.id+'/like/01234567') 65 | .set('cookie', cookie) 66 | .end(function(err,res){ 67 | res.should.have.status(400); 68 | done() 69 | }); 70 | }); 71 | 72 | it('user like /v1/users/me/like/'+data.Products[0].sku+' should return 200',function(done){ 73 | request(app) 74 | .post('/v1/users/'+user.id+'/like/'+data.Products[0].sku) 75 | .set('cookie', cookie) 76 | .end(function(err,res){ 77 | res.should.have.status(200); 78 | res.body.likes.length.should.equal(1) 79 | res.body.likes[0].should.equal(data.Products[0].sku) 80 | done() 81 | }); 82 | }); 83 | 84 | it('GET /v1/users/me should return 1 like 200',function(done){ 85 | request(app) 86 | .get('/v1/users/me') 87 | .set('cookie', cookie) 88 | .end(function(err,res){ 89 | res.should.have.status(200); 90 | // console.log(res.body.likes) 91 | res.body.likes.length.should.equal(1) 92 | res.body.likes[0].should.equal(data.Products[0].sku) 93 | done() 94 | }); 95 | 96 | }); 97 | 98 | 99 | it('user like /v1/users/me/like/'+data.Products[0].sku+' should return 200',function(done){ 100 | request(app) 101 | .post('/v1/users/'+user.id+'/like/'+data.Products[0].sku) 102 | .set('cookie', cookie) 103 | .end(function(err,res){ 104 | res.should.have.status(200); 105 | res.body.likes.length.should.equal(0) 106 | done() 107 | }); 108 | }); 109 | 110 | 111 | 112 | }); 113 | 114 | -------------------------------------------------------------------------------- /test/api.users.payment.webhook.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js"]); 8 | 9 | //http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm 10 | describe("api.users.payment.postfinance.webhook", function(){ 11 | var request= require('supertest'); 12 | 13 | var _=require('underscore'); 14 | 15 | var cookie, user; 16 | 17 | var pspWebhook={ 18 | orderID: 'AS1423152577442', 19 | currency: 'CHF', 20 | amount: '1', 21 | PM: 'PostFinance Card', 22 | ACCEPTANCE: 'test123', 23 | STATUS: '5', 24 | CARDNO: '**-XXXX-81', 25 | ALIAS: '2091529620513003', 26 | ED: '0719', 27 | CN: 'test1 test1', 28 | TRXDATE: '02/05/15', 29 | PAYID: '39152967', 30 | NCERROR: '0', 31 | BRAND: 'PostFinance Card', 32 | IPCTY: 'CH', 33 | CCCTY: '99', 34 | ECI: '7', 35 | CVCCheck: 'NO', 36 | AAVCheck: 'NO', 37 | VC: '', 38 | AAVADDRESS: 'NO', 39 | AAVNAME: 'NO', 40 | AAVMAIL: 'NO', 41 | IP: '84.227.169.49', 42 | createAlias: 'true', 43 | user: '1279482741765243', 44 | SHASIGN: '5FF339B3F8EEFBB976C0249BD74FD156287BDA3D5A5E99FC1858EB880B1051EE' 45 | } 46 | 47 | //378282246310005 48 | 49 | 50 | before(function(done){ 51 | dbtools.clean(function(e){ 52 | dbtools.load(["../fixtures/Users.js"],db,function(err){ 53 | should.not.exist(err); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | 60 | after(function(done){ 61 | dbtools.clean(function(){ 62 | done(); 63 | }); 64 | }); 65 | 66 | 67 | 68 | 69 | 70 | 71 | it.skip('PSP webhook create payment for postfinance card',function(done){ 72 | request(app) 73 | .post('/v1/psp/qawsedr/webhook') 74 | .send(pspWebhook) 75 | .end(function(err,res){ 76 | res.should.have.status(200); 77 | db.model('Users').findOne({id: pspWebhook.user}, function(err,user){ 78 | user.payments[0].alias.should.equal(pspWebhook.ALIAS.crypt()) 79 | user.payments[0].type.should.equal(pspWebhook.BRAND.toLowerCase()) 80 | user.payments[0].name.should.equal(pspWebhook.CN) 81 | user.payments[0].number.should.equal(pspWebhook.CARDNO) 82 | user.payments[0].expiry.should.equal('7/2019') 83 | done() 84 | }) 85 | }); 86 | }); 87 | 88 | it.skip('second PSP webhook got error payment already exist',function(done){ 89 | request(app) 90 | .post('/v1/psp/qawsedr/webhook') 91 | .send(pspWebhook) 92 | .end(function(err,res){ 93 | res.should.have.status(200); 94 | db.model('Users').findOne({id: pspWebhook.user}, function(err,user){ 95 | user.payments.length.should.equal(1) 96 | done() 97 | }); 98 | }); 99 | }); 100 | 101 | }); 102 | -------------------------------------------------------------------------------- /test/api.users.phones.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js",'Products.js']); 8 | 9 | 10 | describe("api.users.phones", function(){ 11 | var request= require('supertest'); 12 | 13 | var _=require('underscore'); 14 | 15 | var cookie, user; 16 | 17 | before(function(done){ 18 | dbtools.clean(function(e){ 19 | dbtools.load(["../fixtures/Categories.js"],db,function(err){ 20 | should.not.exist(err); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | 27 | after(function(done){ 28 | dbtools.clean(function(){ 29 | done(); 30 | }); 31 | }); 32 | 33 | 34 | 35 | 36 | it('POST /register should return 200 ',function(done){ 37 | var r={ 38 | email:"reg1@test.com", 39 | firstname:"first", 40 | lastname:"last", 41 | password:"123456", 42 | confirm:"123456" 43 | }; 44 | 45 | request(app) 46 | .post('/register') 47 | .send(r) 48 | .end(function(err,res){ 49 | res.should.have.status(200); 50 | done(); 51 | }); 52 | }); 53 | 54 | 55 | 56 | 57 | it('POST /login return 200',function(done){ 58 | request(app) 59 | .post('/login') 60 | .send({ email:"reg1@test.com", provider:'local', password:'123456' }) 61 | .end(function(err,res){ 62 | res.should.have.status(200); 63 | res.body.email.address.should.equal("reg1@test.com"); 64 | should.not.exist(res.body.hash) 65 | should.not.exist(res.body.salt) 66 | cookie = res.headers['set-cookie']; 67 | user=res.body; 68 | //res.headers.location.should.equal('/'); 69 | done(); 70 | }); 71 | }); 72 | 73 | 74 | 75 | it('POST without phone /v1/users/ should return 400',function(done){ 76 | var u=_.extend({},user) 77 | u.phoneNumbers=[]; 78 | u.addresses=[] 79 | request(app) 80 | .post('/v1/users/'+user.id) 81 | .send(u) 82 | .set('cookie', cookie) 83 | .end(function(err,res){ 84 | res.should.have.status(400); 85 | res.text.should.containEql('au moins un téléphone') 86 | //console.log(res.body.addresses[0]) 87 | done() 88 | }); 89 | }); 90 | 91 | it('POST without phone for reminder /v1/users/ should return 200',function(done){ 92 | var u=_.extend({},user) 93 | u.addresses=[] 94 | u.phoneNumbers=[]; 95 | u.save_reminder=true; 96 | request(app) 97 | .post('/v1/users/'+user.id) 98 | .send(u) 99 | .set('cookie', cookie) 100 | .end(function(err,res){ 101 | res.should.have.status(200); 102 | res.body.phoneNumbers.length.should.equal(0); 103 | done() 104 | }); 105 | }); 106 | 107 | 108 | it('POST with phone /v1/users/ should return 200',function(done){ 109 | var u=_.extend({},user) 110 | u.phoneNumbers=[{number:'076.378.89.98',what:'mobile'}] 111 | u.addresses=[] 112 | request(app) 113 | .post('/v1/users/'+user.id) 114 | .send(u) 115 | .set('cookie', cookie) 116 | .end(function(err,res){ 117 | res.should.have.status(200); 118 | res.body.phoneNumbers.length.should.equal(1); 119 | res.body.phoneNumbers[0].number.should.equal('076.378.89.98') 120 | done() 121 | }); 122 | }); 123 | 124 | 125 | 126 | }); 127 | 128 | -------------------------------------------------------------------------------- /test/api.users.update.secure.admin.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should");require("should-http"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js",'Products.js']); 8 | 9 | 10 | describe("api.users", function(){ 11 | var request= require('supertest'); 12 | 13 | var _=require('underscore'); 14 | 15 | var cookie; 16 | 17 | before(function(done){ 18 | dbtools.clean(function(e){ 19 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js","../fixtures/Products.js"],db,function(err){ 20 | should.not.exist(err); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | 27 | after(function(done){ 28 | dbtools.clean(function(){ 29 | done(); 30 | }); 31 | }); 32 | 33 | 34 | var user; 35 | 36 | it('POST /login should return 200',function(done){ 37 | request(app) 38 | .post('/login') 39 | .send({ email:"evaleto@gmail.com", provider:'local', password:'password' }) 40 | .end(function(err,res){ 41 | res.should.have.status(200); 42 | cookie = res.headers['set-cookie']; 43 | user=res.body; 44 | done(); 45 | }); 46 | }); 47 | 48 | it('POST update user, with different user.email.status be ok for admin',function(done){ 49 | var u=data.Users[1]; 50 | request(app) 51 | .post('/v1/users/'+user.id) 52 | .send(_.extend({},u,{email:{status:false}})) 53 | .set('cookie', cookie) 54 | .end(function (err,res) { 55 | res.should.have.status(200); 56 | res.body.email.status.should.equal(false) 57 | res.body.email.address.should.equal(u.email.address) 58 | done(); 59 | }) 60 | }); 61 | 62 | it('POST update other user with admin role, should return 200',function(done){ 63 | var u=data.Users[0]; 64 | request(app) 65 | .post('/v1/users/'+data.Users[0].id) 66 | .send(u) 67 | .set('cookie', cookie) 68 | .end(function (err,res) { 69 | res.should.have.status(200); 70 | res.body.id.should.equal(data.Users[0].id) 71 | done(); 72 | }) 73 | }); 74 | 75 | 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /test/db.maintain.js: -------------------------------------------------------------------------------- 1 | var app = require("../app"); 2 | 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js"]); 8 | 9 | var DbMaintain = db.model('DbMaintain'); 10 | 11 | 12 | describe("DbMaintain", function(){ 13 | var _ = require("underscore"); 14 | 15 | var error, log; 16 | 17 | 18 | before(function(done){ 19 | dbtools.clean(function(){ 20 | done(); 21 | }); 22 | }); 23 | 24 | it("Find latest version with no entry", function(done){ 25 | DbMaintain.findLatestVersion(function(err, version){ 26 | should.not.exist(err); 27 | version.should.equal(0); 28 | done(); 29 | }); 30 | }); 31 | 32 | 33 | it("First DbMaintain entry", function(done){ 34 | var dbm={ 35 | version: 1, 36 | log:"standard log", 37 | }; 38 | DbMaintain.save(dbm, function(err, new_dbm){ 39 | should.not.exist(err); 40 | new_dbm['version'].should.equal(dbm['version']) 41 | done(); 42 | }); 43 | }); 44 | 45 | 46 | it("Add DbMaintain entry", function(done){ 47 | var dbm={ 48 | version: 2, 49 | log:"new log", 50 | }; 51 | DbMaintain.save(dbm, function(err, new_dbm){ 52 | should.not.exist(err); 53 | new_dbm['version'].should.equal(dbm['version']) 54 | done(); 55 | }); 56 | }); 57 | 58 | it("Find all entries", function(done){ 59 | DbMaintain.findAll(function(err, version){ 60 | should.not.exist(err); 61 | version.length.should.equal(2); 62 | done(); 63 | }); 64 | }); 65 | 66 | it("Find lates version", function(done){ 67 | DbMaintain.findLatestVersion(function(err, version){ 68 | should.not.exist(err); 69 | version.should.equal(2); 70 | done(); 71 | }); 72 | }); 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /test/fixtures.js: -------------------------------------------------------------------------------- 1 | 2 | var app = require("../app"); 3 | var db = require("mongoose"); 4 | var should = require("should"); 5 | 6 | 7 | var dbtools = require("./fixtures/dbtools"); 8 | var data = dbtools.fixtures(["Users.js","Categories.js"]); 9 | 10 | describe("mongoose.fixtures", function(){ 11 | 12 | before(function(done){ 13 | dbtools.clean(function(err){ 14 | should.not.exist(err) 15 | done(); 16 | }); 17 | }); 18 | 19 | 20 | after(function(done){ 21 | dbtools.clean(function(err){ 22 | should.not.exist(err) 23 | done(); 24 | }); 25 | }); 26 | 27 | 28 | it('load json from dbtools.fixtures',function(done){ 29 | should.exist(data.Users) 30 | should.exist(data.Categories) 31 | done() 32 | }); 33 | 34 | it('load users',function(done){ 35 | dbtools.load(["../fixtures/Users.js"],db, function(err){ 36 | should.not.exist(err) 37 | db.model('Users').find({},function(e,users){ 38 | users.length.should.equal(5); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | it('check user password after loading fixture',function(done){ 45 | var u=data.Users[0]; 46 | db.model('Users').authenticate(u.email.address, u.password, function(err,user){ 47 | user.email.address.should.equal(u.email.address); 48 | done(); 49 | }); 50 | }); 51 | 52 | it('load categories',function(done){ 53 | dbtools.load(["../fixtures/Categories.js"],db, function(err){ 54 | should.not.exist(err) 55 | db.model('Categories').find({},function(e,docs){ 56 | docs.length.should.equal(4); 57 | done(); 58 | }); 59 | }); 60 | }); 61 | 62 | it('load shops',function(done){ 63 | dbtools.load(['../fixtures/Shops.js'],db, function(err,d,c){ 64 | should.not.exist(err) 65 | db.model('Shops').find({},function(e,docs){ 66 | docs.length.should.equal(4); 67 | done(); 68 | }); 69 | }); 70 | }); 71 | 72 | it('load products',function(done){ 73 | dbtools.load(['../fixtures/Products.js'],db, function(err,d,c){ 74 | should.not.exist(err) 75 | db.model('Products').find({},function(e,docs){ 76 | docs.length.should.equal(3); 77 | done(); 78 | }); 79 | }); 80 | }); 81 | 82 | }); 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /test/fixtures/Categories.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongodb').ObjectID; 2 | 3 | exports.Categories=[{ 4 | _id:new ObjectId('115ec12e56a8d5961e000000'), 5 | name:"alimentaire", 6 | slug:"alimentaire", 7 | weight:1, 8 | type:"Catalog" 9 | },{ 10 | _id:new ObjectId('115ec12e56a8d5961e000001'), 11 | name:"Fruits", 12 | slug:"fruits", 13 | weight:2, 14 | type:"Category" 15 | },{ 16 | _id:new ObjectId('115ec12e56a8d5961e000002'), 17 | name:"Légumes", 18 | slug:"legumes", 19 | weight:1, 20 | type:"Category" 21 | },{ 22 | _id:new ObjectId('115ec12e56a8d5961e000003'), 23 | name:"Poissons", 24 | slug:"poissons", 25 | weight:0, 26 | type:"Category" 27 | } 28 | ]; 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/fixtures/Documents.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongodb').ObjectID; 2 | 3 | // 12345 ==> evaleto@gluck.com 4 | // 12346 ==> evaleto@gmail.com 5 | // 12347 ==> delphine@gmail.com 6 | 7 | exports.Documents=[{ 8 | title: 'fixture titre', 9 | slug: 'fixture-titre', 10 | header:'##test1', 11 | content:'##test1', 12 | photo:{ 13 | header:'http://photooooz', 14 | bundle:['http://photooooz'] 15 | }, 16 | 17 | created: new Date(), 18 | updated: new Date(), 19 | available:true, 20 | published:false, 21 | 22 | skus:[12345,12346,12347], 23 | type: 'page', 24 | owner:12345 25 | } 26 | ]; 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/fixtures/Products.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongodb').ObjectID; 2 | var d=require('./Categories'); 3 | 4 | // shop[0](un-autre-shop, id:0004, status:true, owner:gluck) 5 | // shop[1](mon-shop, id:0005, status:false, owner:gmail) 6 | // shop[2](invalid-shop, id:0006, status:Date.now, owner:gluck) 7 | exports.Products=[{ 8 | _id : new ObjectId(), 9 | sku:12345, 10 | title: "Test product bio 1", 11 | details:{ 12 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 13 | comment:"Temps de cuisson : 16 minutes", 14 | homemade:true, 15 | natural:false, 16 | bio:true, 17 | }, 18 | 19 | attributes:{ 20 | available:true, 21 | comment:false, 22 | discount:false 23 | }, 24 | 25 | pricing: { 26 | stock:10, 27 | price:3.80, 28 | discount:3.0, 29 | part:'100gr' 30 | }, 31 | photo:{ 32 | url:"http://photooooz" 33 | }, 34 | //un-autre-shop, status:true, owner:gluck 35 | vendor:ObjectId('515ec12e56a8d5961e000004') 36 | },{ 37 | _id : new ObjectId(), 38 | sku:12346, 39 | title: "Test product 2", 40 | details:{ 41 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 42 | comment:"Temps de cuisson : 16 minutes", 43 | homemade:true, 44 | natural:false, 45 | bio:false, 46 | }, 47 | attributes:{ 48 | available:true, 49 | comment:false, 50 | discount:false 51 | }, 52 | pricing: { 53 | stock:10, 54 | price:3.80, 55 | part:'100gr' 56 | }, 57 | photo:{ 58 | url:"http://photooooz" 59 | }, 60 | categories: d.Categories[3]._id , 61 | //un-autre-shop, status:true, owner:gluck 62 | vendor:'515ec12e56a8d5961e000004' 63 | },{ 64 | _id : new ObjectId(), 65 | sku:12347, 66 | title: "Test product bio 3", 67 | details:{ 68 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 69 | comment:"Temps de cuisson : 16 minutes", 70 | homemade:true, 71 | natural:false, 72 | bio:true, 73 | }, 74 | attributes:{ 75 | available:true, 76 | comment:false, 77 | discount:false 78 | }, 79 | pricing: { 80 | stock:10, 81 | price:3.80, 82 | discount:3.0, 83 | part:'100gr' 84 | }, 85 | photo:{ 86 | url:"http://photooooz" 87 | }, 88 | categories: d.Categories[3]._id , 89 | //mon-shop, id:0005, status:false, owner:gmail 90 | vendor:'515ec12e56a8d5961e000005' 91 | } 92 | ]; 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /test/fixtures/Products.more.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongodb').ObjectID; 2 | var data=require('./Categories.js'); 3 | 4 | // 2products ->shop[0](un-autre-shop, id:0004, status:true, owner:gluck) 5 | // 1product ->shop[1](mon-shop, id:0005, status:false, owner:gmail) 6 | // 0product ->shop[2](invalid-shop, id:0006, status:Date.now, owner:gluck) 7 | 8 | // data.Categories[0] -> alimentaire 9 | // data.Categories[1] -> fruits 10 | // data.Categories[2] -> legumes 11 | // data.Categories[3] -> poissons 12 | 13 | exports.Products=[{ 14 | _id : new ObjectId(), 15 | sku:1000001, 16 | title: "Product 1 with cat", 17 | details:{ 18 | description:"description", 19 | comment:"Temps de cuisson : 16 minutes", 20 | homemade:true, 21 | natural:false, 22 | bio:false, 23 | }, 24 | 25 | attributes:{ 26 | available:true, 27 | comment:false, 28 | discount:true 29 | }, 30 | 31 | pricing: { 32 | stock:10, 33 | price:3.80, 34 | discount:3.0, 35 | part:'100gr' 36 | }, 37 | /* weight:0,1 */ 38 | categories: data.Categories[1]._id, 39 | //un-autre-shop, id:0004, status:true, owner:gluck 40 | vendor:'515ec12e56a8d5961e000004' 41 | },{ 42 | _id : new ObjectId(), 43 | sku:1000002, 44 | title: "Product 2 with cat", 45 | details:{ 46 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 47 | comment:"Temps de cuisson : 16 minutes", 48 | homemade:true, 49 | natural:true, 50 | bio:true, 51 | }, 52 | attributes:{ 53 | available:true, 54 | comment:false, 55 | discount:false, 56 | home:true 57 | }, 58 | pricing: { 59 | stock:10, 60 | price:3.80, 61 | part:'0.75L' 62 | }, 63 | /* weight:2 */ 64 | categories:data.Categories[3]._id, 65 | //un-autre-shop, id:0004, status:true, owner:gluck 66 | vendor:'515ec12e56a8d5961e000004' 67 | },{ 68 | _id : new ObjectId(), 69 | sku:1000003, 70 | title: "Product 3 with cat", 71 | details:{ 72 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 73 | comment:"Temps de cuisson : 16 minutes", 74 | homemade:true, 75 | natural:false, 76 | bio:true, 77 | }, 78 | attributes:{ 79 | available:true, 80 | comment:false, 81 | discount:false 82 | }, 83 | pricing: { 84 | stock:10, 85 | price:3.80, 86 | discount:3.0, 87 | part:'0.75L' 88 | }, 89 | categories: data.Categories[1]._id, 90 | //mon-shop, id:0005, status:false, owner:gmail 91 | vendor:'515ec12e56a8d5961e000005' 92 | } 93 | ]; 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /test/fixtures/Products.sort.js: -------------------------------------------------------------------------------- 1 | var ObjectId = require('mongodb').ObjectID; 2 | var data=require('./Categories.js'); 3 | 4 | // 2products ->shop[0](un-autre-shop, id:0004, status:true, owner:gluck) 5 | // 1product ->shop[1](mon-shop, id:0005, status:false, owner:gmail) 6 | // 0product ->shop[2](invalid-shop, id:0006, status:Date.now, owner:gluck) 7 | 8 | exports.Products=[{ 9 | _id : new ObjectId(), 10 | sku:1000001, 11 | title: "Product 1 with cat", 12 | details:{ 13 | description:"description", 14 | comment:"Temps de cuisson : 16 minutes", 15 | homemade:true, 16 | natural:false, 17 | bio:false, 18 | }, 19 | 20 | attributes:{ 21 | available:true, 22 | comment:false, 23 | discount:false 24 | }, 25 | 26 | pricing: { 27 | stock:10, 28 | price:3.80, 29 | discount:3.0, 30 | }, 31 | /* weight:0,1 */ 32 | categories: data.Categories[1]._id, 33 | //un-autre-shop, id:0004, status:true, owner:gluck 34 | vendor:'515ec12e56a8d5961e000004' 35 | },{ 36 | _id : new ObjectId(), 37 | sku:1000002, 38 | title: "Product 2 with cat", 39 | details:{ 40 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 41 | comment:"Temps de cuisson : 16 minutes", 42 | homemade:true, 43 | natural:true, 44 | bio:true, 45 | }, 46 | attributes:{ 47 | available:true, 48 | comment:false, 49 | discount:false 50 | }, 51 | pricing: { 52 | stock:10, 53 | price:3.80, 54 | discount:3.0, 55 | part:'0.75L' 56 | }, 57 | /* weight:2 */ 58 | categories: data.Categories[3]._id, 59 | //un-autre-shop, id:0004, status:true, owner:gluck 60 | vendor:'515ec12e56a8d5961e000004' 61 | },{ 62 | _id : new ObjectId(), 63 | sku:1000003, 64 | title: "Product 3 with cat", 65 | details:{ 66 | description:"Gragnano de sa colline qui donne sur le Golfe de Naples, est depuis le XVI siècle la patrie de la pasta. ", 67 | comment:"Temps de cuisson : 16 minutes", 68 | homemade:true, 69 | natural:false, 70 | bio:true, 71 | }, 72 | attributes:{ 73 | available:true, 74 | comment:false, 75 | discount:false 76 | }, 77 | pricing: { 78 | stock:10, 79 | price:3.80, 80 | discount:3.0, 81 | part:'0.75L' 82 | }, 83 | categories: data.Categories[2]._id, 84 | //mon-shop, id:0005, status:false, owner:gmail 85 | vendor:'515ec12e56a8d5961e000004' 86 | } 87 | ]; 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /test/fixtures/Wallets.js: -------------------------------------------------------------------------------- 1 | 2 | var ObjectId = require('mongoose').Schema.Types.ObjectID; 3 | 4 | // 12345 ==> evaleto@gluck.com 5 | // 12346 ==> evaleto@gmail.com 6 | // 12347 ==> delphine@gmail.com 7 | 8 | exports.Wallets=[{ 9 | id:'12345', 10 | apikey:config.payment.wallet.apikey, 11 | description: 'this is a demo wallet', 12 | email: 'evaleto@gluck.com', 13 | card:{ 14 | last4: '4657', 15 | number: '4091517362214657', 16 | expiry: new Date('Tue Nov 08 2016 23:59:00 GMT+0100 (CET)') 17 | }, 18 | external_account: { 19 | name:'Demo Wallet', 20 | iban:'BE68539007547034' 21 | }, 22 | transfers_enabled:false, 23 | transfers: [], 24 | transactions: [], 25 | created: new Date('Mon Nov 09 2015 08:20:21 GMT+0100 (CET)'), 26 | updated: new Date('Mon Nov 09 2015 08:20:21 GMT+0100 (CET)'), 27 | balance: 0, 28 | wid: 'wa_1234567890' 29 | }, 30 | { 31 | id:'12346', 32 | apikey:config.payment.wallet.apikey, 33 | description: 'this is a demo wallet', 34 | email: 'evaleto@gmail.com', 35 | card:{ 36 | last4: '1520', 37 | number: '4091002818331520', 38 | expiry: new Date('Tue Nov 08 2016 23:59:00 GMT+0100 (CET)') 39 | }, 40 | external_account: { 41 | name:'Demo Wallet', 42 | iban:'BE68539007547034' 43 | }, 44 | transfers_enabled:false, 45 | transfers: [], 46 | transactions: [], 47 | created: new Date('Mon Nov 09 2015 08:20:21 GMT+0100 (CET)'), 48 | updated: new Date('Mon Nov 09 2015 08:20:21 GMT+0100 (CET)'), 49 | balance: 0, 50 | wid: 'wa_1234567891' 51 | }, 52 | { 53 | id:'12347', 54 | apikey:config.payment.wallet.apikey, 55 | description: 'this is a demo wallet', 56 | email: 'delphine@gmail.com', 57 | card:{ 58 | last4: '1282', 59 | number: '2923209776891282', 60 | expiry: new Date('Tue Nov 08 2016 23:59:00 GMT+0100 (CET)') 61 | }, 62 | external_account: { 63 | name:'Demo Wallet', 64 | iban:'BE68539007547034' 65 | }, 66 | transfers_enabled:false, 67 | transfers: [], 68 | transactions: [], 69 | created: new Date('Mon Nov 09 2015 08:20:21 GMT+0100 (CET)'), 70 | updated: new Date('Mon Nov 09 2015 08:20:21 GMT+0100 (CET)'), 71 | balance: 0, 72 | wid: 'wa_1234567892' 73 | }]; 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /test/fixtures/dbtools.js: -------------------------------------------------------------------------------- 1 | 2 | var async = require("async"); 3 | var db = require("mongoose"); 4 | 5 | var fx = require('pow-mongoose-fixtures'); 6 | 7 | 8 | exports.clean=function(callback){ 9 | if (process.env.NODE_ENV!=='test'){ 10 | console.log('cannot run test without test environement: NODE_ENV=test mocha') 11 | process.exit(1); 12 | } 13 | 14 | var collections=['Users','Categories','Shops','Products','Sequences','DbMaintain', 'Emails','Wallets']; 15 | var iterator = function(name, nextcb){ 16 | db.model(name).remove({},function(e){ 17 | nextcb(e); 18 | }); 19 | }; 20 | async.forEach(collections, iterator,callback); 21 | }; 22 | 23 | exports.fixtures=function(names){ 24 | if (process.env.NODE_ENV!=='test'){ 25 | console.log('cannot run test without test environement: NODE_ENV=test mocha') 26 | process.exit(1); 27 | } 28 | 29 | var data={}; 30 | names.forEach(function(name) { 31 | var fx=require('../fixtures/'+name); 32 | Object.keys(fx).forEach(function(model){ 33 | data[model]=fx[model]; 34 | }); 35 | }); 36 | return data; 37 | } 38 | exports.load=function(fixtures, cb, callback){ 39 | if (process.env.NODE_ENV!=='test'){ 40 | console.log('cannot run test without test environement: NODE_ENV=test mocha') 41 | process.exit(1); 42 | } 43 | 44 | var iterator = function(fixture, nextcb){ 45 | fx.load(fixture,db, nextcb); 46 | }; 47 | async.forEach(fixtures, iterator,callback); 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | -R spec 3 | --ui bdd 4 | 5 | -------------------------------------------------------------------------------- /test/order.find.user.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require('mongoose'); 5 | var dbtools = require("./fixtures/dbtools"); 6 | var should = require("should"); 7 | var data = dbtools.fixtures(["Users.js","Categories.js","Orders.find.js"]); 8 | 9 | var Products=db.model('Products') 10 | , Orders=db.model('Orders') 11 | , today=new Date() 12 | , toshortDay 13 | , okDay; 14 | 15 | 16 | /** 17 | * find order with criteria: 18 | * - closed(all or with date), open 19 | * - filter by shipping date 20 | * - filter by shop slug 21 | */ 22 | 23 | describe("orders.find.user", function(){ 24 | var _ = require("underscore"); 25 | 26 | var nextday=Orders.findNextShippingDay(); 27 | var monday=Orders.jumpToNextWeekDay(new Date(),1); 28 | 29 | // on friday next shipping day equal monday 30 | // If days equal , orders count are different 31 | var dateEqual=(monday.getDay()==nextday.getDay()) 32 | 33 | 34 | before(function(done){ 35 | dbtools.clean(function(e){ 36 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Orders.find.js"],db,function(err){ 37 | should.not.exist(err); 38 | // Orders.printInfo() 39 | // Orders.find({}).exec(function(e,os){ 40 | // os.forEach(function(o){o.print()}) 41 | // }) 42 | 43 | done(); 44 | }); 45 | }); 46 | }); 47 | 48 | 49 | after(function(done){ 50 | dbtools.clean(function(){ 51 | done(); 52 | }); 53 | }); 54 | 55 | it("find all orders (3) for user evaleto", function(done){ 56 | var criteria={ 57 | user: 12346, 58 | } 59 | db.model('Orders').findByCriteria(criteria, function(err,order){ 60 | should.not.exist(err) 61 | order.length.should.equal(4) 62 | order[0].customer.id.should.equal(12346) 63 | done(); 64 | }); 65 | }); 66 | 67 | it.skip("find open orders (3) for user evaleto", function(done){ 68 | var criteria={ 69 | user: 12346, 70 | closed:null 71 | } 72 | db.model('Orders').findByCriteria(criteria, function(err,order){ 73 | should.not.exist(err) 74 | order[0].customer.id.should.equal(12346) 75 | order.length.should.equal(3) 76 | done(); 77 | }); 78 | }); 79 | 80 | it("find closed orders (1) for user evaleto", function(done){ 81 | var criteria={ 82 | user: 12346, 83 | closed:true 84 | } 85 | db.model('Orders').findByCriteria(criteria, function(err,order){ 86 | should.not.exist(err) 87 | order[0].customer.id.should.equal(12346) 88 | order.length.should.equal(1) 89 | done(); 90 | }); 91 | }); 92 | 93 | 94 | }); 95 | 96 | -------------------------------------------------------------------------------- /test/products.find.disabled.js: -------------------------------------------------------------------------------- 1 | var app = require("../app"); 2 | 3 | var db = require("mongoose"); 4 | 5 | 6 | var dbtools = require("./fixtures/dbtools"); 7 | var should = require("should"); 8 | var data = dbtools.fixtures(["Users.js","Categories.js","Products.disabled.js"]); 9 | var Users=db.model('Users'); 10 | 11 | 12 | describe("products.find.disabled:", function(){ 13 | var async= require("async"); 14 | var _ = require("underscore"); 15 | var Products=db.model('Products'); 16 | 17 | 18 | before(function(done){ 19 | dbtools.clean(function(e){ 20 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Products.disabled.js"],db,function(err){ 21 | should.not.exist(err); 22 | done(); 23 | }); 24 | }); 25 | }); 26 | 27 | 28 | after(function(done){ 29 | dbtools.clean(function(){ 30 | done(); 31 | }) 32 | }); 33 | 34 | //product 1 not available , but shop is available 35 | //product 2 available , shop not available 36 | //product 3 available , shop closed 37 | //product 4 available , shop will be closed in futur 38 | //product 5 available , shop was closed in past 39 | it("only product 4, 5 should be available", function(done){ 40 | Products.findByCriteria({status:true,available:true},function(err,products){ 41 | should.not.exist(err); 42 | should.exist(products); 43 | console.log('find',products.map(function (p) {return p.sku;})); 44 | products.length.should.equal(2) 45 | products[0].sku.should.equal(1000004); 46 | products[1].sku.should.equal(1000005); 47 | done(); 48 | }); 49 | }); 50 | 51 | 52 | it.skip("Product could have a related products", function(done){ 53 | }); 54 | 55 | it.skip("Product could have variations", function(done){ 56 | }); 57 | 58 | it.skip("Control if out of stock products can still be shown and are available for purchase", function(done){ 59 | }); 60 | 61 | 62 | }); 63 | 64 | -------------------------------------------------------------------------------- /test/products.find.more.js: -------------------------------------------------------------------------------- 1 | var app = require("../app"); 2 | 3 | var db = require("mongoose"); 4 | 5 | 6 | var dbtools = require("./fixtures/dbtools"); 7 | var should = require("should"); 8 | var data = dbtools.fixtures(["Users.js","Categories.js","Products.more.js","Shops.js"]); 9 | var Users=db.model('Users'); 10 | 11 | 12 | describe("products.find.more", function(){ 13 | var _ = require("underscore"); 14 | var Products=db.model('Products'); 15 | 16 | 17 | before(function(done){ 18 | dbtools.clean(function(e){ 19 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js","../fixtures/Products.more.js"],db,function(err){ 20 | should.not.exist(err); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | 27 | after(function(done){ 28 | dbtools.clean(function(){ 29 | done(); 30 | }) 31 | }); 32 | 33 | it("Find popular products ", function(done){ 34 | var criteria={ popular: true }; 35 | Products.findByCriteria(criteria,function(err,products){ 36 | should.not.exist(err); 37 | should.exist(products); 38 | products.length.should.equal(3) 39 | done(); 40 | }); 41 | }); 42 | 43 | 44 | 45 | it("Find home products ", function(done){ 46 | var criteria={ home:true }; 47 | Products.findByCriteria(criteria,function(err,products){ 48 | should.not.exist(err); 49 | should.exist(products); 50 | products.length.should.equal(1) 51 | done(); 52 | }); 53 | 54 | }); 55 | 56 | it("Find discount products ", function(done){ 57 | var criteria={ discount:true }; 58 | Products.findByCriteria(criteria,function(err,products){ 59 | should.not.exist(err); 60 | should.exist(products); 61 | products.length.should.equal(1) 62 | done(); 63 | }); 64 | }); 65 | 66 | it("Find discount products SKU ", function(done){ 67 | Products.findDiscountSKUs(function(err,skus){ 68 | should.not.exist(err); 69 | should.exist(skus); 70 | skus.length.should.equal(1); 71 | (typeof skus[0]).should.equal('number'); 72 | done(); 73 | }); 74 | 75 | }); 76 | 77 | 78 | 79 | 80 | }); 81 | 82 | -------------------------------------------------------------------------------- /test/products.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | // Use a different DB for tests 3 | var app = require("../app"); 4 | 5 | var db = require('mongoose'); 6 | var dbtools = require("./fixtures/dbtools"); 7 | var should = require("should"); 8 | var data = dbtools.fixtures(["Users.js","Categories.js","Shops.js"]); 9 | 10 | 11 | 12 | describe("products", function(){ 13 | var _ = require("underscore"); 14 | 15 | before(function(done){ 16 | dbtools.clean(function(e){ 17 | dbtools.load(["../fixtures/Users.js","../fixtures/Categories.js","../fixtures/Shops.js"],db,function(err){ 18 | should.not.exist(err); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | 25 | after(function(done){ 26 | dbtools.clean(function(){ 27 | done(); 28 | }); 29 | }); 30 | 31 | 32 | it.skip("Find products by Manufacturer and Category and details ", function(done){ 33 | }); 34 | 35 | it.skip("Product can be enabled or disabled", function(done){ 36 | }); 37 | 38 | it.skip("Product could have a related products", function(done){ 39 | }); 40 | 41 | it.skip("Product could have variations", function(done){ 42 | }); 43 | 44 | it.skip("Control if out of stock products can still be shown and are available for purchase", function(done){ 45 | }); 46 | 47 | 48 | 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test/sequences.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"), 3 | db = require('mongoose'), 4 | dbtools = require("./fixtures/dbtools"), 5 | should = require("should"); 6 | 7 | 8 | describe("sequences", function(){ 9 | 10 | var SKU=db.model('Sequences').initNumber('sku'); 11 | before(function(done){ 12 | dbtools.clean(function(){ 13 | done(); 14 | }); 15 | }); 16 | 17 | 18 | after(function(done){ 19 | dbtools.clean(function(){ 20 | done(); 21 | }); 22 | }); 23 | 24 | it("First SKU ", function(done){ 25 | db.model('Sequences').nextSku(function(err,sku){ 26 | sku.should.equal(SKU); 27 | done(); 28 | }); 29 | }); 30 | 31 | it("Next SKU, ", function(done){ 32 | db.model('Sequences').nextSku(function(err,sku){ 33 | sku.should.equal(SKU+1); 34 | done(); 35 | }); 36 | }); 37 | 38 | it("Next SKU, ", function(done){ 39 | db.model('Sequences').next('sku',function(err,sku){ 40 | sku.should.equal(SKU+2); 41 | done(); 42 | }); 43 | }); 44 | 45 | it("Next SKU with promise ", function(done){ 46 | db.model('Sequences').nextSku().then(function(sku){ 47 | sku.should.equal(SKU+3); 48 | done(); 49 | }); 50 | }); 51 | 52 | 53 | it("Race condition without init",function (done) { 54 | require("async").parallelLimit([ 55 | function(cb){ 56 | db.model('Sequences').next('sku',cb); 57 | }, 58 | function(cb){ 59 | db.model('Sequences').next('sku',cb); 60 | }, 61 | function(cb){ 62 | db.model('Sequences').next('sku',cb); 63 | }, 64 | function(cb){ 65 | db.model('Sequences').nextSku(cb); 66 | } 67 | ],4, function(err,seq){ 68 | seq.length.should.equal(4) 69 | seq.should.containEql(1000004) 70 | seq.should.containEql(1000005) 71 | seq.should.containEql(1000006) 72 | seq.should.containEql(1000007) 73 | done() 74 | }); 75 | 76 | 77 | }) 78 | 79 | it.skip("Race condition with promise",function (done) { 80 | require("async").parallelLimit([ 81 | function(cb){ 82 | db.model('Sequences').next('other',cb); 83 | }, 84 | function(cb){ 85 | db.model('Sequences').next('other',cb); 86 | }, 87 | function(cb){ 88 | db.model('Sequences').next('other',cb); 89 | }, 90 | function(cb){ 91 | db.model('Sequences').next('other',cb); 92 | } 93 | ],4, function(err,seq){ 94 | done() 95 | }); 96 | 97 | 98 | }) 99 | 100 | 101 | 102 | }); 103 | 104 | -------------------------------------------------------------------------------- /test/users.reminder.js: -------------------------------------------------------------------------------- 1 | // Use a different DB for tests 2 | var app = require("../app"); 3 | 4 | var db = require("mongoose"); 5 | 6 | var dbtools = require("./fixtures/dbtools"); 7 | var should = require("should"); 8 | var data = dbtools.fixtures(["Users.reminder.js"]); 9 | 10 | 11 | 12 | 13 | describe("users.reminder", function(){ 14 | 15 | before(function(done){ 16 | dbtools.clean(function(e){ 17 | dbtools.load(["../fixtures/Users.reminder.js"],db,function(err){ 18 | should.not.exist(err); 19 | // db.model('Users').find({}).exec(function(e,us) { 20 | // us.forEach(function (u) { 21 | // console.log('-------------',u.id,u.email.address,JSON.stringify(u.reminder)) 22 | // }) 23 | // }) 24 | done(); 25 | }); 26 | }); 27 | }); 28 | 29 | after(function(done){ 30 | dbtools.clean(function(){ 31 | done(); 32 | }); 33 | }); 34 | 35 | 36 | it('find users with reminder weekday(0),time(11) should return 0',function(done) { 37 | var reminder={ 38 | weekdays:[0],time:11 39 | }; 40 | db.model('Users').findByReminder(reminder).then(function(users) { 41 | setTimeout(function() { 42 | users.length.should.equal(0); 43 | done() 44 | }); 45 | }) 46 | }) 47 | 48 | it('find users with reminder weekday(1),time(10) should return 0',function(done) { 49 | var reminder={ 50 | weekdays:[1],time:10 51 | }; 52 | db.model('Users').findByReminder(reminder).then(function(users) { 53 | setTimeout(function() { 54 | users.length.should.equal(0); 55 | done() 56 | }); 57 | }) 58 | }) 59 | 60 | it('find users with reminder weekday(1),time(11) should return 2',function(done) { 61 | var reminder={ 62 | weekdays:[1],time:11 63 | }; 64 | db.model('Users').findByReminder(reminder).then(function(users) { 65 | setTimeout(function() { 66 | users.length.should.equal(2); 67 | done() 68 | }); 69 | }) 70 | }) 71 | 72 | 73 | it('activate reminder for user evaleto@gmail.com',function(done) { 74 | db.model('Users').findAndUpdate(12346,{reminder:{active:true}}).then(function(user) { 75 | user.reminder.active.should.equal(true); 76 | should.exist(user.reminder.weekdays) 77 | should.exist(user.reminder.time) 78 | done() 79 | }) 80 | }) 81 | 82 | it('find activated previous reminder weekday(0),time(11) should return 1',function(done) { 83 | var reminder={ 84 | weekdays:[0],time:11 85 | }; 86 | db.model('Users').findByReminder(reminder).then(function(users) { 87 | setTimeout(function() { 88 | users.length.should.equal(1); 89 | done() 90 | }); 91 | }) 92 | }) 93 | 94 | it('find users with reminder weekday 10',function(done) { 95 | done() 96 | }) 97 | 98 | 99 | 100 | }); 101 | 102 | -------------------------------------------------------------------------------- /update-gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | if git status --porcelain 2>/dev/null | grep -q .; then 3 | echo "Working copy is dirty" >&2 4 | exit 1 5 | fi 6 | rm -rf build 7 | git checkout gh-pages 8 | git merge master 9 | grunt dist 10 | git add build 11 | git commit -m 'automatic gh-pages build' 12 | git checkout master 13 | -------------------------------------------------------------------------------- /views/500.jade: -------------------------------------------------------------------------------- 1 | 2 | block main 3 | h1 Oops something went wrong 4 | br 5 | span 500 6 | 7 | block content 8 | pre= error 9 | -------------------------------------------------------------------------------- /views/cookie.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Karibou API 5 | link(href="/css/bootstrap-white.min.css", rel="stylesheet") 6 | link(href="/css/bootstrap-responsive.min.css", rel="stylesheet") 7 | script(src="js/jquery-1.9.0.min.js") 8 | script(src="js/bootstrap.min.js") 9 | 10 | body 11 | 12 | body(data-spy="scroll", data-target=".bs-docs-sidebar") 13 | block content 14 | div.alert.alert-info.fade.in 15 | button(type="button", class="close", data-dismiss="alert") 16 | p Certains navigateur demande une confirmation du cookie, 17 | a(href='http://karibou.evaletolab.ch', title="Accept Cookie") en acceptant de cliquer ce lien vous pourrez profiter pleinement de votre site -------------------------------------------------------------------------------- /views/document.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(itemscope itemtype="http://schema.org/Article") 3 | head 4 | // http://schema.org/Article 5 | title: =getLocal(doc.title)+' ('+doc.type+')' 6 | meta(http-equiv='Content-Type', content='text/html; charset=UTF-8') 7 | meta(name='robots', content='index,follow') 8 | meta(name="viewport" content="width=device-width, initial-scale=1.0") 9 | meta(name='revised', content='#{doc.updated}') 10 | 11 | 12 | // Place this data between the tags of your website 13 | meta( name="description", content="#{getLocal(doc.header)}") 14 | 15 | // Schema.org markup for Google+ 16 | meta( itemprop="name", content="#{getLocal(doc.title)}") 17 | meta( itemprop="description", content="#{getLocal(doc.header)}") 18 | meta( itemprop="image", content="#{prependUrlImage(doc.photo.header)}") 19 | 20 | // Twitter Card data 21 | meta( name="twitter:card", content="article") 22 | meta( name="twitter:title", content="#{getLocal(doc.title)}") 23 | meta( name="twitter:description", content="#{getLocal(doc.header)}") 24 | meta( name="twitter:creator", content="#{doc.signature}") 25 | meta( name="twitter:image", content="#{prependUrlImage(doc.photo.header)}") 26 | 27 | // Open Graph data 28 | meta( property="og:title", content="#{getLocal(doc.title)}" ) 29 | meta( property="og:type", content="article" ) 30 | meta( property="og:url", content="https://karibou.ch/content/#{doc.slug[0]}" ) 31 | meta( property="og:image", content="#{prependUrlImage(doc.photo.header)}-/resize/600x/" ) 32 | meta( property="og:description", content="#{getLocal(doc.header)}" ) 33 | meta( property="og:site_name", content="https://karibou.ch" ) 34 | 35 | // Full example with review and price 36 | // https://schema.org/price 37 | link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css") 38 | link(href="/css/style.css", rel="stylesheet") 39 | body 40 | div(class="container") 41 | 42 | div(class="header clearfix") 43 | nav(class="navbar navbar-default") 44 | ul(class="nav navbar-nav ") 45 | - each menu in config.shared.menu 46 | li(role="presentation" class="") 47 | if menu.active && (['karibou','howto','links'].indexOf(menu.group)!==-1) 48 | a( href="#{menu.url}"): | !{getLocal(menu.name)} 49 | li(role="presentation" class=""): a( href="/shops") Les boutiques 50 | 51 | 52 | div(class="jumbotron") 53 | h2 Des produits frais, bio, votre maraîcher, boulanger, fromager, boucher... livrés à votre porte! 54 | 55 | 56 | 57 | 58 | h1(itemprop="name"): =getLocal(doc.title) 59 | div(itemprop="description") 60 | !{md.render(getLocal(doc.header))} 61 | if doc.photo.header 62 | img(src='#{prependUrlImage(doc.photo.header)}-/resize/600x/', class="img-responsive") 63 | 64 | h1: La sélection 65 | - each product in products 66 | img(src='#{prependUrlImage(product.photo.url)}-/resize/600x/') 67 |
68 | 69 | div 70 | !{md.render(getLocal(doc.content))} 71 | 72 | 73 | h4 74 | !{(doc.updated).toDateString()} , by #{doc.signature} 75 | -------------------------------------------------------------------------------- /views/home.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | block content 3 | - if (!user) 4 | li: a(href='/login', rel="tooltip", title="Login") Login 5 | li: a(href='/register', rel="tooltip", title="Login") Register 6 | li: a(href='/auth/twitter', rel="tooltip", title="Login") Login with Twitter 7 | - else 8 | li: a: =user.id+'@'+user.provider 9 | 10 | 11 | li: a(href='/logout') Logout 12 | 13 | -------------------------------------------------------------------------------- /views/homeseo.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(http-equiv='Content-Type', content='text/html; charset=UTF-8') 5 | meta(name="viewport" content="width=device-width, initial-scale=1.0") 6 | meta(name='robots', content='index,follow') 7 | title: |Karibou - producteurs, artisans et épiciers online! 8 | meta(name="title", content="Karibou - producteurs, artisans et épiciers online!") 9 | meta(charset="utf-8") 10 | meta(name="description", content="Karibou - Votre maraîcher, boulanger, fromager, boucher... livrés à votre porte!") 11 | meta(name="author", content="karibou team") 12 | link(rel="canonical", href="https://karibou.ch/") 13 | link(rel="icon", type="image/png", href="/img/k-small.png" ) 14 | meta(property="og:type", content="website") 15 | meta(property="og:image", content="/img/k-small.png") 16 | meta(property="og:title", content="Karibou") 17 | meta(property="og:description", content="Karibou - Votre maraîcher, boulanger, fromager, boucher... livrés à votre porte!" ) 18 | meta(property="og:site_name", content="Karibou") 19 | meta(property="og:url", content="https://Karibou.ch") 20 | meta(name="twitter:site", content="@kariboulab") 21 | link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css") 22 | link(href="/css/style.css", rel="stylesheet") 23 | // GG analytics 24 | script(type='text/javascript'). 25 | if(location.origin.indexOf('evaletolab')==-1){ 26 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 27 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 28 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 29 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 30 | ga('require', 'displayfeatures'); 31 | ga('create', 'UA-57032730-1', 'auto'); 32 | } 33 | body 34 | div(class="container") 35 | div(class="header clearfix") 36 | nav(class="navbar navbar-default") 37 | ul(class="nav navbar-nav ") 38 | - each menu in config.shared.menu 39 | li(role="presentation" class="") 40 | if menu.active && (['karibou','howto','links'].indexOf(menu.group)!==-1) 41 | a( href="#{menu.url}"): | !{getLocal(menu.name)} 42 | li(role="presentation" class=""): a( href="/shops") Les boutiques 43 | 44 | 45 | 46 | div(class="jumbotron") 47 | h2 Des produits frais, bio, votre maraîcher, boulanger, fromager, boucher... livrés à votre porte! 48 | img(src="/img/hello-home.jpg", class="img-responsive img") 49 | 50 | 51 | 52 | div(class="list-group") 53 | - each cat in categories 54 | a(href="/products/category/#{cat.slug}" class="list-group-item link"): =cat.name 55 | 56 | block content 57 | 58 | 59 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title karibou api - make things 5 | link(rel='stylesheet', href='/css/style.css') 6 | link(rel='stylesheet', href='http://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext') 7 | meta(http-equiv='Content-Type', content='text/html; charset=UTF-8') 8 | script(src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js') 9 | body 10 | 11 | 12 | #container 13 | h1 14 | |API documentations 15 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Karibou API 5 | meta(name='robots', content='noindex,nofollow') 6 | link(href="/css/bootstrap-white.min.css", rel="stylesheet") 7 | link(href="/css/bootstrap-responsive.min.css", rel="stylesheet") 8 | link(href="/css/bootswatch.css", rel="stylesheet") 9 | script(src="js/jquery-1.9.0.min.js") 10 | script(src="js/bootstrap.min.js") 11 | 12 | // 13 | script(src='http://static.ak.fbcdn.net/connect/en_US/core.js') 14 | script. 15 | $(function(){ 16 | var a = $('a.menu[href="' + window.location.pathname + '"]'); 17 | a.parent().addClass('active'); 18 | }); 19 | body 20 | 21 | body(data-spy="scroll", data-target=".bs-docs-sidebar") 22 | 23 | // Navbar 24 | // ================================================== 25 | div.navbar.navbar-inverse.navbar-fixed-top 26 | div.navbar-inner 27 | div.container 28 | button( type="button", class="btn btn-navbar", data-toggle="collapse", data-target=".nav-collapse") 29 | span.icon-bar o 30 | span.icon-bar 31 | span.icon-bar 32 | a(class="brand", href="https://github.com/evaletolab/karibou-api") Karibou 33 | div.nav-collapse.collapse 34 | ul.nav 35 | li: a.menu(href="/") Home 36 | li: a.menu(href="/v1") API V1 37 | li: a.menu(href="/about") About 38 | 39 | ul(class="nav pull-right", id="main-menu-right") 40 | block content 41 | 42 | #main.container 43 | h2 Karibou API, the open source Marketplace 44 | p 45 | |Karibou is an open-source projects aim to help the creation of a online community marketplace. 46 | |The Karibou-api is the backend - for developpers - and is implemented as JSON and follow the rules of REST. 47 | - if (user) 48 | pre: =JSON.stringify(user) 49 | table.table(style="font-family: sans-serif;") 50 | thead: tr 51 | th: path 52 | th: method 53 | th: secure 54 | th: params 55 | tbody 56 | tr(colspan=4) 57 | td: b Selections 58 | each route in filter(api.get) 59 | tr(style="background-color:#eee") 60 | td: =route.path 61 | td: =route.method 62 | td: - 63 | td: =route.params 64 | tr(colspan=4) 65 | td: b Creations 66 | each route in filter(api.post) 67 | tr 68 | td: =route.path 69 | td: =route.method 70 | - if(route.callbacks.length) 71 | td: b secure 72 | - else 73 | td: - 74 | td: =route.params 75 | tr(colspan=4) 76 | td: b Deletions 77 | each route in filter(api.delete) 78 | tr(style="background-color:#eee") 79 | td: =route.path 80 | td: =route.method 81 | - if(route.callbacks[0]) 82 | td: b secure 83 | - else 84 | td: - 85 | td: =route.params 86 | tr(colspan=4) 87 | td: b Updates 88 | each route in filter(api.put) 89 | tr 90 | td: =route.path 91 | td: =route.method 92 | - if(route.callbacks[0]) 93 | td: b secure 94 | - else 95 | td: - 96 | td: =route.params 97 | 98 | -------------------------------------------------------------------------------- /views/login.jade: -------------------------------------------------------------------------------- 1 | title Login to karibou 2 | 3 | div 4 | div#header 5 | h2 karibou 6 | p Log In 7 | 8 | div 9 | form(action='/login', method='POST') 10 | #login.center 11 | label(for='email') Email: 12 | input(type='text', name='email', placeholder='email@email.com') 13 | #password.center 14 | label(for='password') Password: 15 | input(type='password', name='password', placeholder='password') 16 | input(type='hidden', name='provider', value='local') 17 | #submit.button 18 | input(type='submit', value='Log Me In!') Login 19 | -------------------------------------------------------------------------------- /views/products.jade: -------------------------------------------------------------------------------- 1 | extends homeseo 2 | 3 | block content 4 | h1: | Liste des produits 5 | div(class="list-group") 6 | - each product in products 7 | a(href="/products/#{product.sku}/#{product.slug}", class="list-group-item link", title="#{product.title}"): =product.title 8 | 9 | 10 | -------------------------------------------------------------------------------- /views/pspstd.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | // 3 | This is example of Dynamic Template Page you can start from to personnalize the look 4 | of your payment pages. 5 | This page must be hosted on your own website. We retrieve it every time an authorization 6 | is requested. The URL of the Template Page hosted on your site is given by you in 7 | the hidden fields of the ordering form for each authorization request. 8 | You can change anything in this page but you must keep the "PAYMENT ZONE" and 9 | the $$$ strings exactly as they are. 10 | The styles and classes defined hereafter allow you to modify the lay out of 11 | the elements added by us during the authorization process. Just change the 12 | properties of those styles to fit to the look of your site. 13 | head 14 | meta(http-equiv='Content-Type', content='text/html;CHARSET=utf-8') 15 | title Demo "standard mode" Sample Template Page 16 | | 17 | style(type='text/css'). 18 | body {margin:0px;} 19 | td.ncolh1 {} 20 | td.ncoltxtl {} 21 | td.ncoltxtl2 {} 22 | td.ncoltxtr {} 23 | td.ncoltxtc {} 24 | td.ncollogol {} 25 | td.ncollogor {} 26 | td.ncollogoc {} 27 | td.ncoltxtmessage {background-color : lightblue; color : lightblue; text-align : left; font-weight : bold} 28 | td.ncolinput {} 29 | td.ncolline1 {} 30 | td.ncolline2 {} 31 | input.ncol {} 32 | table.ncoltable1 { width:100%;background-color:#FCF8E3;display:none; } 33 | table.ncoltable2 { width:100%;background-color:#FCF8E3; } 34 | table.ncoltable3 { width:100%;background-color:#FCF8E3; } 35 | // for Direct Debit payments 36 | .DDtxt {text-align: left;margin-left:2em;font-weight: normal;margin-top:0;} 37 | .DDlabel {text-align: left; margin-left:4em;font-weight: normal;margin-top:0;} 38 | .DDdata {font-weight: normal;margin-top:0;} 39 | .MKtxt {text-align: left;font-weight: bold; margin-left:2em;margin-top:0;} 40 | .MKlabel {text-align: left; margin-left:4em;font-weight: normal;font-style:italic;margin-top:0;} 41 | td.ncoltxtr p.MKlabel {margin-left:0;} 42 | .MKdata {font-weight: normal;margin-top:0;} 43 | .DDimp {font-weight: bold;margin-left:2em;text-align: left;margin-top:0;} 44 | .DDsection {font-weight: bold;margin-left:0em;text-align: left; margin-top:1em; margin-bottom: 0em;} 45 | body 46 | table(border='0', width='100%') 47 | tr 48 | td(width='100%') 49 | // here is the payment form 50 | | $$$PAYMENT ZONE$$$ 51 | -------------------------------------------------------------------------------- /views/pspsuccess.jade: -------------------------------------------------------------------------------- 1 | div 2 | div#header 3 | h2 Votre mode de paiement à été correctement enregistré 4 | -------------------------------------------------------------------------------- /views/register.jade: -------------------------------------------------------------------------------- 1 | title Register a New User 2 | 3 | div 4 | div#header 5 | h2 CrowdNotes 6 | p Register as a User 7 | 8 | div 9 | form(action='/register', method='POST') 10 | #login.center 11 | label(for='email') Email: 12 | input(type='text', name='email', placeholder='email@email.com') 13 | #password.center 14 | label(for='password') Password: 15 | input(type='password', name='password', placeholder='password') 16 | #password.center 17 | label(for='confirm') Confirm: 18 | input(type='password', name='confirm', placeholder='confirm') 19 | #first-name.center 20 | label(for='name.first') First Name: 21 | input(type='text', name='name.first', placeholder='First Name') 22 | #last-name.center 23 | label(for='name.last') Last Name: 24 | input(type='text', name='name.last', placeholder='Last Name') 25 | #submit.button 26 | input(type='submit') Register 27 | 28 | div.button 29 | a(href='/account') HOME 30 | -------------------------------------------------------------------------------- /views/shop.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(itemscope, itemtype="http://schema.org/LocalBusiness") 3 | head 4 | // http://schema.org/LocalBusiness 5 | title: =shop.name 6 | meta(http-equiv='Content-Type', content='text/html; charset=UTF-8') 7 | meta(name="viewport" content="width=device-width, initial-scale=1.0") 8 | meta(name='robots', content='index,follow') 9 | 10 | // Place this data between the tags of your website 11 | meta( name="description", content="#{shop.description}") 12 | 13 | // Schema.org markup for Google+ 14 | meta( itemprop="name", content="#{shop.name}") 15 | meta( itemprop="description", content="#{shop.description}") 16 | meta( itemprop="image", content="#{prependUrlImage(shop.photo.fg)}") 17 | meta( itemprop="url", content="https://karibou.ch/shop/#{shop.urlpath}" ) 18 | 19 | // Twitter Card data 20 | meta( name="twitter:card", content="article") 21 | //meta( name="twitter:site", content="@publisher_handle") 22 | meta( name="twitter:title", content="#{shop.name}") 23 | meta( name="twitter:description", content="#{shop.description}") 24 | //meta( name="twitter:creator", content="@author_handle") 25 | meta( name="twitter:image", content="#{prependUrlImage(shop.photo.fg)}") 26 | 27 | // Open Graph data 28 | meta( property="og:title", content="#{shop.name}" ) 29 | meta( property="og:url", content="https://karibou.ch/shop/#{shop.urlpath}" ) 30 | meta( property="og:image", content="#{prependUrlImage(shop.photo.fg)}" ) 31 | meta( property="og:description", content="#{shop.description}" ) 32 | meta( property="og:site_name", content="https://karibou.ch" ) 33 | link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css") 34 | link(href="/css/style.css", rel="stylesheet") 35 | 36 | body 37 | div(class="container") 38 | div(class="header clearfix") 39 | nav(class="navbar navbar-default") 40 | ul(class="nav navbar-nav ") 41 | - each menu in config.shared.menu 42 | li(role="presentation" class="") 43 | if menu.active && (['karibou','howto','links'].indexOf(menu.group)!==-1) 44 | a( href="#{menu.url}"): | !{getLocal(menu.name)} 45 | li(role="presentation" class=""): a( href="/shops") Les boutiques 46 | 47 | h3( class="text-muted"): |Karibou Marketplace 48 | 49 | 50 | div(class="jumbotron") 51 | h2 Des produits frais, bio, votre maraîcher, boulanger, fromager, boucher... livrés à votre porte! 52 | 53 | h1: =shop.name 54 | div: =shop.description 55 | div 56 | img(src='#{prependUrlImage(shop.photo.fg)}', class="img-responsive") 57 | h1 Boutique 58 | div(itemprop='owns') 59 | div(itemprop="name"): =shop.address.name 60 | div(itemprop="url")#{shop.name} 61 | 62 | h2 En vente sur le marché 63 | ul(class="list-group") 64 | - each place in shop.marketplace 65 | li(class="list-group-item"): =place 66 | 67 | 68 | h2 Contact 69 | ul(class="list-group", itemscope, itemtype="http://schema.org/Organization") 70 | li(class="list-group-item",itemprop="telephone"): =shop.address.phone 71 | ul(class="list-group",itemscope, itemtype="http://schema.org/PostalAddress") 72 | li(class="list-group-item", itemprop="name"): =shop.address.name 73 | li(class="list-group-item", itemprop="streetAddress"): =shop.address.streetAdress 74 | li(class="list-group-item", itemprop="addressRegion"): =shop.address.region 75 | li(class="list-group-item", itemprop="postalCode"): =shop.address.postalCode 76 | -------------------------------------------------------------------------------- /views/shops.jade: -------------------------------------------------------------------------------- 1 | extends homeseo 2 | 3 | block content 4 | h1: | Liste des boutiques 5 | ol 6 | - each shop in shops 7 | li 8 | a( href="/shop/#{shop.urlpath}", title="#{shop.name}"): =shop.name 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /views/welcome.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Karibou API 5 | link(href="/css/bootstrap-white.min.css", rel="stylesheet") 6 | link(href="/css/bootstrap-responsive.min.css", rel="stylesheet") 7 | link(href="/css/bootswatch.css", rel="stylesheet") 8 | script(src="js/jquery-1.9.0.min.js") 9 | script(src="js/bootstrap.min.js") 10 | 11 | 12 | body(data-spy="scroll", data-target=".bs-docs-sidebar") 13 | 14 | --------------------------------------------------------------------------------