├── .nvmrc ├── .bowerrc ├── web ├── config.js ├── app.js ├── templates │ ├── basic.html │ ├── userProfile.html │ ├── login.html │ ├── homepage_detail.html │ ├── create.html │ └── managePage.html ├── js │ ├── lib │ │ ├── Logout.js │ │ ├── Rice.js │ │ ├── UserSession.js │ │ └── User.js │ ├── Model │ │ ├── RiceModel.js │ │ ├── UserLogin.js │ │ ├── CreateAccountModel.js │ │ ├── CreateRiceModel.js │ │ └── UserModel.js │ ├── CreateAccountView.js │ ├── HomeView.js │ ├── UserProfileView.js │ ├── LoginView.js │ └── AdminView.js ├── test │ └── spec │ │ ├── UserLoginSpec.js │ │ ├── CreateAccountViewSpec.js │ │ ├── routerSpec.js │ │ ├── LoginViewSpec.js │ │ ├── UserSessionSpec.js │ │ └── RiceModelSpec.js ├── main.js ├── index.html ├── lib │ └── require │ │ ├── async.js │ │ └── json.js ├── router.js └── css │ └── pricing.css ├── .npmignore ├── test ├── mocha.opts ├── common.js ├── services │ ├── rice_test.js │ ├── authenticate_test.js │ └── create_account_test.js ├── mapper │ └── db_mapper_test.js ├── rest │ └── rest_test.js └── system │ └── function_test.js ├── .gitignore ├── .travis.yml ├── .jslint.conf ├── database.json ├── migrations ├── 20141021181116-create-rice.js ├── 20141021180444-create-user.js ├── 20141023042211-update-user.js ├── 20141021173743-add-user.js └── 20141021173924-add-rice.js ├── server ├── mapper │ ├── db_prototype.js │ ├── db_helper.js │ ├── rice_mapper.js │ └── account_mapper.js ├── service │ ├── account_service.js │ ├── service_helper.js │ ├── rice_service.js │ └── authenticate.js └── app.js ├── bower.json ├── README.md ├── Gruntfile.js └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 0.10.32 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "web/lib", 3 | "analytics": false 4 | } -------------------------------------------------------------------------------- /web/config.js: -------------------------------------------------------------------------------- 1 | define({ 2 | localhost: "http://0.0.0.0:8080" 3 | }); -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dev.db 2 | .jslint.conf 3 | .travis.yml 4 | coverage/ 5 | web/lib 6 | .grunt 7 | _SpecRunner.html -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter dot 2 | --ui bdd 3 | --growl 4 | --check-leaks 5 | --colors 6 | --require test/common -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log 4 | dev.db 5 | coverage/ 6 | web/lib 7 | .grunt 8 | _SpecRunner.html -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10.32" 4 | 5 | notifications: 6 | email: false 7 | 8 | before_script: 9 | - db-migrate up 10 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | global.expect = require("chai").expect; 2 | global.assert = require("chai").assert; 3 | global.should = require('chai').should; 4 | 5 | require("../server/app"); -------------------------------------------------------------------------------- /.jslint.conf: -------------------------------------------------------------------------------- 1 | { 2 | "nomen": true, 3 | "debug": true, 4 | "evil": false, 5 | "vars": true, 6 | "unparam": true, 7 | "sub": true, 8 | "white": true, 9 | "regexp": true, 10 | "esnext": true, 11 | "predef" : ["define", "Backbone", "window"] 12 | } -------------------------------------------------------------------------------- /database.json: -------------------------------------------------------------------------------- 1 | { 2 | "dev": { 3 | "driver": "sqlite3", 4 | "filename": "dev.db" 5 | }, 6 | 7 | "test": { 8 | "driver": "sqlite3", 9 | "filename": "dev.db" 10 | }, 11 | 12 | "prod": { 13 | "driver": "sqlite3", 14 | "filename": "dev.db" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define([ 4 | 'jquery', 5 | 'underscore', 6 | 'backbone', 7 | 'router' 8 | ], function($, _, Backbone, Router){ 9 | 10 | var initialize = function(){ 11 | this.router = new Router(); 12 | }; 13 | 14 | return { 15 | initialize: initialize 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /web/templates/basic.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{title}}

5 |

6 | {{data}} 7 |

8 |

9 |
10 |
-------------------------------------------------------------------------------- /web/js/lib/Logout.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'underscore', 4 | 'mustache', 5 | 'js/lib/User', 6 | 'js/lib/UserSession' 7 | ],function($, _, Mustache, User, UserSession){ 8 | 'use strict'; 9 | var Logout = function(){ 10 | 11 | }; 12 | 13 | Logout.prototype.logout = function(){ 14 | UserSession.remove(); 15 | }; 16 | 17 | return Logout; 18 | }); -------------------------------------------------------------------------------- /web/templates/userProfile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{#.}} 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{/.}} 16 |
Nameemail
{{name}}{{email}}
-------------------------------------------------------------------------------- /migrations/20141021181116-create-rice.js: -------------------------------------------------------------------------------- 1 | var dbm = require('db-migrate'); 2 | var type = dbm.dataType; 3 | 4 | exports.up = function(db, callback) { 5 | db.insert('rice', 6 | ['name', 'type', 'price', 'quantity', 'description'], 7 | ["Rice","Good", 12, 1 , "Made in China"], 8 | callback) 9 | }; 10 | 11 | exports.down = function(db, callback) { 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20141021180444-create-user.js: -------------------------------------------------------------------------------- 1 | var dbm = require('db-migrate'); 2 | var type = dbm.dataType; 3 | 4 | exports.up = function(db, callback) { 5 | db.insert('user', 6 | ['name', 'password', 'email', 'role','enabled'], 7 | ["admin","admin", "admin@phodal.com", 'user', 1], 8 | callback) 9 | }; 10 | 11 | exports.down = function(db, callback) { 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /web/js/Model/RiceModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define(['backbone', "config"], function(Backbone, config) { 4 | var RiceModel = Backbone.Model.extend({}); 5 | var Rices = Backbone.Collection.extend({ 6 | model: RiceModel, 7 | url: config.localhost + '/all/rice', 8 | parse: function (data) { 9 | return data; 10 | } 11 | }); 12 | return Rices; 13 | }); -------------------------------------------------------------------------------- /migrations/20141023042211-update-user.js: -------------------------------------------------------------------------------- 1 | var dbm = require('db-migrate'); 2 | var type = dbm.dataType; 3 | 4 | exports.up = function(db, callback) { 5 | var updateAdminSql = 'UPDATE USER SET PASSWORD = ? WHERE NAME = "admin"'; 6 | 7 | db.runSql(updateAdminSql, ['$2a$10$Vq/cxG7Nxai6SN5U4k.tQOrVySVf840wcmwFkbhXd5u9DQj5fwUiO'], callback); 8 | }; 9 | 10 | exports.down = function(db, callback) { 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /web/js/Model/UserLogin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define(["backbone", "config"], function(Backbone, config) { 4 | var LoginAccount = Backbone.Model.extend({ 5 | defaults: { 6 | name: null, 7 | password: null 8 | }, 9 | url: function () { 10 | return config.localhost + '/login/user'; 11 | } 12 | }); 13 | 14 | return LoginAccount; 15 | }); -------------------------------------------------------------------------------- /web/js/Model/CreateAccountModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define(["backbone", "config"], function(Backbone, config) { 4 | var CreateAccount = Backbone.Model.extend({ 5 | defaults: { 6 | name: null, 7 | email: null, 8 | password: null 9 | }, 10 | url: function() { 11 | return config.localhost + '/account/create'; 12 | } 13 | }); 14 | 15 | return CreateAccount; 16 | }); 17 | -------------------------------------------------------------------------------- /web/templates/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /web/js/Model/CreateRiceModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define(["backbone", "config"], function(Backbone, config) { 4 | var CreateRice = Backbone.Model.extend({ 5 | defaults: { 6 | name: null, 7 | type: null, 8 | price: null, 9 | quantity: null, 10 | description: null 11 | }, 12 | url: function() { 13 | return config.localhost + '/rice/create'; 14 | } 15 | }); 16 | 17 | return CreateRice; 18 | }); -------------------------------------------------------------------------------- /migrations/20141021173743-add-user.js: -------------------------------------------------------------------------------- 1 | var dbm = require('db-migrate'); 2 | var type = dbm.dataType; 3 | 4 | exports.up = function(db, callback) { 5 | db.createTable('user', { 6 | id: { type: 'int', primaryKey: true }, 7 | name: type.STRING, 8 | password: type.STRING, 9 | email: type.STRING, 10 | role: type.STRING, 11 | enabled: type.BOOLEAN 12 | }, callback); 13 | }; 14 | 15 | exports.down = function(db, callback) { 16 | db.dropTable('user', callback); 17 | }; 18 | -------------------------------------------------------------------------------- /migrations/20141021173924-add-rice.js: -------------------------------------------------------------------------------- 1 | var dbm = require('db-migrate'); 2 | var type = dbm.dataType; 3 | 4 | exports.up = function(db, callback) { 5 | db.createTable('rice', { 6 | id: { type: 'int', primaryKey: true }, 7 | name: type.STRING, 8 | type: type.STRING, 9 | price: type.DECIMAL, 10 | quantity: type.DECIMAL, 11 | description: type.STRING 12 | }, callback); 13 | }; 14 | 15 | exports.down = function(db, callback) { 16 | db.dropTable('rice', callback); 17 | }; 18 | -------------------------------------------------------------------------------- /web/test/spec/UserLoginSpec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'js/Model/CreateAccountModel' 3 | ], function(CreateAccount) { 4 | 'use strict'; 5 | 6 | beforeEach(function() { 7 | this.user = new CreateAccount({ 8 | name: "phodal", 9 | password: "password" 10 | }); 11 | }); 12 | 13 | describe("User Login", function() { 14 | it("should return user name", function() { 15 | expect(this.user.get('name')).toEqual("phodal"); 16 | }); 17 | it("should return user password", function() { 18 | expect(this.user.get('password')).toEqual("password"); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /web/templates/homepage_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{#.}} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{/.}} 23 |
NamePriceDescriptionTypeQuantity
{{name}}{{price}}{{description}}{{type}}{{quantity}}
-------------------------------------------------------------------------------- /web/test/spec/CreateAccountViewSpec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'js/CreateAccountView', 4 | 'jasmine-jquery' 5 | ], function($, CreateAccountView) { 6 | var view; 7 | 8 | beforeEach(function() { 9 | view = new CreateAccountView(); 10 | }); 11 | 12 | describe("when view is constructing", function() { 13 | it("should return user name", function() { 14 | expect(view).toBeDefined(); 15 | }); 16 | }); 17 | describe("should have value", function() { 18 | it('should return the value of #content', function () { 19 | expect(view.$el.find('#content')).toHaveValue(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /server/mapper/db_prototype.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('sqlite3').verbose(); 2 | var _ = require('underscore'); 3 | 4 | function DBPrototype() { 5 | 'use strict'; 6 | return; 7 | } 8 | 9 | DBPrototype.prototype.errorHandler = function (err) { 10 | 'use strict'; 11 | if (err !== null) { 12 | throw err; 13 | } 14 | }; 15 | 16 | DBPrototype.prototype.basic = function(sql, db_callback){ 17 | 'use strict'; 18 | var db = new sqlite3.Database("dev.db"); 19 | db.all(sql, function (err, rows) { 20 | DBPrototype.prototype.errorHandler(err); 21 | db.close(); 22 | db_callback(rows); 23 | }); 24 | }; 25 | 26 | module.exports = DBPrototype; -------------------------------------------------------------------------------- /web/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: '/', 3 | paths: { 4 | 'text': 'lib/text/text', 5 | jquery: 'lib/jquery/dist/jquery.min', 6 | json: 'lib/require/json', 7 | router: 'router', 8 | underscore: 'lib/underscore/underscore', 9 | mustache: 'lib/mustache/mustache', 10 | backbone: 'lib/backbone/backbone', 11 | "jquery-cookie": "lib/jquery.cookie/jquery.cookie", 12 | "config": 'config' 13 | }, 14 | shim: { 15 | "jquery-cookie": ["jquery"], 16 | underscore: { 17 | exports: '_' 18 | } 19 | } 20 | }); 21 | 22 | require(['app'], function(App){ 23 | "use strict"; 24 | App.initialize(); 25 | }); -------------------------------------------------------------------------------- /web/test/spec/routerSpec.js: -------------------------------------------------------------------------------- 1 | //define([ 2 | // 'sinon', 3 | // 'router', 4 | // 'jasmine-jquery' 5 | //], function(sinon, AppRouter) { 6 | // 'use strict'; 7 | // 8 | // beforeEach(system() { 9 | // //this.router = new AppRouter(); 10 | // //this.routeSpy = sinon.spy(); 11 | // }); 12 | // 13 | // describe("Router Test", system() { 14 | // it("should request the url and fetch", system () { 15 | // //this.router.bind("route:index", this.routeSpy); 16 | // //this.router.navigate("", true); 17 | // //expect(this.routeSpy).toHaveBeenCalledOnce(); 18 | // //expect(this.routeSpy).toHaveBeenCalledWith(); 19 | // }); 20 | // }); 21 | //}); 22 | -------------------------------------------------------------------------------- /server/mapper/db_helper.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | function db_helper() { 4 | 'use strict'; 5 | return; 6 | } 7 | 8 | db_helper.prototype.getValue = function (account) { 9 | 'use strict'; 10 | var str = ""; 11 | _.each(account, function (key) { 12 | str += "'" + key + "',"; 13 | }); 14 | str = str.substring(0, str.length - 1); 15 | return str; 16 | }; 17 | 18 | db_helper.prototype.getKey = function (account) { 19 | 'use strict'; 20 | var str = ""; 21 | _.each(account, function (key, value) { 22 | str += value + ","; 23 | }); 24 | str = str.substring(0, str.length - 1); 25 | return str; 26 | }; 27 | 28 | module.exports = db_helper; -------------------------------------------------------------------------------- /web/test/spec/LoginViewSpec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'js/LoginView', 4 | 'jasmine-jquery' 5 | ], function($, LoginView) { 6 | var view; 7 | 8 | beforeEach(function() { 9 | view = new LoginView(); 10 | view.$el.find('#username').val('admin').trigger('change'); 11 | view.$el.find('#password').val('admin').trigger('change'); 12 | }); 13 | 14 | describe("when view is constructing", function() { 15 | it("should return user name", function() { 16 | expect(view).toBeDefined(); 17 | view.$el.find('#login').trigger('click'); 18 | }); 19 | }); 20 | describe("should have value", function() { 21 | it('should return the value of #content', function () { 22 | expect(view.$el.find('#content')).toHaveValue(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /web/js/Model/UserModel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define(['backbone', "config"], function(Backbone, config) { 4 | var User = Backbone.Model.extend({ 5 | initialize : function(username) { 6 | this.username = username; 7 | }, 8 | defaults:{ 9 | username:null 10 | } 11 | }); 12 | 13 | var UserModel = Backbone.Collection.extend({ 14 | default: { 15 | username:null 16 | }, 17 | initialize : function(models, options) { 18 | this.user = new User(this.get('username')); 19 | this.username = options.username; 20 | }, 21 | model: User, 22 | urlRoot: config.localhost + '/account/name/', 23 | "url": function () { 24 | return this.urlRoot + this.username; 25 | } 26 | }); 27 | 28 | return UserModel; 29 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freerice", 3 | "version": "0.0.2", 4 | "homepage": "https://github.com/phodal/freerice", 5 | "authors": [ 6 | "Phodal HUANG " 7 | ], 8 | "description": "free rice", 9 | "main": "web/main.js", 10 | "keywords": [ 11 | "cms", 12 | "mobile", 13 | "cms", 14 | "free", 15 | "rice" 16 | ], 17 | "dependencies": { 18 | "backbone" : "1.1.2", 19 | "jquery": "2.1.1", 20 | "jquery.cookie": "1.4.1", 21 | "mustache": "0.8.2", 22 | "requirejs": "2.1.14", 23 | "underscore": "1.6.0", 24 | "text": "2.0.12", 25 | "sinon": "1.11.1", 26 | "jasmine-jquery": "2.0.5", 27 | "pure": "0.5.0" 28 | }, 29 | "license": "MIT", 30 | "ignore": [ 31 | "**/.*", 32 | "node_modules", 33 | "bower_components", 34 | ".*", 35 | "CHANGELOG.md", 36 | "component.json", 37 | "package.json", 38 | "_includes", 39 | "_layouts" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /server/service/account_service.js: -------------------------------------------------------------------------------- 1 | var AccountMapper = require("./../mapper/account_mapper"); 2 | var db = new AccountMapper(); 3 | 4 | function DBService() { 5 | 'use strict'; 6 | return; 7 | } 8 | 9 | DBService.prototype.getAccountById = function (req, res, next) { 10 | 'use strict'; 11 | var userId = req.params.id; 12 | db.getAccountById(userId, function (result) { 13 | res.send(result); 14 | next(); 15 | }); 16 | }; 17 | 18 | DBService.prototype.getAccountByName = function (req, res, next) { 19 | 'use strict'; 20 | var userName = req.params.name; 21 | db.getAccountByName(userName, function (result) { 22 | res.send(result); 23 | next(); 24 | }); 25 | }; 26 | 27 | DBService.prototype.findAllAccount = function (req, res, next) { 28 | 'use strict'; 29 | db.findAllAccount(function (result) { 30 | res.send(result); 31 | next(); 32 | }); 33 | }; 34 | 35 | module.exports = DBService; -------------------------------------------------------------------------------- /server/service/service_helper.js: -------------------------------------------------------------------------------- 1 | var bcrypt = require('bcrypt'); 2 | var _ = require('underscore'); 3 | 4 | function ServiceHelper() { 5 | 'use strict'; 6 | return; 7 | } 8 | 9 | ServiceHelper.prototype.verifyRiceInput = function (rice) { 10 | 'use strict'; 11 | return rice.name === undefined || rice.type === undefined || rice.price === undefined || rice.quantity === undefined ||rice.description === undefined; 12 | }; 13 | 14 | ServiceHelper.prototype.verifyAccountInput = function (account) { 15 | 'use strict'; 16 | return account.name === undefined || account.password === undefined || account.email === undefined; 17 | }; 18 | 19 | ServiceHelper.prototype.encryptPassword = function encryptPassword(password, cb) { 20 | 'use strict'; 21 | bcrypt.genSalt(10, function (err, salt) { 22 | bcrypt.hash(password, salt, function(err, hash) { 23 | cb(null, hash); 24 | }); 25 | }); 26 | }; 27 | 28 | module.exports = ServiceHelper; -------------------------------------------------------------------------------- /web/js/lib/Rice.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'underscore', 4 | 'mustache', 5 | 'js/lib/UserSession', 6 | '../Model/CreateRiceModel' 7 | ],function($, _, Mustache, UserSession, CreateRice){ 8 | 'use strict'; 9 | function Rice(){ 10 | 11 | } 12 | Rice.prototype.create = function(riceObject) { 13 | var createRice = new CreateRice({ 14 | name: riceObject.name, 15 | type: riceObject.type, 16 | price: riceObject.price, 17 | quantity: riceObject.quantity, 18 | description: riceObject.description 19 | }); 20 | 21 | createRice.save({}, { 22 | success: function(model, response) { 23 | if(response.status === "success"){ 24 | console.log("createRice success"); 25 | } else { 26 | 27 | } 28 | }, 29 | error: function(model, response) { 30 | } 31 | }); 32 | }; 33 | 34 | return Rice; 35 | }); -------------------------------------------------------------------------------- /web/test/spec/UserSessionSpec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'js/lib/UserSession' 3 | ], function(UserSession) { 4 | 'use strict'; 5 | 6 | describe("User Session", function() { 7 | it("should return default name,accessToken be null", function() { 8 | expect(UserSession.defaults.userName).toBe(null); 9 | expect(UserSession.defaults.accessToken).toBe(null); 10 | }); 11 | 12 | it("should be authenticated after save session", function() { 13 | UserSession.save({ 14 | name: "test", 15 | accessToken: "test" 16 | }); 17 | expect(UserSession.authenticated()).toBe(true); 18 | }); 19 | 20 | it("should can load session after save session", function() { 21 | UserSession.save({ 22 | name: "test", 23 | accessToken: "test" 24 | }); 25 | spyOn(UserSession, 'load'); 26 | UserSession.initialize(); 27 | expect(UserSession.load).toHaveBeenCalled(); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /web/templates/create.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/phodal/freerice.png)](https://travis-ci.org/phodal/freerice) 2 | [![Code Climate](https://codeclimate.com/github/phodal/freerice/badges/gpa.svg)](https://codeclimate.com/github/phodal/freerice) 3 | [![Test Coverage](https://codeclimate.com/github/phodal/freerice/badges/coverage.svg)](https://codeclimate.com/github/phodal/freerice) 4 | [![Dependencies](https://david-dm.org/phodal/freerice.svg?style=flat)](https://david-dm.org/phodal/freerice.svg?style=flat0) 5 | 6 | #Moqi Mobiel CMS (Version: freerice) 7 | 8 | ##Front-end DEPENDENCIES 9 | - Backbone 10 | - RequireJS 11 | - Underscore 12 | - Mustache 13 | - Pure CSS 14 | 15 | ##Back-end DEPENDENCIES 16 | 17 | - RESTify 18 | 19 | ##Test DEPENDENCIES 20 | 21 | - Jasmine 22 | - Chai 23 | - Sinon 24 | - Mocha 25 | - Jasmine-jquery 26 | 27 | ##Quick Start 28 | 29 | 1. Run ``npm install`` 30 | 2. Run ``db-migrate up`` 31 | 3. Run ``node server/app.js`` (only:production) 32 | 33 | ## License 34 | 35 | © 2014 [Phodal Huang](http://www.phodal.com). This code is distributed under the MIT license. 36 | 37 | -------------------------------------------------------------------------------- /web/js/CreateAccountView.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'backbone', 4 | 'underscore', 5 | 'mustache', 6 | 'text!/templates/create.html', 7 | 'js/lib/User' 8 | ],function($, Backbone, _, Mustache, createAccountTemplate, User){ 9 | 'use strict'; 10 | var user = new User(); 11 | 12 | var LoginView = Backbone.View.extend ({ 13 | el: $("#content"), 14 | 15 | initialize: function(){ 16 | }, 17 | events: { 18 | "click #createAccount": "create_account" 19 | }, 20 | create_account: function(event){ 21 | event.preventDefault(); 22 | var userInfo = { 23 | name: $('#fld_name').val(), 24 | password: $('#fld_password').val(), 25 | email: $('#fld_email').val() 26 | }; 27 | user.create(userInfo); 28 | window.app.navigate('login', true); 29 | }, 30 | render: function(){ 31 | this.$el.html(Mustache.to_html(createAccountTemplate, {data:"data"})); 32 | } 33 | }); 34 | 35 | return LoginView; 36 | }); -------------------------------------------------------------------------------- /web/js/HomeView.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'backbone', 4 | 'underscore', 5 | 'mustache', 6 | 'text!/templates/homepage_detail.html', 7 | 'js/Model/RiceModel' 8 | ],function($, Backbone, _, Mustache, homepageTemplate, Rices){ 9 | 'use strict'; 10 | var HomeView = Backbone.View.extend ({ 11 | el: $("#content"), 12 | 13 | initialize: function(){ 14 | var that = this; 15 | this.collection = new Rices(); 16 | this.collection.fetch({ 17 | success: function(){ 18 | that.render(); 19 | } 20 | }); 21 | }, 22 | render: function(){ 23 | this.$el.find("#content").remove(); 24 | var result = []; 25 | _.each(this.collection.models, function(model){ 26 | if(model.attributes.quantity !== 0 ){ 27 | result.push(model.attributes); 28 | } 29 | }); 30 | this.$el.html(Mustache.to_html(homepageTemplate, result)); 31 | } 32 | }); 33 | 34 | return HomeView; 35 | }); -------------------------------------------------------------------------------- /server/mapper/rice_mapper.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('sqlite3').verbose(); 2 | var _ = require('underscore'); 3 | var DBHelper = require('./db_helper'); 4 | var db_helper = new DBHelper(); 5 | var DBPrototype = require('./db_prototype'); 6 | 7 | function RiceMapper() { 8 | 'use strict'; 9 | return; 10 | } 11 | 12 | RiceMapper.prototype = new DBPrototype(); 13 | 14 | RiceMapper.prototype.findAllRice = function (callback) { 15 | 'use strict'; 16 | var sql = "SELECT * FROM rice"; 17 | RiceMapper.prototype.basic(sql, callback); 18 | }; 19 | 20 | RiceMapper.prototype.createRice = function (rice, callback) { 21 | 'use strict'; 22 | 23 | var sql = "INSERT OR REPLACE INTO RICE (" + db_helper.getKey(rice) + ") VALUES (" + db_helper.getValue(rice) + ");"; 24 | 25 | var db = new sqlite3.Database("dev.db"); 26 | db.all(sql, function (err, rows) { 27 | RiceMapper.prototype.errorHandler(err); 28 | db.close(); 29 | if(_.isEmpty(rows)){ 30 | rows = { 31 | "status": "success" 32 | }; 33 | } 34 | callback(rows); 35 | }); 36 | }; 37 | 38 | module.exports = RiceMapper; -------------------------------------------------------------------------------- /web/js/lib/UserSession.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'underscore', 4 | 'backbone', 5 | 'jquery-cookie' 6 | ],function($, _, Backbone){ 7 | 'use strict'; 8 | var UserSession = Backbone.Model.extend({ 9 | defaults: { 10 | 'accessToken': null, 11 | "userName": null 12 | }, 13 | initialize: function(){ 14 | this.load(); 15 | }, 16 | authenticated: function(){ 17 | return !_.isEmpty($.cookie('accessToken')) && $.cookie('accessToken')!== null ; 18 | }, 19 | principal: function() { 20 | return $.cookie('name'); 21 | }, 22 | save: function(authHash){ 23 | $.cookie('name', authHash.name, { expires: 0.5 }); 24 | $.cookie('accessToken', authHash.accessToken, { expires: 0.5 }); 25 | }, 26 | remove: function(){ 27 | $.removeCookie('name'); 28 | $.removeCookie('accessToken'); 29 | }, 30 | load: function(){ 31 | this.userName = $.cookie('name'); 32 | this.accessToken = $.cookie('accessToken'); 33 | } 34 | }); 35 | 36 | return new UserSession(); 37 | }); -------------------------------------------------------------------------------- /web/js/UserProfileView.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'backbone', 4 | 'underscore', 5 | 'mustache', 6 | 'text!/templates/userProfile.html', 7 | 'js/Model/UserModel', 8 | 'js/lib/UserSession' 9 | ],function($, Backbone, _, Mustache, userProfileTemplate, UserModel, UserSession){ 10 | 'use strict'; 11 | var HomeView = Backbone.View.extend ({ 12 | el: $("#content"), 13 | 14 | initialize: function(){ 15 | var that = this; 16 | this.collection = new UserModel([], {username: UserSession.principal()}); 17 | this.collection.fetch({ 18 | success: function(){ 19 | that.render(); 20 | } 21 | }); 22 | }, 23 | render: function(){ 24 | this.$el.find("#content").remove(); 25 | var result = []; 26 | _.each(this.collection.models, function(model){ 27 | if(model.username !== null ){ 28 | result.push(model.attributes); 29 | } 30 | }); 31 | this.$el.html(Mustache.to_html(userProfileTemplate, result)); 32 | } 33 | }); 34 | 35 | return HomeView; 36 | }); -------------------------------------------------------------------------------- /web/js/LoginView.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'underscore', 4 | 'mustache', 5 | 'text!/templates/login.html', 6 | 'js/lib/User', 7 | 'js/lib/UserSession' 8 | ],function($, _, Mustache, loginTemplate, User, UserSession){ 9 | 'use strict'; 10 | var user = new User(); 11 | 12 | var LoginView = Backbone.View.extend ({ 13 | el: $("#content"), 14 | 15 | initialize: function(){ 16 | 17 | }, 18 | events: { 19 | "click #login": "login" 20 | }, 21 | login: function(event){ 22 | event.preventDefault(); 23 | var userInfo = { 24 | name: $('#username').val(), 25 | password: $('#password').val() 26 | }; 27 | user.login(userInfo, function(){ 28 | UserSession.save({ 29 | name: userInfo.name, 30 | accessToken: userInfo.name 31 | }); 32 | window.app.navigate('userProfile', true); 33 | }); 34 | }, 35 | render: function(){ 36 | this.$el.html(Mustache.to_html(loginTemplate, {data:"data"})); 37 | } 38 | }); 39 | 40 | return LoginView; 41 | }); -------------------------------------------------------------------------------- /server/service/rice_service.js: -------------------------------------------------------------------------------- 1 | var RiceMapper = require("./../mapper/rice_mapper"); 2 | var db = new RiceMapper(); 3 | var _ = require("underscore"); 4 | var restify = require("restify"); 5 | var ServiceHelper = require("./service_helper"); 6 | var serviceHelper = new ServiceHelper(); 7 | 8 | function Rice() { 9 | 'use strict'; 10 | return; 11 | } 12 | 13 | Rice.prototype.findAllRice = function (req, res, next) { 14 | 'use strict'; 15 | db.findAllRice(function (result) { 16 | res.send(result); 17 | next(); 18 | }); 19 | }; 20 | 21 | Rice.prototype.create = function (req, res, next) { 22 | 'use strict'; 23 | 24 | var rice = req.params; 25 | 26 | if (serviceHelper.verifyRiceInput(rice)) { 27 | return next(new restify.InvalidArgumentError('String must be supplied')); 28 | } 29 | 30 | db.createRice(rice, function(result) { 31 | if (result.status === "success") { 32 | res.send({status: "success"}); 33 | next(); 34 | } else { 35 | result = _.extend(result, {status: "fail"}); 36 | res.send(result); 37 | next(); 38 | } 39 | }); 40 | }; 41 | 42 | module.exports = Rice; -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Free Rice CMS 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Free Rice CMS 16 | 22 | 23 |
24 | 25 |
26 |
27 | 28 | 33 | 34 | -------------------------------------------------------------------------------- /web/test/spec/RiceModelSpec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'sinon', 3 | 'js/Model/RiceModel' 4 | ], function(sinon, Rices) { 5 | 'use strict'; 6 | var data = {"id":1,"name":"Rice","type":"Good","price":12,"quantity":1,"description":"Made in China"}; 7 | 8 | beforeEach(function() { 9 | this.server = sinon.fakeServer.create(); 10 | this.rices = new Rices(); 11 | this.server.respondWith( 12 | "GET", 13 | "http://0.0.0.0:8080/all/rice", 14 | [ 15 | 200, 16 | {"Content-Type": "application/json"}, 17 | JSON.stringify(data) 18 | ] 19 | ); 20 | }); 21 | 22 | afterEach(function() { 23 | this.server.restore(); 24 | }); 25 | 26 | describe("Collection Test", function() { 27 | it("should request the url and fetch", function () { 28 | this.rices.fetch(); 29 | expect(this.server.requests.length) 30 | .toEqual(1); 31 | expect(this.server.requests[0].method) 32 | .toEqual("GET"); 33 | expect(this.server.requests[0].url) 34 | .toEqual("http://0.0.0.0:8080/all/rice"); 35 | }); 36 | 37 | it("should get data from the url", function() { 38 | this.rices.fetch(); 39 | this.server.respond(); 40 | var result = JSON.parse(JSON.stringify(this.rices.models[0])); 41 | expect(result["id"]) 42 | .toEqual(1); 43 | expect(result["price"]) 44 | .toEqual(12); 45 | expect(result) 46 | .toEqual(data); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/services/rice_test.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var restify = require('restify'); 3 | 4 | var client = restify.createJsonClient({ 5 | url: 'http://127.0.0.1:8080/', 6 | version: '*' 7 | }); 8 | 9 | describe('Create Rice Test', function() { 10 | it('should return create rice success', function (done) { 11 | client.post('/rice/create', { 12 | name: "rice name", 13 | type: "rice type", 14 | price: 12, 15 | quantity: 12, 16 | description: "description" 17 | }, function (err, req, res, data) { 18 | if (err) { 19 | throw new Error(err); 20 | } 21 | else { 22 | if (data.status != "success") { 23 | throw new Error('create failed'); 24 | } 25 | res.destroy(); 26 | done(); 27 | } 28 | }); 29 | }); 30 | 31 | it('should return String must be supplied when without type', function (done) { 32 | client.post('/rice/create', { 33 | name: "rice name", 34 | price: 12, 35 | quantity: 12, 36 | description: "description" 37 | }, function (err, req, res, data) { 38 | if (err.message === "String must be supplied") { 39 | res.destroy(); 40 | done(); 41 | } 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/services/authenticate_test.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var restify = require('restify'); 3 | 4 | var client = restify.createJsonClient({ 5 | url: 'http://127.0.0.1:8080/', 6 | version: '*' 7 | }); 8 | 9 | describe('Authenticate Test', function() { 10 | it('should return login success', function(done) { 11 | client.post('/login/user', { name: 'admin', password: "admin" }, function(err, req, res, data) { 12 | if (err) { 13 | throw new Error(err); 14 | } 15 | else { 16 | if (data.status != "success") { 17 | throw new Error('login failed'); 18 | } 19 | done(); 20 | } 21 | }); 22 | }); 23 | 24 | it('should return login failed', function(done) { 25 | client.post('/login/user', { name: 'admin', password: "noadmin" }, function(err, req, res, data) { 26 | if (err) { 27 | throw new Error(err); 28 | } 29 | else { 30 | if (data.status == "fail") { 31 | done(); 32 | } 33 | } 34 | }); 35 | }); 36 | 37 | it('should return login failed when lost user name', function(done) { 38 | client.post('/login/user', { password: "noadmin" }, function(err, req, res, data) { 39 | if (err.message = "Name must be supplied") { 40 | done(); 41 | } 42 | }); 43 | }); 44 | 45 | }); -------------------------------------------------------------------------------- /web/lib/require/async.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * RequireJS plugin for async dependency load like JSONP and Google Maps 3 | * Author: Miller Medeiros 4 | * Version: 0.1.1 (2011/11/17) 5 | * Released under the MIT license 6 | */ 7 | define(function(){ 8 | 9 | var DEFAULT_PARAM_NAME = 'callback', 10 | _uid = 0; 11 | 12 | function injectScript(src){ 13 | var s, t; 14 | s = document.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = src; 15 | t = document.getElementsByTagName('script')[0]; t.parentNode.insertBefore(s,t); 16 | } 17 | 18 | function formatUrl(name, id){ 19 | var paramRegex = /!(.+)/, 20 | url = name.replace(paramRegex, ''), 21 | param = (paramRegex.test(name))? name.replace(/.+!/, '') : DEFAULT_PARAM_NAME; 22 | url += (url.indexOf('?') < 0)? '?' : '&'; 23 | return url + param +'='+ id; 24 | } 25 | 26 | function uid() { 27 | _uid += 1; 28 | return '__async_req_'+ _uid +'__'; 29 | } 30 | 31 | return{ 32 | load : function(name, req, onLoad, config){ 33 | if(config.isBuild){ 34 | onLoad(null); //avoid errors on the optimizer 35 | }else{ 36 | var id = uid(); 37 | //create a global variable that stores onLoad so callback 38 | //function can define new module after async load 39 | window[id] = onLoad; 40 | injectScript(formatUrl(name, id)); 41 | } 42 | } 43 | }; 44 | }); 45 | -------------------------------------------------------------------------------- /test/mapper/db_mapper_test.js: -------------------------------------------------------------------------------- 1 | var AccountMapper =require("../../server/mapper/account_mapper"); 2 | var sqlite = new AccountMapper(); 3 | 4 | var RiceMapper =require("../../server/mapper/rice_mapper"); 5 | var riceDB = new RiceMapper(); 6 | 7 | 8 | describe('DB Mapper Throw Error Test', function() { 9 | it('should throw error on errorHandler', function () { 10 | expect(sqlite.errorHandler()).to.throw(); 11 | }); 12 | }); 13 | 14 | describe("Mapper Test", function () { 15 | describe('Create Account', function() { 16 | var account = { name: "user", password: "user", email: "newuser@phodal.com" }; 17 | 18 | it('should return user exist when create account again', function(done) { 19 | sqlite.createAccount(account , function(){ 20 | sqlite.createAccount(account , function(result){ 21 | if(result.error === "user exist"){ 22 | done(); 23 | } 24 | }); 25 | }); 26 | }); 27 | }); 28 | 29 | describe('Create Rice', function() { 30 | it('should return the create rice', function(done) { 31 | var rice = { name: "zero", type: "user", quantity: 23, price:23, description: "newuser@phodal.com" }; 32 | 33 | riceDB.createRice(rice , function(result){}); 34 | riceDB.findAllRice(function(result){ 35 | if(result[0].description = "newuser@phodal.com"){ 36 | done(); 37 | } 38 | }); 39 | }); 40 | }); 41 | 42 | }); -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var restify = require('restify'); 2 | var server = restify.createServer(); 3 | 4 | var Authenticate = require('./service/authenticate'); 5 | var auth = new Authenticate(); 6 | 7 | var DBService = require('./service/account_service'); 8 | var get_response = new DBService(); 9 | 10 | var Rice = require('./service/rice_service'); 11 | var rice = new Rice(); 12 | 13 | server.use(restify.gzipResponse()); 14 | server.use(restify.bodyParser()); 15 | server.use(restify.acceptParser(['json', 'application/json'])); 16 | server.use( 17 | function crossOrigin(req,res,next){ 18 | 'use strict'; 19 | res.header("Access-Control-Allow-Origin", "*"); 20 | res.header("Access-Control-Allow-Headers", "X-Requested-With"); 21 | return next(); 22 | } 23 | ); 24 | 25 | server.get('/all/account', get_response.findAllAccount); 26 | server.get('/account/id/:id', get_response.getAccountById); 27 | server.get('/account/name/:name', get_response.getAccountByName); 28 | 29 | server.post('/account/create', auth.create); 30 | server.post('/login/user', auth.login); 31 | 32 | server.get('/all/rice', rice.findAllRice); 33 | server.post('/rice/create', rice.create); 34 | 35 | server.get('/', restify.serveStatic({ 36 | directory: 'web', 37 | default: 'index.html' 38 | })); 39 | 40 | server.get(/\/?.*/, restify.serveStatic({ 41 | directory: 'web' 42 | })); 43 | 44 | 45 | server.listen(8080, function () { 46 | 'use strict'; 47 | console.log('%s listening at %s', server.name, server.url); 48 | }); 49 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | "use strict"; 3 | 4 | grunt.initConfig({ 5 | 6 | pkg: grunt.file.readJSON('package.json') 7 | , jasmine: { 8 | src: "web/test/lib/*.js" 9 | , options: { 10 | specs: "web/test/spec/*Spec.js", 11 | template: require('grunt-template-jasmine-requirejs'), 12 | templateOptions:{ 13 | requireConfig: { 14 | baseUrl: './web', 15 | paths: { 16 | 'text': 'lib/text/text', 17 | jquery: 'lib/jquery/dist/jquery.min', 18 | json: 'lib/require/json', 19 | router: 'router', 20 | underscore: 'lib/underscore/underscore', 21 | mustache: 'lib/mustache/mustache', 22 | backbone: 'lib/backbone/backbone', 23 | "jquery-cookie": "lib/jquery.cookie/jquery.cookie", 24 | "jasmine-jquery": "lib/jasmine-jquery/lib/jasmine-jquery", 25 | sinon: "lib/sinon/lib/sinon" 26 | }, 27 | shim: { 28 | "jquery-cookie": ["jquery"], 29 | backbone: { 30 | exports:"Backbone" 31 | }, 32 | underscore: { 33 | exports: '_' 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | 42 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 43 | grunt.registerTask('server', 'Start a custom web server.', function() { 44 | grunt.log.writeln('Starting web server on port 8080.'); 45 | require('./server/app.js'); 46 | }); 47 | grunt.registerTask('default', ['server','jasmine']); 48 | }; 49 | -------------------------------------------------------------------------------- /web/templates/managePage.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {{#.}} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {{/.}} 60 |
NamePriceDescriptionTypeQuantity
{{name}}{{price}}{{description}}{{type}}{{quantity}}
-------------------------------------------------------------------------------- /web/js/lib/User.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'underscore', 4 | 'mustache', 5 | 'js/lib/UserSession', 6 | '../Model/CreateAccountModel', 7 | '../Model/UserLogin' 8 | ],function($, _, Mustache, UserSession, CreateAccount, LoginAccount){ 9 | 'use strict'; 10 | function User(){ 11 | 12 | } 13 | User.prototype.login = function(userObject, callback) { 14 | var login = new LoginAccount({ 15 | name: userObject.name, 16 | password: userObject.password 17 | }); 18 | 19 | login.save({}, { 20 | success: function(model, response) { 21 | if(response.status === "success"){ 22 | var name = userObject.name; 23 | UserSession.save({ 24 | "name": name, 25 | "accessToken": response.accessToken 26 | }); 27 | callback(); 28 | } else { 29 | } 30 | }, 31 | error: function(model, response) { 32 | callback(model, response); 33 | } 34 | }); 35 | }; 36 | 37 | User.prototype.create = function(userObject) { 38 | var login = new CreateAccount({ 39 | name: userObject.name, 40 | email: userObject.email, 41 | password: userObject.password 42 | }); 43 | 44 | login.save({}, { 45 | success: function(model, response) { 46 | if(response.status === "success"){ 47 | console.log("login success"); 48 | } else { 49 | 50 | } 51 | }, 52 | error: function(model, response) { 53 | } 54 | }); 55 | }; 56 | 57 | return User; 58 | }); -------------------------------------------------------------------------------- /web/js/AdminView.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'jquery', 3 | 'backbone', 4 | 'underscore', 5 | 'mustache', 6 | 'text!/templates/managePage.html', 7 | 'js/lib/Rice', 8 | 'js/Model/RiceModel' 9 | ],function($, Backbone, _, Mustache, manageTemplate, Rice, RiceModel){ 10 | 'use strict'; 11 | var rice = new Rice(); 12 | 13 | var AdminView = Backbone.View.extend ({ 14 | el: $("#content"), 15 | 16 | initialize: function(){ 17 | var that = this; 18 | this.collection = new RiceModel(); 19 | this.collection.fetch({ 20 | success: function(){ 21 | that.render(); 22 | } 23 | }); 24 | }, 25 | events: { 26 | "click #createRice": "create_rice" 27 | }, 28 | create_rice: function(event){ 29 | event.preventDefault(); 30 | var riceInfo = { 31 | name: $('input[name="name"]').val(), 32 | type: $('input[name="type"]').val(), 33 | price: $('input[name="price"]').val(), 34 | quantity: $('input[name="quantity"]').val(), 35 | description: $('input[name="description"]').val() 36 | }; 37 | rice.create(riceInfo); 38 | window.app.navigate('admin', true); 39 | }, 40 | render: function(){ 41 | this.$el.find("#content").remove(); 42 | var result = []; 43 | _.each(this.collection.models, function(model){ 44 | if(model.attributes.quantity !== 0 ){ 45 | result.push(model.attributes); 46 | } 47 | }); 48 | this.$el.html(Mustache.to_html(manageTemplate, result)); 49 | } 50 | }); 51 | 52 | return AdminView; 53 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freerice", 3 | "version": "0.2.0", 4 | "description": "MoQi Mobile CMS.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:phodal/freerice.git" 8 | }, 9 | "keywords": [ 10 | "mobile cms", 11 | "cms", 12 | "mobile" 13 | ], 14 | "dependencies": { 15 | "underscore": "1.7.0", 16 | "restify": "2.8.3", 17 | "sqlite3": "3.0.2", 18 | "db-migrate": "0.7.1", 19 | "bcrypt": "~0.8.0", 20 | "bower": "1.3.12" 21 | }, 22 | "author": [ 23 | { 24 | "name": "Fengda HUANG", 25 | "url": "http://www.phodal.com/" 26 | } 27 | ], 28 | "license": "MIT", 29 | "devDependencies": { 30 | "bl": "~0.9.0", 31 | "chai": "~1.9.1", 32 | "codeclimate-test-reporter": "0.0.4", 33 | "istanbul": "0.3.2", 34 | "jslint": "0.6.4", 35 | "grunt": "0.4.5", 36 | "grunt-cli": "0.1.13", 37 | "grunt-contrib-connect":"0.8.0", 38 | "grunt-contrib-jasmine": "0.8.1", 39 | "grunt-template-jasmine-requirejs": "0.2.0", 40 | "grunt-exec": "0.4.5", 41 | "mocha": "~2.0.1", 42 | "pre-commit": "0.0.9", 43 | "request": "2.45.0", 44 | "requirejs": "^2.1.15", 45 | "sinon": "~1.11.1", 46 | "zombie": "2.0.1" 47 | }, 48 | "pre-commit": [ 49 | "jslint", 50 | "test" 51 | ], 52 | "scripts": { 53 | "start": "node server/app.js", 54 | "initdb": "rm dev.db;./node_modules/.bin/db-migrate up", 55 | "install": "bower install", 56 | "test": "grunt jasmine;rm dev.db;./node_modules/.bin/db-migrate up;istanbul cover node_modules/mocha/bin/_mocha -- -R spec 'test/**/*_test.js';", 57 | "jslint": "jslint --edition=latest 'server/*.js' 'server/mapper/*.js' 'server/service/*.js' web/js/**/*.js web/js/*.js web/*.js", 58 | "sendCoverage": "CODECLIMATE_REPO_TOKEN=62ad5b3cbab8aee9e9c32763f7097a0eb673cd2705442d4049b5c9464bb09850 codeclimate < coverage/lcov.info" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/service/authenticate.js: -------------------------------------------------------------------------------- 1 | var AccountMapper = require("./../mapper/account_mapper"); 2 | var _ = require("underscore"); 3 | var restify = require("restify"); 4 | var bcrypt = require('bcrypt'); 5 | var ServiceHelper = require("./service_helper"); 6 | 7 | var db = new AccountMapper(); 8 | var serviceHelper = new ServiceHelper(); 9 | 10 | function Authenticate() { 11 | 'use strict'; 12 | return; 13 | } 14 | 15 | Authenticate.prototype.login = function (req, res, next) { 16 | 'use strict'; 17 | var account = req.params; 18 | 19 | if (account.name === undefined || account.password === undefined) { 20 | return next(new restify.InvalidArgumentError('Name must be supplied')); 21 | } 22 | 23 | db.getPasswordByName(account.name, function (result) { 24 | bcrypt.compare(account.password, _.first(result).password, function(err, success) { 25 | if (success) { 26 | res.send({status: "success"}); 27 | } else { 28 | res.send({status: "fail"}); 29 | } 30 | next(); 31 | }); 32 | }); 33 | }; 34 | 35 | Authenticate.prototype.create = function (req, res, next) { 36 | 'use strict'; 37 | 38 | var account = req.params; 39 | if (serviceHelper.verifyAccountInput(account)) { 40 | return next(new restify.InvalidArgumentError('Name must be supplied')); 41 | } 42 | 43 | serviceHelper.encryptPassword(account.password, function (err, password) { 44 | if (err) { 45 | throw new Error(err); 46 | } 47 | account.password = password; 48 | db.createAccount(account, function (result) { 49 | if (result.status === "success") { 50 | res.send({status: "success"}); 51 | } else { 52 | result = _.extend(result, {status: "fail"}); 53 | res.send(result); 54 | } 55 | next(); 56 | }); 57 | }); 58 | }; 59 | 60 | 61 | module.exports = Authenticate; -------------------------------------------------------------------------------- /test/rest/rest_test.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var bl = require('bl'); 3 | 4 | describe('RESTful GET Test', function () { 5 | before(function() { 6 | 7 | }); 8 | 9 | it('should return 200 when start rest server', function (done) { 10 | http.get('http://localhost:8080/', function (res) { 11 | assert(200, res.statusCode); 12 | done(); 13 | }); 14 | }); 15 | 16 | it('should return at least one account on /all/account', function (done) { 17 | http.get('http://localhost:8080/all/account', function (res) { 18 | res.pipe(bl(function(err, data) { 19 | var json = JSON.parse(data)[0]; 20 | if(json.id === 1){ 21 | done(); 22 | } 23 | })); 24 | }); 25 | }); 26 | 27 | it('should return at least one account on /all/rice', function (done) { 28 | http.get('http://localhost:8080/all/rice', function (res) { 29 | res.pipe(bl(function(err, data) { 30 | var json = JSON.parse(data)[0]; 31 | if(json.id === 1){ 32 | done(); 33 | } 34 | })); 35 | }); 36 | }); 37 | 38 | it('should return the AdminCat when get by id = 1', function (done) { 39 | http.get('http://localhost:8080/account/id/1', function (res) { 40 | res.pipe(bl(function(err, data) { 41 | var json = JSON.parse(data)[0]; 42 | if(json.id === 1){ 43 | done(); 44 | } 45 | })); 46 | }); 47 | }); 48 | 49 | it('should return the AdminCat by account_name', function (done) { 50 | http.get('http://localhost:8080/account/name/admin', function (res) { 51 | res.pipe(bl(function(err, data) { 52 | var json = JSON.parse(data)[0]; 53 | if(json.name === 'admin'){ 54 | done(); 55 | } 56 | })); 57 | }); 58 | }); 59 | }); -------------------------------------------------------------------------------- /server/mapper/account_mapper.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('sqlite3').verbose(); 2 | var _ = require('underscore'); 3 | var DBHelper = require('./db_helper'); 4 | var db_helper = new DBHelper(); 5 | var DBPrototype = require('./db_prototype'); 6 | 7 | function AccountMapper() { 8 | 'use strict'; 9 | return; 10 | } 11 | 12 | AccountMapper.prototype = new DBPrototype(); 13 | 14 | AccountMapper.prototype.getAccountById = function (user_id, callback) { 15 | 'use strict'; 16 | var sql = "SELECT id,name,email FROM user WHERE id = " + user_id; 17 | AccountMapper.prototype.basic(sql, callback); 18 | }; 19 | 20 | AccountMapper.prototype.getPasswordByName = function (user_name, callback) { 21 | 'use strict'; 22 | var sql = "SELECT * FROM user WHERE name = '" + user_name + "' LIMIT 1"; 23 | AccountMapper.prototype.basic(sql, callback); 24 | }; 25 | 26 | AccountMapper.prototype.getAccountByName = function (user_name, callback) { 27 | 'use strict'; 28 | var sql = "SELECT id,name,email FROM user WHERE name = '" + user_name + "' LIMIT 1"; 29 | AccountMapper.prototype.basic(sql, callback); 30 | }; 31 | 32 | AccountMapper.prototype.findAllAccount = function (callback) { 33 | 'use strict'; 34 | var sql = "SELECT id,name,email FROM user"; 35 | AccountMapper.prototype.basic(sql, callback); 36 | }; 37 | 38 | AccountMapper.prototype.createAccount = function (account, callback) { 39 | 'use strict'; 40 | 41 | function createNewAccount() { 42 | var sql = "INSERT OR REPLACE INTO USER (" + db_helper.getKey(account) + ") VALUES (" + db_helper.getValue(account) + ");"; 43 | 44 | var db = new sqlite3.Database("dev.db"); 45 | db.all(sql, function (err, rows) { 46 | AccountMapper.prototype.errorHandler(err); 47 | db.close(); 48 | if (_.isEmpty(rows)) { 49 | rows = { 50 | "status": "success" 51 | }; 52 | } else { 53 | rows = {}; 54 | } 55 | callback(rows); 56 | }); 57 | } 58 | 59 | AccountMapper.prototype.getAccountByName(account.name, function(result){ 60 | if(!_.isEmpty(result)){ 61 | return callback({ 62 | "error": "user exist" 63 | }); 64 | } 65 | createNewAccount(); 66 | }); 67 | }; 68 | 69 | module.exports = AccountMapper; -------------------------------------------------------------------------------- /web/lib/require/json.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * RequireJS plugin for loading JSON files 3 | * - depends on Text plugin and it was HEAVILY "inspired" by it as well. 4 | * Author: Miller Medeiros 5 | * Version: 0.3.1 (2013/02/04) 6 | * Released under the MIT license 7 | */ 8 | define(['text'], function(text){ 9 | 10 | var CACHE_BUST_QUERY_PARAM = 'bust', 11 | CACHE_BUST_FLAG = '!bust', 12 | jsonParse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function')? JSON.parse : function(val){ 13 | return eval('('+ val +')'); //quick and dirty 14 | }, 15 | buildMap = {}; 16 | 17 | function cacheBust(url){ 18 | url = url.replace(CACHE_BUST_FLAG, ''); 19 | url += (url.indexOf('?') < 0)? '?' : '&'; 20 | return url + CACHE_BUST_QUERY_PARAM +'='+ Math.round(2147483647 * Math.random()); 21 | } 22 | 23 | //API 24 | return { 25 | 26 | load : function(name, req, onLoad, config) { 27 | if ( config.isBuild && (config.inlineJSON === false || name.indexOf(CACHE_BUST_QUERY_PARAM +'=') !== -1) ) { 28 | //avoid inlining cache busted JSON or if inlineJSON:false 29 | onLoad(null); 30 | } else { 31 | text.get(req.toUrl(name), function(data){ 32 | if (config.isBuild) { 33 | buildMap[name] = data; 34 | onLoad(data); 35 | } else { 36 | onLoad(jsonParse(data)); 37 | } 38 | }, 39 | onLoad.error, { 40 | accept: 'application/json' 41 | } 42 | ); 43 | } 44 | }, 45 | 46 | normalize : function (name, normalize) { 47 | //used normalize to avoid caching references to a "cache busted" request 48 | return (name.indexOf(CACHE_BUST_FLAG) === -1)? name : cacheBust(name); 49 | }, 50 | 51 | //write method based on RequireJS official text plugin by James Burke 52 | //https://github.com/jrburke/requirejs/blob/master/text.js 53 | write : function(pluginName, moduleName, write){ 54 | if(moduleName in buildMap){ 55 | var content = buildMap[moduleName]; 56 | write('define("'+ pluginName +'!'+ moduleName +'", function(){ return '+ content +';});\n'); 57 | } 58 | } 59 | 60 | }; 61 | }); 62 | -------------------------------------------------------------------------------- /web/router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | define([ 4 | 'jquery', 5 | 'underscore', 6 | 'backbone', 7 | 'js/HomeView.js', 8 | 'js/LoginView.js', 9 | 'js/CreateAccountView.js', 10 | 'js/lib/User.js', 11 | 'js/UserProfileView.js', 12 | 'js/lib/UserSession.js', 13 | 'js/lib/Logout.js', 14 | 'js/AdminView' 15 | ],function($, _, Backbone, HomeView, LoginView, CreateAccountView, User, UserProfileView, UserSession, Logout, AdminView){ 16 | var AppRouter = Backbone.Router.extend({ 17 | index: function(){ 18 | var homeView = new HomeView(); 19 | homeView.initialize(); 20 | }, 21 | createAccount: function(){ 22 | var createAccountView = new CreateAccountView(); 23 | createAccountView.render(); 24 | }, 25 | login: function(){ 26 | var loginView = new LoginView(); 27 | loginView.render(); 28 | }, 29 | logout: function(){ 30 | var logout = new Logout(); 31 | logout.logout(); 32 | this.navigate('/', true); 33 | }, 34 | admin: function(){ 35 | if(UserSession.authenticated() !== true ){ 36 | this.navigate('login', true); 37 | } 38 | var adminView = new AdminView(); 39 | adminView.render(); 40 | }, 41 | userProfile: function(){ 42 | if (UserSession.authenticated() === true) { 43 | var userProfileView = new UserProfileView(); 44 | userProfileView.render(); 45 | } else { 46 | this.navigate('account/login', true); 47 | Backbone.history.loadUrl(); 48 | } 49 | }, 50 | initialize: function() { 51 | var router = this, 52 | routes = [ 53 | [ /^.*$/, "index" ], 54 | [ "account/create", "createAccount" ], 55 | [ "account/login", "login" ], 56 | [ "account/logout", "logout" ], 57 | [ "userProfile", "userProfile" ], 58 | [ "admin", "admin" ] 59 | ]; 60 | 61 | _.each(routes, function(route) { 62 | router.route.apply(router,route); 63 | }); 64 | Backbone.history.stop(); 65 | Backbone.history.start(); 66 | } 67 | }); 68 | 69 | window.app = new AppRouter(); 70 | return AppRouter; 71 | }); -------------------------------------------------------------------------------- /test/services/create_account_test.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var restify = require('restify'); 3 | var request = require('request'); 4 | var _ = require('underscore'); 5 | 6 | var client = restify.createJsonClient({ 7 | url: 'http://127.0.0.1:8080/', 8 | version: '*' 9 | }); 10 | 11 | var client2 = restify.createJsonClient({ 12 | url: 'http://127.0.0.1:8080/', 13 | version: '*' 14 | }); 15 | 16 | describe('Create Account Test', function() { 17 | it('should return create success', function (done) { 18 | var randomNumber = _.random(0, 100); 19 | client2.post('/account/create', { 20 | name: 'user' + randomNumber, 21 | password: "user" + randomNumber, 22 | email: "user@phodal.com" 23 | }, function (err, req, res, data) { 24 | if (err) { 25 | throw new Error(err); 26 | } 27 | else { 28 | if (data.status !== "success") { 29 | throw new Error('create failed'); 30 | } else { 31 | res.destroy(); 32 | done(); 33 | } 34 | } 35 | }); 36 | }); 37 | 38 | it('should return Name must be supplied when without name', function (done) { 39 | client.post('/account/create', {password: "user", email: "user@phodal.com"}, function (err, req, res, data) { 40 | if (err.message === "Name must be supplied") { 41 | res.destroy(); 42 | done(); 43 | } 44 | }); 45 | }); 46 | }); 47 | 48 | describe('Visit Account Test', function() { 49 | it('should return 404 when visit the /account/create', function (done) { 50 | request('http://127.0.0.1:8080/account/create', function (error, response, body) { 51 | if (response.statusCode === 404) { 52 | done(); 53 | } 54 | }); 55 | }); 56 | 57 | it('should return 404 when visit the /account/create', function (done) { 58 | request('http://127.0.0.1:8080/account/create', function (error, response, body) { 59 | if (JSON.parse(body).code === "ResourceNotFound") { 60 | done(); 61 | } 62 | }); 63 | }); 64 | }); 65 | 66 | describe('Create Account Test', function() { 67 | it('should return Name must be supplied when name repeat', function(done) { 68 | request.post({url:'http://127.0.0.1:8080/account/create', 69 | form: { name: "admin", password: "user", email: "newuser@phodal.com" }}, 70 | function(err, httpResponse, body){ 71 | if (err) { 72 | throw new Error(err); 73 | } 74 | else { 75 | if (JSON.parse(body).error === "user exist") { 76 | done(); 77 | } 78 | } 79 | }); 80 | }); 81 | }); -------------------------------------------------------------------------------- /test/system/function_test.js: -------------------------------------------------------------------------------- 1 | var Browser = require('zombie'); 2 | var _ = require('underscore'); 3 | var website = "http://0.0.0.0:8080/"; 4 | 5 | describe("Function Test", function () { 6 | describe("Authenticate", function () { 7 | var browser = new Browser({ site: website }); 8 | 9 | it("should load the home page", function(done) { 10 | browser.visit('/', function (error) { 11 | assert.ifError(error); 12 | browser.assert.success(); 13 | }); 14 | done(); 15 | }); 16 | 17 | it('should be on login page', function(done) { 18 | browser.visit('#/account/login') 19 | .then(function() { 20 | assert.equal(browser.location.href, website + '#/account/login', 'It is not the Login page'); 21 | assert.ok(browser.success, 'It did not load successfully.'); 22 | }) 23 | .then(done, done); 24 | }); 25 | 26 | it('should redirect to user profile after login', function(done) { 27 | browser.visit('#/account/login') 28 | .then(function() { 29 | browser.fill('input[name=username]', 'admin'); 30 | browser.fill('input[name=password]', 'admin'); 31 | browser.pressButton("Sign in", function() { 32 | if(browser.location.href === website + '#userProfile'){ 33 | done(); 34 | } 35 | }); 36 | }); 37 | }); 38 | 39 | it('should redirect to homepage after logout', function(done) { 40 | browser.visit('#/account/logout') 41 | .then(function() { 42 | if(browser.location.href === website + '#'){ 43 | done(); 44 | } 45 | }); 46 | }); 47 | }); 48 | describe("Create Account", function () { 49 | var browser = new Browser({ site: website }); 50 | var randomNumber = _.random(1, 1000); 51 | var userName = 'user' + randomNumber; 52 | 53 | it("should redirect to login in page after create account", function () { 54 | browser.visit('#account/create') 55 | .then(function() { 56 | browser.fill('input[name=name]', userName); 57 | browser.fill('input[name=password]', userName); 58 | browser.fill('input[name=email]', userName + "@gmail.com"); 59 | browser.pressButton("Sign in", function() { 60 | if(browser.location.href === website + '#/account/login'){ 61 | done(); 62 | } 63 | }); 64 | }); 65 | }); 66 | 67 | it("should login with created user", function () { 68 | browser.visit('#/account/login') 69 | .then(function() { 70 | browser.fill('input[name=username]', userName); 71 | browser.fill('input[name=password]', userName); 72 | browser.pressButton("Sign in", function() { 73 | if(browser.location.href === website + '#userProfile'){ 74 | done(); 75 | } 76 | }); 77 | }); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /web/css/pricing.css: -------------------------------------------------------------------------------- 1 | /* 2 | * -- BASE STYLES -- 3 | * Most of these are inherited from Base, but I want to change a few. 4 | */ 5 | body { 6 | color: #526066; 7 | } 8 | 9 | h2, h3 { 10 | letter-spacing: 0.25em; 11 | text-transform: uppercase; 12 | font-weight: 600; 13 | } 14 | 15 | 16 | /* 17 | * -- Layout Styles -- 18 | */ 19 | .l-content { 20 | margin: 0 auto; 21 | } 22 | 23 | .l-box { 24 | padding: 0.5em 2em; 25 | } 26 | 27 | /* 28 | * -- MENU STYLES -- 29 | * Make the menu have a very faint box-shadow. 30 | */ 31 | .pure-menu { 32 | box-shadow: 0 1px 1px rgba(0,0,0, 0.10); 33 | } 34 | 35 | 36 | /* 37 | * -- BANNER -- 38 | * The top banner with the headings. By using a combination 39 | * of `display: table;` and `display: table-cell;`, we can 40 | * vertically center the text. 41 | */ 42 | 43 | .banner { 44 | background: transparent url('../images/banner.jpg') 0 0 no-repeat fixed; 45 | text-align: center; 46 | background-size: cover; 47 | filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/banner.jpg', sizingMethod='scale'); 48 | 49 | height: 200px; 50 | width: 100%; 51 | margin-bottom: 3em; 52 | display: table; 53 | } 54 | 55 | .banner-head { 56 | display: table-cell; 57 | vertical-align: middle; 58 | margin-bottom: 0; 59 | font-size: 2em; 60 | color: white; 61 | font-weight: 500; 62 | text-shadow: 0 1px 1px black; 63 | } 64 | 65 | 66 | 67 | /* 68 | * -- PRICING TABLE WRAPPER -- 69 | * This element wraps up all the pricing table elements 70 | */ 71 | .pricing-tables, 72 | .information { 73 | max-width: 980px; 74 | margin: 0 auto; 75 | } 76 | .pricing-tables { 77 | margin-bottom: 3.125em; 78 | text-align: center; 79 | } 80 | 81 | /* 82 | * -- PRICING TABLE -- 83 | * Every pricing table has the .pricing-table class 84 | */ 85 | .pricing-table { 86 | border: 1px solid #ddd; 87 | margin: 0 0.5em 2em; 88 | padding: 0 0 3em; 89 | } 90 | 91 | /* 92 | * -- PRICING TABLE HEADER COLORS -- 93 | * Choose a different color based on the type of pricing table. 94 | */ 95 | .pricing-table-free .pricing-table-header { 96 | background: #519251; 97 | } 98 | 99 | .pricing-table-biz .pricing-table-header { 100 | background: #2c4985; 101 | } 102 | 103 | /* 104 | * -- PRICING TABLE HEADER -- 105 | * By default, a header is black/white, and has some styles for its

name. 106 | */ 107 | .pricing-table-header { 108 | background: #111; 109 | color: #fff; 110 | } 111 | .pricing-table-header h2 { 112 | margin: 0; 113 | padding-top: 2em; 114 | font-size: 1em; 115 | font-weight: normal; 116 | 117 | } 118 | 119 | 120 | /* 121 | * -- PRICING TABLE PRICE -- 122 | * Styles for the price and the corresponding per month 123 | */ 124 | .pricing-table-price { 125 | font-size: 6em; 126 | margin: 0.2em 0 0; 127 | font-weight: 100; 128 | } 129 | .pricing-table-price span { 130 | display: block; 131 | text-transform: uppercase; 132 | font-size: 0.2em; 133 | padding-bottom: 2em; 134 | font-weight: 400; 135 | color: rgba(255, 255, 255, 0.5); 136 | *color: #fff; 137 | } 138 | 139 | 140 | 141 | /* 142 | * -- PRICING TABLE LIST -- 143 | * Each pricing table has a