aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2017-01-15 18:24:09 -0800
committerAdam Soltys <[email protected]>2017-01-15 18:24:09 -0800
commit7762ceaa7e2bb34f8758e17a4c8f512fa6a81b9e (patch)
tree91bb1d1b8fe6ad77a48f0e429d9bfd35834c78d9
parent0ae266bf88f9f79c7e99b272688126898303daa4 (diff)
downloadcoinos-server-7762ceaa7e2bb34f8758e17a4c8f512fa6a81b9e.tar.gz
coinos-server-7762ceaa7e2bb34f8758e17a4c8f512fa6a81b9e.zip
Initial Vueification
-rw-r--r--.babelrc5
-rw-r--r--.editorconfig9
-rw-r--r--.eslintignore2
-rw-r--r--.eslintrc.js22
-rw-r--r--.gitignore18
-rw-r--r--Gruntfile.coffee48
-rw-r--r--README.md28
-rw-r--r--app.js291
-rw-r--r--bower.json43
-rw-r--r--build/build.js36
-rw-r--r--build/check-versions.js45
-rw-r--r--build/dev-client.js9
-rw-r--r--build/dev-server.js69
-rw-r--r--build/utils.js61
-rw-r--r--build/webpack.base.conf.js94
-rw-r--r--build/webpack.dev.conf.js36
-rw-r--r--build/webpack.prod.conf.js102
-rw-r--r--config/dev.env.js6
-rw-r--r--config/index.js32
-rw-r--r--config/prod.env.js3
-rw-r--r--config/test.env.js6
-rw-r--r--index.html44
-rw-r--r--package.json121
-rw-r--r--public/css/coinos.css222
-rw-r--r--public/css/coinos.less123
-rw-r--r--public/css/signin.css20
-rw-r--r--public/css/signin.less16
-rw-r--r--settings.js8
-rw-r--r--settings.js.sample (renamed from config.js.sample)0
-rw-r--r--src/App.vue200
-rw-r--r--src/components/SignIn.vue44
-rw-r--r--src/main.js26
-rw-r--r--src/routes/sessions.coffee20
-rw-r--r--src/routes/users.coffee248
-rw-r--r--static/img/bitcoin.png (renamed from public/img/bitcoin.png)bin17755 -> 17755 bytes
-rw-r--r--static/img/blockchain.png (renamed from public/img/blockchain.png)bin1150 -> 1150 bytes
-rw-r--r--static/img/coinos_logo.png (renamed from public/img/coinos_logo.png)bin8316 -> 8316 bytes
-rw-r--r--static/img/coinos_logo.svg (renamed from public/img/coinos_logo.svg)0
-rw-r--r--static/img/favicon.ico (renamed from public/img/favicon.ico)bin1150 -> 1150 bytes
-rw-r--r--static/img/forever.gif (renamed from public/img/forever.gif)bin840447 -> 840447 bytes
-rw-r--r--static/img/glyphicons-halflings-white.png (renamed from public/img/glyphicons-halflings-white.png)bin8777 -> 8777 bytes
-rw-r--r--static/img/glyphicons-halflings.png (renamed from public/img/glyphicons-halflings.png)bin12799 -> 12799 bytes
-rw-r--r--static/img/qr.png (renamed from public/img/qr.png)bin1090 -> 1090 bytes
-rw-r--r--static/img/village_books.png (renamed from public/img/village_books.png)bin121526 -> 121526 bytes
-rw-r--r--static/js/bip38.min.js (renamed from public/js/bip38.min.js)0
-rw-r--r--static/js/bip39.js (renamed from public/js/bip39.js)0
-rw-r--r--static/js/bip39.min.js (renamed from public/js/bip39.min.js)0
-rw-r--r--static/js/bitcoinjs-lib.min.js (renamed from public/js/bitcoinjs-lib.min.js)0
-rw-r--r--static/js/buffer.min.js (renamed from public/js/buffer.min.js)0
-rw-r--r--static/js/calculator.coffee (renamed from public/js/calculator.coffee)0
-rw-r--r--static/js/calculator.js (renamed from public/js/calculator.js)0
-rw-r--r--static/js/check_email.js (renamed from public/js/check_email.js)0
-rw-r--r--static/js/check_password.js (renamed from public/js/check_password.js)0
-rw-r--r--static/js/generate_address.coffee (renamed from public/js/generate_address.coffee)0
-rw-r--r--static/js/generate_address.js (renamed from public/js/generate_address.js)0
-rw-r--r--static/js/index.coffee (renamed from public/js/index.coffee)0
-rw-r--r--static/js/index.js (renamed from public/js/index.js)0
-rw-r--r--static/js/print_element.js (renamed from public/js/print_element.js)0
-rw-r--r--static/js/profile.coffee (renamed from public/js/profile.coffee)0
-rw-r--r--static/js/profile.js (renamed from public/js/profile.js)0
-rw-r--r--static/js/qrcode.js (renamed from public/js/qrcode.js)0
-rw-r--r--static/js/register.coffee (renamed from public/js/register.coffee)0
-rw-r--r--static/js/register.js (renamed from public/js/register.js)0
-rw-r--r--static/js/report.coffee (renamed from public/js/report.coffee)0
-rw-r--r--static/js/report.js (renamed from public/js/report.js)0
-rw-r--r--static/js/setup.coffee (renamed from public/js/setup.coffee)0
-rw-r--r--static/js/setup.js (renamed from public/js/setup.js)0
-rw-r--r--static/js/signin.coffee (renamed from public/js/signin.coffee)0
-rw-r--r--static/js/signin.js (renamed from public/js/signin.js)0
-rw-r--r--static/js/transaction.json (renamed from public/js/transaction.json)0
-rw-r--r--static/js/wallet.coffee (renamed from public/js/wallet.coffee)0
-rw-r--r--static/js/wallet.js (renamed from public/js/wallet.js)0
-rw-r--r--static/success.wav (renamed from public/success.wav)bin13092 -> 13092 bytes
-rw-r--r--test/e2e/custom-assertions/elementCount.js26
-rw-r--r--test/e2e/nightwatch.conf.js46
-rw-r--r--test/e2e/runner.js31
-rw-r--r--test/e2e/specs/test.js19
-rw-r--r--test/unit/.eslintrc9
-rw-r--r--test/unit/index.js13
-rw-r--r--test/unit/karma.conf.js75
-rw-r--r--test/unit/specs/Hello.spec.js13
81 files changed, 1328 insertions, 961 deletions
diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..41789cac
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,5 @@
+{
+ "presets": ["es2015", "stage-2"],
+ "plugins": ["transform-runtime"],
+ "comments": false
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..9d08a1a8
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..34af3774
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+build/*.js
+config/*.js
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000..8e6549e5
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,22 @@
+module.exports = {
+ root: true,
+ parser: 'babel-eslint',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
+ extends: 'standard',
+ // required to lint *.vue files
+ plugins: [
+ 'html'
+ ],
+ // add your custom rules here
+ 'rules': {
+ // allow paren-less arrow functions
+ 'arrow-parens': 0,
+ // allow async-await
+ 'generator-star-spacing': 0,
+ // allow debugger during development
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
+ }
+}
diff --git a/.gitignore b/.gitignore
index 20a7665e..de2ab56a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,8 @@
-node_modules
-builtAssets
-database.json
-sequelize.coffee
+.DS_Store
+node_modules/
+dist/
npm-debug.log
-public/js/rates.json
-src/config.coffee
-config.js
-passenger*
-tmp
-public/img/logos/*
-public/bower
-public/.well_known
+test/unit/coverage
+test/e2e/reports
+selenium-debug.log
db
diff --git a/Gruntfile.coffee b/Gruntfile.coffee
deleted file mode 100644
index ce7d0e9d..00000000
--- a/Gruntfile.coffee
+++ /dev/null
@@ -1,48 +0,0 @@
-module.exports = (grunt) ->
- grunt.initConfig(
- pkg: grunt.file.readJSON('package.json')
- coffee:
- static:
- expand: true
- cwd: 'public/js'
- src: '**/*.coffee'
- dest: 'public/js'
- ext: '.js'
- server:
- expand: true
- cwd: 'src'
- src: '**/*.coffee'
- dest: '.'
- ext: '.js'
- less:
- options:
- paths: ["public/css"]
- src:
- expand: true
- cwd: "public/css"
- src: "*.less"
- dest: 'public/css'
- ext: ".css"
- browserSync:
- dev:
- bsFiles:
- src: ['views/**/*.html', 'public/js/*.js', 'public/css/*.css']
- options:
- open: 'local'
- proxy: 'http://localhost:3000/'
- watchTask: true
- watch:
- less:
- files: ['public/css/*.less']
- tasks: ['newer:less']
- coffee:
- files: ['**/*.coffee']
- tasks: ['newer:coffee']
- )
-
- grunt.loadNpmTasks('grunt-browser-sync')
- grunt.loadNpmTasks('grunt-contrib-less')
- grunt.loadNpmTasks('grunt-contrib-coffee')
- grunt.loadNpmTasks('grunt-contrib-watch')
- grunt.loadNpmTasks('grunt-newer')
- grunt.registerTask('default', ['browserSync', 'watch'])
diff --git a/README.md b/README.md
index f8b86724..bb5d3cf7 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,6 @@
-# CoinOS
+# The Bitcoin Co-op
-CoinOS is a mobile-friendly point-of-sale app that merchants can use to accept Bitcoin payments.
-
-# Features
-
-* Setup a customized payment page with your company name and logo
-* Enter sale amounts in your local currency and have them converted to an amount in bitcoin using your chosen exchange rate
-* A QR code payment request is generated with your receiving address and the sale amount for your customers to scan
-* Get am on-screen notification when the requested amount is received at your address
-* Transactions and the exchange rate at the time of sale are recorded and can be viewed later and filtered by date range
-
-# Installation
-
-Install nodeJS (http://nodejs.org/) and redis (http://redis.io/). On Ubuntu that looks like:
-
- sudo apt-get install nodejs redis-server
-
-Download and configure CoinOS:
-
- git clone https://github.com/thebitcoincoop/coinos
- cd coinos
- npm install
- bower install
- npm start
-
-Now the app should be runnning at http://localhost:3001/
+This website uses the Twitter Bootstrap library and the ExpressJS framework. If you want to contribute changes or updates, contact [email protected] to get started.
# License
diff --git a/app.js b/app.js
index 7510f08a..0703d01f 100644
--- a/app.js
+++ b/app.js
@@ -1,63 +1,23 @@
(function() {
- var RedisStore, app, authorize, bodyParser, cache, config, cookieParser, express, fetchRates, fs, passport, path, proxy, proxyContext, proxyMiddleware, proxyOptions, request, session, sessionStore, sessions, transactions, twilio, users,
- indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+ var app, config, engines, express, fetchRates, fs, path, request, route, routes, view, _fn;
- request = require('request');
+ fs = require('fs');
express = require('express');
- bodyParser = require('body-parser');
-
- cookieParser = require('cookie-parser');
-
path = require('path');
- passport = require('./passport');
-
- config = require('./config');
-
- fs = require('fs');
-
- proxyMiddleware = require('http-proxy-middleware');
-
- sessions = require("./routes/sessions")(passport);
-
- transactions = require("./routes/transactions");
-
- twilio = require('twilio');
-
- users = require("./routes/users")(sessions);
-
- session = require('express-session');
-
- RedisStore = require('connect-redis')(session);
-
- sessionStore = new RedisStore(require('./redis').host, {
- ttl: 172800
- });
-
- proxyContext = '/blockcypher';
+ engines = require('consolidate');
- proxyOptions = {
- target: 'https://api.blockcypher.com',
- changeOrigin: true,
- pathRewrite: {
- '^/blockcypher/': '/'
- },
- onProxyReq: function(proxyReq, req, res) {
- var symbol;
- symbol = indexOf.call(proxyReq.path, '?') >= 0 ? '&' : '?';
- return proxyReq.path += symbol + "token=" + config.blockcypher_token;
- }
- };
+ request = require('request');
- proxy = proxyMiddleware(proxyContext, proxyOptions);
+ config = require('./config');
app = express();
app.enable('trust proxy');
- app.engine('html', require('hogan-express'));
+ app.engine('html', require('mmm').__express);
app.set('view engine', 'html');
@@ -65,50 +25,11 @@
app.use(express["static"](__dirname + '/public'));
- app.use(proxy);
-
- app.use(bodyParser.urlencoded({
- extended: true
- }));
-
- app.use(bodyParser.json());
-
- app.use(bodyParser.json({
- type: 'application/vnd.api+json'
- }));
+ app.use(express.bodyParser());
- app.use(cookieParser(config.secret));
+ app.use(express.cookieParser());
- app.use(session({
- resave: true,
- saveUninitialized: true,
- secret: config.secret,
- store: sessionStore,
- cookie: {
- maxAge: 1209600000
- },
- key: 'coinos.sid'
- }));
-
- app.use(passport.initialize());
-
- app.use(passport.session());
-
- authorize = function(req, res, next) {
- var ref, ref1;
- if (req.params.user === ((ref = req.user) != null ? ref.username : void 0) || ((ref1 = req.user) != null ? ref1.username : void 0) === 'admin') {
- return next();
- }
- req.session.redirect = req.path;
- return res.redirect('/login');
- };
-
- cache = function(req, res, next) {
- if (req.path !== '/login') {
- res.setHeader("Cache-Control", "public, max-age=900");
- }
- return next();
- };
+ app.use(app.router);
(fetchRates = function() {
request("https://api.bitcoinaverage.com/exchanges/all", function(error, response, body) {
@@ -120,38 +41,136 @@
return fs.truncate(file, 0, function() {
return stream.write(body);
});
- } catch (undefined) {}
+ } catch (_error) {}
});
return setTimeout(fetchRates, 120000);
})();
- require('./bcoin').init(app);
+ routes = {
+ '/': 'index',
+ '/about': 'about',
+ '/coinfest': 'coinfest',
+ '/coinos': 'coinos',
+ '/contact': 'contact',
+ '/directors': 'directors',
+ '/membership': 'membership',
+ '/merchants': 'merchants',
+ '/partners': 'partners',
+ '/register': 'register'
+ };
- app.get('/', cache, sessions["new"]);
+ _fn = function(route, view) {
+ return app.get(route, function(req, res) {
+ return res.render(view, {
+ js: (function() {
+ return global.js;
+ }),
+ css: (function() {
+ return global.css;
+ }),
+ layout: 'layout'
+ });
+ });
+ };
+ for (route in routes) {
+ view = routes[route];
+ _fn(route, view);
+ }
+
+ app.get('/users', function(req, res) {
+ var db;
+ db = require('./redis');
+ return db.keys('member:*', function(err, keys) {
+ var i, key, users, _i, _len, _results;
+ users = [];
+ _results = [];
+ for (i = _i = 0, _len = keys.length; _i < _len; i = ++_i) {
+ key = keys[i];
+ _results.push((function(i, db) {
+ return db.hgetall(key, function(err, user) {
+ if (!user["private"]) {
+ users.push(user);
+ }
+ if (i >= keys.length - 1) {
+ users.sort(function(a, b) {
+ if (parseInt(a.number) < parseInt(b.number)) {
+ return -1;
+ }
+ if (parseInt(a.number) > parseInt(b.number)) {
+ return 1;
+ }
+ return 0;
+ });
+ res.write(JSON.stringify(users));
+ return res.end();
+ }
+ });
+ })(i, db));
+ }
+ return _results;
+ });
+ });
- app.get('/address', cache, function(req, res) {
- return res.render('address', {
- layout: 'layout',
- js: (function() {
- return global.js;
- }),
- css: (function() {
- return global.css;
- })
+ app.post('/users', function(req, res) {
+ var db, userkey;
+ db = require('./redis');
+ userkey = "member:" + req.body.email;
+ return db.hgetall(userkey, function(err, obj) {
+ if (obj) {
+ res.status(500).send("Sorry, that email address is already registered");
+ return;
+ }
+ db.sadd("users", userkey);
+ return db.incr('members', function(err, number) {
+ return db.hmset(userkey, {
+ name: req.body.name,
+ email: req.body.email,
+ address: req.body.address,
+ number: number,
+ date: req.body.date,
+ txid: req.body.txid
+ }, function(err, obj) {
+ var email;
+ if (true || process.env.NODE_ENV === 'production') {
+ email = req.body.email;
+ res.render('welcome', {
+ layout: 'mail',
+ js: (function() {
+ return global.js;
+ }),
+ css: (function() {
+ return global.css;
+ })
+ }, function(err, html) {
+ var sendgrid;
+ sendgrid = require('sendgrid')(config.sendgrid_user, config.sendgrid_password);
+ email = new sendgrid.Email({
+ to: email,
+ subject: 'Welcome to the Co-op!',
+ html: html
+ });
+ return sendgrid.send(email);
+ });
+ }
+ return res.end();
+ });
+ });
});
});
- app.get('/ticker', cache, function(req, res) {
+ app.get('/ticker', function(req, res) {
+ var fd;
fs = require('fs');
- return fs.readFile("./public/js/rates.json", function(err, data) {
- var base, base1, base2, e, error1, exchange;
- (base = req.query).currency || (base.currency = 'CAD');
- (base1 = req.query).symbol || (base1.symbol = 'quadrigacx');
- (base2 = req.query).type || (base2.type = 'bid');
+ return fd = fs.readFile("./public/js/rates.json", function(err, data) {
+ var e, exchange, _base, _base1, _base2;
+ (_base = req.query).currency || (_base.currency = 'CAD');
+ (_base1 = req.query).symbol || (_base1.symbol = 'quadrigacx');
+ (_base2 = req.query).type || (_base2.type = 'bid');
try {
exchange = JSON.parse(data)[req.query.currency][req.query.symbol]['rates'][req.query.type].toString();
- } catch (error1) {
- e = error1;
+ } catch (_error) {
+ e = _error;
exchange = "0";
}
res.writeHead(200, {
@@ -163,64 +182,12 @@
});
});
- app.get('/tips', cache, function(req, res) {
- return res.render('tips', {
- notice: true,
- layout: 'layout',
- js: (function() {
- return global.js;
- }),
- css: (function() {
- return global.css;
- })
- });
- });
-
- app.get('/login', cache, sessions["new"]);
-
- app.post('/login', sessions.create);
-
- app.get('/logout', sessions.destroy);
-
- app.get('/users.json', users.index);
-
- app.get('/register', cache, users["new"]);
-
- app.get('/users/new', cache, users["new"]);
-
- app.post('/users', users.create);
-
- app.get('/verify/:token', users.verify);
-
- app.post('/:user', authorize, users.update);
-
- app.get('/:user/edit', authorize, users.edit);
-
- app.get('/:user/profile', authorize, users.profile);
-
- app.get('/:user/wallet', authorize, users.wallet);
-
- app.get('/:user/transactions.json', authorize, transactions.json);
-
- app.post('/:user/transactions', transactions.create);
-
- app.post('/transactions/:txid', transactions.update);
-
- app["delete"]('/:user/transactions/:txid', transactions["delete"]);
-
- app.get('/:user/report', authorize, transactions.index);
-
- app.get('/:user.json', users.json);
-
- app.get('/:user', cache, users.show);
-
app.use(function(err, req, res, next) {
res.status(500);
- res.send('An error occurred');
- console.error(err.stack);
+ console.log(err);
return res.end();
});
- app.listen(3000);
+ app.listen(3002);
}).call(this);
diff --git a/bower.json b/bower.json
deleted file mode 100644
index 241bae58..00000000
--- a/bower.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "name": "coinos",
- "description": "Bitcoin Point of Sale App",
- "main": "app.js",
- "authors": [
- "Adam Soltys <[email protected]>"
- ],
- "license": "MIT",
- "keywords": [
- "bitcoin",
- "point",
- "of",
- "sale"
- ],
- "homepage": "https://github.com/asoltys/coinos.io",
- "moduleType": [
- "node"
- ],
- "ignore": [
- "**/.*",
- "node_modules",
- "bower_components",
- "public",
- "test",
- "tests"
- ],
- "dependencies": {
- "font-awesome": "~4.5.0",
- "jquery": "~2.1.4",
- "jquery-ui": "~1.11.4",
- "bootstrap": "https://github.com/asoltys/bootstrap.git#b962f8a4308d094c23c9ac47a0a5ee3d4e9a4bfd",
- "bootstrap-validator": "~0.9.0",
- "bootstrap3-dialog": "~1.34.7",
- "jquery-serialize-object": "~2.5.0",
- "crypto-js": "~3.1.5",
- "bootbox.js": "bootbox#~4.4.0",
- "moment": "^2.15.1",
- "bootstrap-formhelpers": "^2.3.0"
- },
- "resolutions": {
- "bootstrap": "b962f8a4308d094c23c9ac47a0a5ee3d4e9a4bfd"
- }
-}
diff --git a/build/build.js b/build/build.js
new file mode 100644
index 00000000..b3c9aad4
--- /dev/null
+++ b/build/build.js
@@ -0,0 +1,36 @@
+// https://github.com/shelljs/shelljs
+require('./check-versions')()
+require('shelljs/global')
+env.NODE_ENV = 'production'
+
+var path = require('path')
+var config = require('../config')
+var ora = require('ora')
+var webpack = require('webpack')
+var webpackConfig = require('./webpack.prod.conf')
+
+console.log(
+ ' Tip:\n' +
+ ' Built files are meant to be served over an HTTP server.\n' +
+ ' Opening index.html over file:// won\'t work.\n'
+)
+
+var spinner = ora('building for production...')
+spinner.start()
+
+var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
+rm('-rf', assetsPath)
+mkdir('-p', assetsPath)
+cp('-R', 'static/*', assetsPath)
+
+webpack(webpackConfig, function (err, stats) {
+ spinner.stop()
+ if (err) throw err
+ process.stdout.write(stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false
+ }) + '\n')
+})
diff --git a/build/check-versions.js b/build/check-versions.js
new file mode 100644
index 00000000..e2b6cf74
--- /dev/null
+++ b/build/check-versions.js
@@ -0,0 +1,45 @@
+var semver = require('semver')
+var chalk = require('chalk')
+var packageConfig = require('../package.json')
+var exec = function (cmd) {
+ return require('child_process')
+ .execSync(cmd).toString().trim()
+}
+
+var versionRequirements = [
+ {
+ name: 'node',
+ currentVersion: semver.clean(process.version),
+ versionRequirement: packageConfig.engines.node
+ },
+ {
+ name: 'npm',
+ currentVersion: exec('npm --version'),
+ versionRequirement: packageConfig.engines.npm
+ }
+]
+
+module.exports = function () {
+ var warnings = []
+ for (var i = 0; i < versionRequirements.length; i++) {
+ var mod = versionRequirements[i]
+ if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+ warnings.push(mod.name + ': ' +
+ chalk.red(mod.currentVersion) + ' should be ' +
+ chalk.green(mod.versionRequirement)
+ )
+ }
+ }
+
+ if (warnings.length) {
+ console.log('')
+ console.log(chalk.yellow('To use this template, you must update following to modules:'))
+ console.log()
+ for (var i = 0; i < warnings.length; i++) {
+ var warning = warnings[i]
+ console.log(' ' + warning)
+ }
+ console.log()
+ process.exit(1)
+ }
+}
diff --git a/build/dev-client.js b/build/dev-client.js
new file mode 100644
index 00000000..18aa1e21
--- /dev/null
+++ b/build/dev-client.js
@@ -0,0 +1,9 @@
+/* eslint-disable */
+require('eventsource-polyfill')
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
+
+hotClient.subscribe(function (event) {
+ if (event.action === 'reload') {
+ window.location.reload()
+ }
+})
diff --git a/build/dev-server.js b/build/dev-server.js
new file mode 100644
index 00000000..c5f99637
--- /dev/null
+++ b/build/dev-server.js
@@ -0,0 +1,69 @@
+require('./check-versions')()
+var config = require('../config')
+if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
+var path = require('path')
+var express = require('express')
+var webpack = require('webpack')
+var opn = require('opn')
+var proxyMiddleware = require('http-proxy-middleware')
+var webpackConfig = process.env.NODE_ENV === 'testing'
+ ? require('./webpack.prod.conf')
+ : require('./webpack.dev.conf')
+
+// default port where dev server listens for incoming traffic
+var port = process.env.PORT || config.dev.port
+// Define HTTP proxies to your custom API backend
+// https://github.com/chimurai/http-proxy-middleware
+var proxyTable = config.dev.proxyTable
+
+var app = express()
+var compiler = webpack(webpackConfig)
+
+var devMiddleware = require('webpack-dev-middleware')(compiler, {
+ publicPath: webpackConfig.output.publicPath,
+ stats: {
+ colors: true,
+ chunks: false
+ }
+})
+
+var hotMiddleware = require('webpack-hot-middleware')(compiler)
+// force page reload when html-webpack-plugin template changes
+compiler.plugin('compilation', function (compilation) {
+ compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
+ hotMiddleware.publish({ action: 'reload' })
+ cb()
+ })
+})
+
+// proxy api requests
+Object.keys(proxyTable).forEach(function (context) {
+ var options = proxyTable[context]
+ if (typeof options === 'string') {
+ options = { target: options }
+ }
+ app.use(proxyMiddleware(context, options))
+})
+
+// handle fallback for HTML5 history API
+app.use(require('connect-history-api-fallback')())
+
+// serve webpack bundle output
+app.use(devMiddleware)
+
+// enable hot-reload and state-preserving
+// compilation error display
+app.use(hotMiddleware)
+
+// serve pure static assets
+var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
+app.use(staticPath, express.static('./static'))
+
+module.exports = app.listen(port, function (err) {
+ if (err) {
+ console.log(err)
+ return
+ }
+ var uri = 'http://localhost:' + port
+ console.log('Listening at ' + uri + '\n')
+})
diff --git a/build/utils.js b/build/utils.js
new file mode 100644
index 00000000..dc3cdd01
--- /dev/null
+++ b/build/utils.js
@@ -0,0 +1,61 @@
+var path = require('path')
+var config = require('../config')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+
+exports.assetsPath = function (_path) {
+ var assetsSubDirectory = process.env.NODE_ENV === 'production'
+ ? config.build.assetsSubDirectory
+ : config.dev.assetsSubDirectory
+ return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+ options = options || {}
+ // generate loader string to be used with extract text plugin
+ function generateLoaders (loaders) {
+ var sourceLoader = loaders.map(function (loader) {
+ var extraParamChar
+ if (/\?/.test(loader)) {
+ loader = loader.replace(/\?/, '-loader?')
+ extraParamChar = '&'
+ } else {
+ loader = loader + '-loader'
+ extraParamChar = '?'
+ }
+ return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
+ }).join('!')
+
+ // Extract CSS when that option is specified
+ // (which is the case during production build)
+ if (options.extract) {
+ return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
+ } else {
+ return ['vue-style-loader', sourceLoader].join('!')
+ }
+ }
+
+ // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
+ return {
+ css: generateLoaders(['css']),
+ postcss: generateLoaders(['css']),
+ less: generateLoaders(['css', 'less']),
+ sass: generateLoaders(['css', 'sass?indentedSyntax']),
+ scss: generateLoaders(['css', 'sass']),
+ stylus: generateLoaders(['css', 'stylus']),
+ styl: generateLoaders(['css', 'stylus'])
+ }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+ var output = []
+ var loaders = exports.cssLoaders(options)
+ for (var extension in loaders) {
+ var loader = loaders[extension]
+ output.push({
+ test: new RegExp('\\.' + extension + '$'),
+ loader: loader
+ })
+ }
+ return output
+}
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
new file mode 100644
index 00000000..1bfc1cc8
--- /dev/null
+++ b/build/webpack.base.conf.js
@@ -0,0 +1,94 @@
+var path = require('path')
+var config = require('../config')
+var utils = require('./utils')
+var projectRoot = path.resolve(__dirname, '../')
+
+var env = process.env.NODE_ENV
+// check env & config/index.js to decide whether to enable CSS source maps for the
+// various preprocessor loaders added to vue-loader at the end of this file
+var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
+var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
+var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
+
+module.exports = {
+ entry: {
+ app: './src/main.js'
+ },
+ output: {
+ path: config.build.assetsRoot,
+ publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['', '.js', '.vue', '.json'],
+ fallback: [path.join(__dirname, '../node_modules')],
+ alias: {
+ 'vue$': 'vue/dist/vue.common.js',
+ 'src': path.resolve(__dirname, '../src'),
+ 'assets': path.resolve(__dirname, '../src/assets'),
+ 'components': path.resolve(__dirname, '../src/components')
+ }
+ },
+ resolveLoader: {
+ fallback: [path.join(__dirname, '../node_modules')]
+ },
+ module: {
+ preLoaders: [
+ {
+ test: /\.vue$/,
+ loader: 'eslint',
+ include: projectRoot,
+ exclude: /node_modules/
+ },
+ {
+ test: /\.js$/,
+ loader: 'eslint',
+ include: projectRoot,
+ exclude: /node_modules/
+ }
+ ],
+ loaders: [
+ {
+ test: /\.vue$/,
+ loader: 'vue'
+ },
+ {
+ test: /\.js$/,
+ loader: 'babel',
+ include: projectRoot,
+ exclude: /node_modules/
+ },
+ {
+ test: /\.json$/,
+ loader: 'json'
+ },
+ {
+ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+ loader: 'url',
+ query: {
+ limit: 10000,
+ name: utils.assetsPath('img/[name].[hash:7].[ext]')
+ }
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+ loader: 'url',
+ query: {
+ limit: 10000,
+ name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+ }
+ }
+ ]
+ },
+ eslint: {
+ formatter: require('eslint-friendly-formatter')
+ },
+ vue: {
+ loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
+ postcss: [
+ require('autoprefixer')({
+ browsers: ['last 2 versions']
+ })
+ ]
+ }
+}
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
new file mode 100644
index 00000000..cd52bf72
--- /dev/null
+++ b/build/webpack.dev.conf.js
@@ -0,0 +1,36 @@
+var config = require('../config')
+var webpack = require('webpack')
+var merge = require('webpack-merge')
+var utils = require('./utils')
+var baseWebpackConfig = require('./webpack.base.conf')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+
+// add hot-reload related code to entry chunks
+Object.keys(baseWebpackConfig.entry).forEach(function (name) {
+ baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
+})
+
+module.exports = merge(baseWebpackConfig, {
+ module: {
+ loaders: [
+ utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
+ ]
+ },
+ // eval-source-map is faster for development
+ devtool: '#eval-source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': config.dev.env
+ }),
+ // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
+ new webpack.optimize.OccurrenceOrderPlugin(),
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoErrorsPlugin(),
+ // https://github.com/ampedandwired/html-webpack-plugin
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ template: 'index.html',
+ inject: true
+ })
+ ]
+})
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
new file mode 100644
index 00000000..ba564d36
--- /dev/null
+++ b/build/webpack.prod.conf.js
@@ -0,0 +1,102 @@
+var path = require('path')
+var config = require('../config')
+var utils = require('./utils')
+var webpack = require('webpack')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var env = process.env.NODE_ENV === 'testing'
+ ? require('../config/test.env')
+ : config.build.env
+
+var webpackConfig = merge(baseWebpackConfig, {
+ module: {
+ loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
+ },
+ devtool: config.build.productionSourceMap ? '#source-map' : false,
+ output: {
+ path: config.build.assetsRoot,
+ filename: utils.assetsPath('js/[name].[chunkhash].js'),
+ chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
+ },
+ vue: {
+ loaders: utils.cssLoaders({
+ sourceMap: config.build.productionSourceMap,
+ extract: true
+ })
+ },
+ plugins: [
+ // http://vuejs.github.io/vue-loader/en/workflow/production.html
+ new webpack.DefinePlugin({
+ 'process.env': env
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ }
+ }),
+ new webpack.optimize.OccurrenceOrderPlugin(),
+ // extract css into its own file
+ new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
+ // generate dist index.html with correct asset hash for caching.
+ // you can customize output by editing /index.html
+ // see https://github.com/ampedandwired/html-webpack-plugin
+ new HtmlWebpackPlugin({
+ filename: process.env.NODE_ENV === 'testing'
+ ? 'index.html'
+ : config.build.index,
+ template: 'index.html',
+ inject: true,
+ minify: {
+ removeComments: true,
+ collapseWhitespace: true,
+ removeAttributeQuotes: true
+ // more options:
+ // https://github.com/kangax/html-minifier#options-quick-reference
+ },
+ // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+ chunksSortMode: 'dependency'
+ }),
+ // split vendor js into its own file
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ minChunks: function (module, count) {
+ // any required modules inside node_modules are extracted to vendor
+ return (
+ module.resource &&
+ /\.js$/.test(module.resource) &&
+ module.resource.indexOf(
+ path.join(__dirname, '../node_modules')
+ ) === 0
+ )
+ }
+ }),
+ // extract webpack runtime and module manifest to its own file in order to
+ // prevent vendor hash from being updated whenever app bundle is updated
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'manifest',
+ chunks: ['vendor']
+ })
+ ]
+})
+
+if (config.build.productionGzip) {
+ var CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+ webpackConfig.plugins.push(
+ new CompressionWebpackPlugin({
+ asset: '[path].gz[query]',
+ algorithm: 'gzip',
+ test: new RegExp(
+ '\\.(' +
+ config.build.productionGzipExtensions.join('|') +
+ ')$'
+ ),
+ threshold: 10240,
+ minRatio: 0.8
+ })
+ )
+}
+
+module.exports = webpackConfig
diff --git a/config/dev.env.js b/config/dev.env.js
new file mode 100644
index 00000000..efead7c8
--- /dev/null
+++ b/config/dev.env.js
@@ -0,0 +1,6 @@
+var merge = require('webpack-merge')
+var prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+ NODE_ENV: '"development"'
+})
diff --git a/config/index.js b/config/index.js
new file mode 100644
index 00000000..207dfbd4
--- /dev/null
+++ b/config/index.js
@@ -0,0 +1,32 @@
+// see http://vuejs-templates.github.io/webpack for documentation.
+var path = require('path')
+
+module.exports = {
+ build: {
+ env: require('./prod.env'),
+ index: path.resolve(__dirname, '../dist/index.html'),
+ assetsRoot: path.resolve(__dirname, '../dist'),
+ assetsSubDirectory: 'static',
+ assetsPublicPath: '/',
+ productionSourceMap: true,
+ // Gzip off by default as many popular static hosts such as
+ // Surge or Netlify already gzip all static assets for you.
+ // Before setting to `true`, make sure to:
+ // npm install --save-dev compression-webpack-plugin
+ productionGzip: false,
+ productionGzipExtensions: ['js', 'css']
+ },
+ dev: {
+ env: require('./dev.env'),
+ port: 8080,
+ assetsSubDirectory: 'static',
+ assetsPublicPath: '/',
+ proxyTable: {},
+ // CSS Sourcemaps off by default because relative paths are "buggy"
+ // with this option, according to the CSS-Loader README
+ // (https://github.com/webpack/css-loader#sourcemaps)
+ // In our experience, they generally work as expected,
+ // just be aware of this issue when enabling this option.
+ cssSourceMap: false
+ }
+}
diff --git a/config/prod.env.js b/config/prod.env.js
new file mode 100644
index 00000000..773d263d
--- /dev/null
+++ b/config/prod.env.js
@@ -0,0 +1,3 @@
+module.exports = {
+ NODE_ENV: '"production"'
+}
diff --git a/config/test.env.js b/config/test.env.js
new file mode 100644
index 00000000..89f90deb
--- /dev/null
+++ b/config/test.env.js
@@ -0,0 +1,6 @@
+var merge = require('webpack-merge')
+var devEnv = require('./dev.env')
+
+module.exports = merge(devEnv, {
+ NODE_ENV: '"testing"'
+})
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..82e457a3
--- /dev/null
+++ b/index.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1" />
+ <meta name="mobile-web-app-capable" content="yes">
+ <title>CoinOS</title>
+ <link href="/img/favicon.ico" rel="shortcut icon" />
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" />
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap-theme.min.css" />
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-validator/0.5.3/js/bootstrapValidator.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bitcoinjs-lib/0.2.0-1/bitcoinjs-min.js"></script>
+ <script src="/js/buffer.min.js"></script>
+ <script src="/js/qrcode.js"></script>
+ </head>
+ <body>
+ <div class="container-fluid">
+ <nav class="navbar" role="navigation">
+ <button type="button" class="btn btn-primary navbar-toggle" data-toggle="collapse" data-target="#nav">
+ <span class="pull-right glyphicon glyphicon-list"></span>
+ <span id="connection" class="pull-right glyphicon glyphicon-exclamation-sign"></span>
+ </button>
+ <div id="nav" class="collapse navbar-collapse no-transition">
+ <div class="navbar">
+ <ul class="nav navbar-nav">
+ <li><a href="/{{user}}"><span class="glyphicon glyphicon-home"></span> Home</a></li>
+ <li><a href="/{{user}}/report"><span class="glyphicon glyphicon-list-alt"></span> Sales</a></li>
+ <li><a href="/{{user}}/wallet"><span class="fa fa-bank"></span> Wallet</a></li>
+ <li><a href="/{{user}}/profile"><span class="glyphicon glyphicon-user"></span> Account</a></li>
+ <li><a href="/{{user}}/edit"><span class="glyphicon glyphicon-cog"></span> Settings</a></li>
+ <li><a href="/logout"><span class="glyphicon glyphicon-off"></span> Logout</a></li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+ <div class="row" id="app"></div>
+ </div>
+ </body>
+</html>
diff --git a/package.json b/package.json
index 0e298fd3..2be452f9 100644
--- a/package.json
+++ b/package.json
@@ -1,48 +1,93 @@
{
"name": "coinos",
- "version": "0.0.1",
+ "version": "1.0.0",
+ "description": "CoinOS",
+ "author": "Adam Soltys <[email protected]>",
"private": true,
"scripts": {
- "start": "node app.js & grunt",
- "postinstall": "cp config.js.sample config.js"
+ "dev": "node build/dev-server.js",
+ "build": "node build/build.js",
+ "unit": "karma start test/unit/karma.conf.js --single-run",
+ "e2e": "node test/e2e/runner.js",
+ "test": "npm run unit && npm run e2e",
+ "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
},
"dependencies": {
- "async": "2.1.1",
- "bcoin": "git+https://github.com/bcoin-org/bcoin.git#master",
- "bcryptjs": "^2.3.0",
- "bip39": "^2.2.0",
- "bitcoinjs-lib": "^2.3.0",
- "bluebird": "^3.4.6",
- "body-parser": "^1.5.0",
- "browser-sync": "^2.11.0",
- "coffee-script": "1.11.1",
- "connect-assets": "*",
- "connect-redis": "3.1.0",
- "consolidate": "0.14.1",
- "cookie-parser": "^1.3.2",
- "express": "4.14.0",
- "express-session": "^1.6.5",
- "hogan-express": "0.5.2",
- "http-proxy-middleware": "^0.17.2",
- "less": "2.7.1",
- "moment": "^2.10.6",
- "node-http-proxy": "^0.2.3",
- "passport": "0.3.2",
- "passport-local": "1.0.0",
- "redis": "2.6.2",
- "request": "~2.75.0",
- "sendgrid": "^4.3.0",
- "twilio": "^2.11.1"
+ "lokijs": "^1.4.2",
+ "redis": "^2.6.3",
+ "slick-carousel": "^1.6.0",
+ "vue": "^2.1.0",
+ "vue-router": "^2.1.1"
},
"devDependencies": {
- "bower": "^1.8.0",
- "coffeeify": "^2.0.1",
- "grunt": "^1.0.1",
- "grunt-browser-sync": "^2.2.0",
- "grunt-browserify": "^5.0.0",
- "grunt-contrib-coffee": "^1.0.0",
- "grunt-contrib-less": "^1.1.0",
- "grunt-contrib-watch": "^1.0.0",
- "grunt-newer": "^1.1.1"
+ "autoprefixer": "^6.4.0",
+ "babel-core": "^6.0.0",
+ "babel-eslint": "^7.0.0",
+ "babel-loader": "^6.0.0",
+ "babel-plugin-transform-runtime": "^6.0.0",
+ "babel-preset-es2015": "^6.0.0",
+ "babel-preset-stage-2": "^6.0.0",
+ "babel-register": "^6.0.0",
+ "chai": "^3.5.0",
+ "chalk": "^1.1.3",
+ "chromedriver": "^2.21.2",
+ "coffee-loader": "^0.7.2",
+ "connect-history-api-fallback": "^1.1.0",
+ "cross-spawn": "^4.0.2",
+ "css-loader": "^0.25.0",
+ "eslint": "^3.7.1",
+ "eslint-config-standard": "^6.1.0",
+ "eslint-friendly-formatter": "^2.0.5",
+ "eslint-loader": "^1.5.0",
+ "eslint-plugin-html": "^1.3.0",
+ "eslint-plugin-promise": "^3.4.0",
+ "eslint-plugin-standard": "^2.0.1",
+ "eventsource-polyfill": "^0.9.6",
+ "expose-loader": "^0.7.1",
+ "express": "^4.13.3",
+ "extract-text-webpack-plugin": "^1.0.1",
+ "file-loader": "^0.9.0",
+ "function-bind": "^1.0.2",
+ "html-webpack-plugin": "^2.8.1",
+ "http-proxy-middleware": "^0.17.2",
+ "inject-loader": "^2.0.1",
+ "isparta-loader": "^2.0.0",
+ "json-loader": "^0.5.4",
+ "karma": "^1.3.0",
+ "karma-coverage": "^1.1.1",
+ "karma-mocha": "^1.2.0",
+ "karma-phantomjs-launcher": "^1.0.0",
+ "karma-sinon-chai": "^1.2.0",
+ "karma-sourcemap-loader": "^0.3.7",
+ "karma-spec-reporter": "0.0.26",
+ "karma-webpack": "^1.7.0",
+ "lolex": "^1.4.0",
+ "mocha": "^3.1.0",
+ "nightwatch": "^0.9.8",
+ "opn": "^4.0.2",
+ "ora": "^0.3.0",
+ "phantomjs-prebuilt": "^2.1.3",
+ "pug": "^2.0.0-beta6",
+ "pug-loader": "^2.3.0",
+ "reveal.js": "^3.3.0",
+ "selenium-server": "2.53.1",
+ "semver": "^5.3.0",
+ "shelljs": "^0.7.4",
+ "sinon": "^1.17.3",
+ "sinon-chai": "^2.8.0",
+ "stylus": "^0.54.5",
+ "stylus-loader": "^2.4.0",
+ "url-loader": "^0.5.7",
+ "vue-loader": "^10.0.0",
+ "vue-style-loader": "^1.0.0",
+ "vue-template-compiler": "^2.1.0",
+ "webpack": "^1.13.2",
+ "webpack-dev-middleware": "^1.8.3",
+ "webpack-hot-middleware": "^2.12.2",
+ "webpack-merge": "^0.14.1"
+ },
+ "engines": {
+ "node": ">= 4.0.0",
+ "npm": ">= 3.0.0"
}
}
diff --git a/public/css/coinos.css b/public/css/coinos.css
deleted file mode 100644
index 63198679..00000000
--- a/public/css/coinos.css
+++ /dev/null
@@ -1,222 +0,0 @@
-@media (min-width: 580px) {
- .mobile {
- display: none;
- }
-}
-@media (max-width: 360px) {
- .tablet {
- display: none;
- }
-}
-h1 {
- margin-top: 0;
-}
-div.container-fluid {
- max-width: 580px;
- margin-left: auto;
- margin-right: auto;
- text-align: left;
- padding: 0;
- padding-bottom: 10px;
-}
-.navbar-toggle {
- background-color: #428bca;
- width: 100%;
- position: absolute;
- top: 0px;
- height: 30px;
- margin: 0;
- float: none;
- z-index: 1;
- margin-bottom: -30px;
-}
-.navbar-toggle .glyphicon {
- margin-top: -5px;
- padding-left: 10px;
-}
-.navbar {
- margin: 0;
- min-height: 40px;
-}
-.navbar-collapse.in {
- overflow-y: visible;
-}
-#connection {
- display: none;
-}
-.numpad .btn,
-.tippad .btn {
- margin-bottom: 5px;
- width: 6em;
-}
-.numpad h2,
-.tippad h2 {
- margin-bottom: 2px;
- margin-top: 1px;
-}
-.numpad {
- width: 26em;
-}
-.rates {
- margin-top: -15px;
-}
-.rates h2 .label {
- font-size: 14px;
- position: relative;
- top: -5px;
-}
-#slide {
- height: 1.8em;
- width: 100%;
- margin-bottom: 2px;
-}
-@media (max-width: 360px) {
- .tippad .btn {
- width: 4.3em;
- }
- .numpad .btn {
- width: 5.7em;
- }
- .numpad,
- .tippad,
- .rates,
- #payment {
- padding-left: 5px;
- }
- .rates {
- width: 100%;
- text-align: left;
- }
- #address {
- font-size: 16px;
- }
-}
-#payment h2 {
- margin-bottom: 2px;
-}
-.no-transition {
- -webkit-transition: height 0.01s;
- -moz-transition: height 0.01s;
- -ms-transition: height 0.01s;
- -o-transition: height 0.01s;
- transition: height 0.01s;
-}
-#nav .nav {
- margin-top: 30px;
-}
-#nav .nav li a {
- padding: 10px 0 10px 10px;
-}
-.form-group label {
- font-weight: normal;
- font-size: 14px;
- display: block;
-}
-.form-group p,
-.form-group span,
-.form-group .form-control {
- font-size: 18px;
-}
-.form-group span,
-.form-group label {
- color: #999;
-}
-.form-group .form-control {
- height: auto;
- padding: 3px;
- display: inline-block;
-}
-.form-group .input-group-addon {
- color: #000;
-}
-.form-group .input-group-btn {
- padding-top: 24px;
-}
-.form-group .form-control-feedback {
- margin-top: 24px;
-}
-div#calculator #amount {
- width: 5em;
-}
-div#calculator #received {
- font-size: 16px;
- width: 100%;
-}
-div#calculator strong#total {
- font-size: 30px;
-}
-div#calculator #address {
- padding-top: 10px;
-}
-div#calculator #address a {
- color: #fff;
- text-decoration: none;
-}
-div#calculator #title {
- display: none;
-}
-div#calculator #title a {
- color: #000;
- text-decoration: none;
-}
-div#calculator #logo {
- display: none;
- padding: 10px 0;
- margin-bottom: 10px;
- max-height: 225px;
- width: 100%;
-}
-div#calculator #error {
- margin-top: 10px;
-}
-div#calculator #qr {
- padding-bottom: 10px;
- margin-left: 20px;
-}
-div#calculator .numpad .btn-primary {
- background: #abc;
-}
-td.total {
- font-weight: bold;
- text-align: right;
-}
-#main-logo {
- background-image: url('../img/coinos_logo.png');
- width: 300px;
- height: 80px;
- margin-top: 10px;
- margin-bottom: 10px;
-}
-.svg #main-logo {
- background-image: url('../img/coinos_logo.svg') !important;
- background-size: 300px 80px;
-}
-@page {
- margin: 0;
-}
-.btn-sample {
- color: #FFFFFF;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #DBDBDB;
- *background-color: #DBDBDB;
- background-image: -moz-linear-gradient(top, #B5B5B5, #DBDBDB);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#B5B5B5), to(#DBDBDB));
- background-image: -webkit-linear-gradient(top, #B5B5B5, #DBDBDB);
- background-image: -o-linear-gradient(top, #B5B5B5, #DBDBDB);
- background-image: linear-gradient(to bottom, #B5B5B5, #DBDBDB);
- background-repeat: repeat-x;
- border-color: #DBDBDB;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#B5B5B5', endColorstr='#DBDBDB', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-.btn-sample:hover,
-.btn-sample:focus,
-.btn-sample:active,
-.btn-sample.active,
-.btn-sample.disabled,
-.btn-sample[disabled] {
- color: #FFFFFF;
- background-color: #DBDBDB;
- *background-color: #003bb3;
-}
diff --git a/public/css/coinos.less b/public/css/coinos.less
deleted file mode 100644
index 2ded7095..00000000
--- a/public/css/coinos.less
+++ /dev/null
@@ -1,123 +0,0 @@
-@media (min-width: 580px) { .mobile { display: none; } }
-@media (max-width: 360px) { .tablet { display: none; } }
-
-h1 { margin-top: 0; }
-
-div.container-fluid {
- max-width: 580px;
- margin-left: auto;
- margin-right: auto;
- text-align:left;
- padding: 0;
- padding-bottom: 10px;
-}
-
-.navbar-toggle { background-color: #428bca; width: 100%; position: absolute; top: 0px; height: 30px; margin: 0; float: none; z-index: 1; margin-bottom: -30px; }
-.navbar-toggle .glyphicon { margin-top: -5px; padding-left: 10px; }
-.navbar { margin: 0; min-height: 40px; }
-.navbar-collapse.in { overflow-y: visible; }
-#connection { display: none; }
-
-.numpad .btn, .tippad .btn { margin-bottom: 5px; width: 6em; }
-.numpad h2, .tippad h2 { margin-bottom: 2px; margin-top: 1px; }
-.numpad { width: 26em; }
-.rates {
- margin-top: -15px;
- h2 .label { font-size: 14px; position: relative; top: -5px; }
-}
-
-#slide { height: 1.8em; width: 100%; margin-bottom: 2px; }
-
-@media (max-width: 360px) {
- .tippad .btn { width: 4.3em; }
- .numpad .btn { width: 5.7em; }
- .numpad, .tippad, .rates, #payment { padding-left: 5px; }
- .rates { width: 100%; text-align: left; }
- #address{ font-size: 16px; }
-}
-
-
-#payment h2 { margin-bottom: 2px; }
-
-.no-transition {
- -webkit-transition: height 0.01s;
- -moz-transition: height 0.01s;
- -ms-transition: height 0.01s;
- -o-transition: height 0.01s;
- transition: height 0.01s;
-}
-
-#nav {
- .nav { margin-top: 30px; }
- .nav li a { padding: 10px 0 10px 10px; }
-}
-
-.form-group {
- label { font-weight: normal; font-size: 14px; display: block; }
- p, span, .form-control { font-size: 18px; }
- span, label { color: #999; }
- .form-control { height: auto; padding: 3px; display: inline-block; }
- .input-group-addon { color: #000; }
- .input-group-btn { padding-top: 24px; }
- .form-control-feedback { margin-top: 24px; }
-}
-
-div#calculator {
- #amount { width: 5em; }
- #received { font-size: 16px; width: 100%; }
- strong#total { font-size: 30px; }
- #address { padding-top: 10px; }
- #address a { color: #fff; text-decoration: none; }
- #title { display: none; }
- #title a { color: #000; text-decoration: none; }
- #logo { display: none; padding: 10px 0; margin-bottom: 10px; max-height: 225px; width: 100%; }
- #error { margin-top: 10px; }
- #qr { padding-bottom: 10px; margin-left: 20px; }
- .numpad .btn-primary { background: #abc; }
-}
-
-td.total { font-weight: bold; text-align: right; }
-
-#main-logo {
- background-image: url('../img/coinos_logo.png');
- width: 300px;
- height: 80px;
- margin-top: 10px;
- margin-bottom: 10px;
-}
-
-.svg #main-logo {
- background-image: url('../img/coinos_logo.svg') !important;
- background-size: 300px 80px;
-}
-
-@page {
- margin: 0;
-}
-
-.btn-sample {
- color: #FFFFFF;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #DBDBDB;
- *background-color: #DBDBDB;
- background-image: -moz-linear-gradient(top, #B5B5B5, #DBDBDB);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#B5B5B5), to(#DBDBDB));
- background-image: -webkit-linear-gradient(top, #B5B5B5, #DBDBDB);
- background-image: -o-linear-gradient(top, #B5B5B5, #DBDBDB);
- background-image: linear-gradient(to bottom, #B5B5B5, #DBDBDB);
- background-repeat: repeat-x;
- border-color: #DBDBDB;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#B5B5B5', endColorstr='#DBDBDB', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-.btn-sample:hover,
-.btn-sample:focus,
-.btn-sample:active,
-.btn-sample.active,
-.btn-sample.disabled,
-.btn-sample[disabled] {
- color: #FFFFFF;
- background-color: #DBDBDB;
- *background-color: #003bb3;
-}
diff --git a/public/css/signin.css b/public/css/signin.css
deleted file mode 100644
index 2f3291ff..00000000
--- a/public/css/signin.css
+++ /dev/null
@@ -1,20 +0,0 @@
-#main-logo {
- margin-bottom: 15px;
-}
-.form-signin {
- margin-bottom: 10px;
-}
-.form-signin .form-control {
- height: auto;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- padding: 10px;
- font-size: 16px;
-}
-.notice {
- border: 1px solid gray;
- background: #eee;
- padding: 10px;
- margin-bottom: 15px;
-}
diff --git a/public/css/signin.less b/public/css/signin.less
deleted file mode 100644
index 1e109616..00000000
--- a/public/css/signin.less
+++ /dev/null
@@ -1,16 +0,0 @@
-#main-logo { margin-bottom: 15px; }
-
-.form-signin {
- margin-bottom: 10px;
-}
-
-.form-signin .form-control {
- height: auto;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- padding: 10px;
- font-size: 16px;
-}
-
-.notice { border: 1px solid gray; background: #eee; padding: 10px; margin-bottom: 15px; }
diff --git a/settings.js b/settings.js
new file mode 100644
index 00000000..24bc2676
--- /dev/null
+++ b/settings.js
@@ -0,0 +1,8 @@
+(function() {
+ module.exports.secret = 'weareallmadeofstars';
+ module.exports.sendgrid_token = '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';
+ module.exports.blockcypher_token = '5cc216aacc2f107d48d9c66c4c895b18';
+ module.exports.twilio_sid = 'AC2836301208c04e499e4eeb3a139a352b';
+ module.exports.twilio_token = '741a75a0922dea9ec00981380a76afc6';
+ module.exports.twilio_number = '5874001495';
+}).call(this);
diff --git a/config.js.sample b/settings.js.sample
index 35969183..35969183 100644
--- a/config.js.sample
+++ b/settings.js.sample
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 00000000..90059e78
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,200 @@
+<template lang="pug">
+ router-view
+</template>
+
+<script>
+import SignIn from './components/SignIn'
+
+export default {
+ name: 'App',
+ components: {
+ SignIn
+ }
+}
+</script>
+
+<style lang="stylus">
+ @media (min-width: 580px)
+ .mobile
+ display none
+
+ @media (max-width: 360px)
+ .tablet
+ display none
+
+ h1
+ margin-top 0
+
+ div
+ &.container-fluid
+ max-width 580px
+ margin-left auto
+ margin-right auto
+ text-align left
+ padding 0
+ padding-bottom 10px
+
+ .navbar-toggle
+ background-color #428bca
+ width 100%
+ position absolute
+ top 0px
+ height 30px
+ margin 0
+ float none
+ z-index 1
+ margin-bottom -30px
+ .glyphicon
+ margin-top -5px
+ padding-left 10px
+
+ .navbar
+ margin 0
+ min-height 40px
+
+ .navbar-collapse
+ &.in
+ overflow-y visible
+
+ #connection
+ display none
+
+ .numpad .btn,
+ .tippad .btn
+ margin-bottom 5px
+ width 6em
+
+ .numpad h2,
+ .tippad h2
+ margin-bottom 2px
+ margin-top 1px
+
+ .numpad
+ width 26em
+
+ .rates
+ margin-top -15px
+ h2
+ .label
+ font-size 14px
+ position relative
+ top -5px
+
+ #slide
+ height 1.8em
+ width 100%
+ margin-bottom 2px
+
+ @media (max-width: 360px)
+ .tippad
+ .btn
+ width 4.3em
+ .numpad
+ .btn
+ width 5.7em
+ .rates
+ width 100%
+ text-align left
+ #address
+ font-size 16px
+
+ .numpad,
+ .tippad,
+ .rates,
+
+ #payment
+ h2
+ margin-bottom 2px
+
+ .no-transition
+ -webkit-transition height 0.01s
+ -moz-transition height 0.01s
+ -ms-transition height 0.01s
+ -o-transition height 0.01s
+ transition height 0.01s
+
+ #nav
+ .nav
+ margin-top 30px
+ li
+ a
+ padding 10px 0 10px 10px
+
+ .form-group
+ label
+ font-weight normal
+ font-size 14px
+ display block
+ .form-control
+ height auto
+ padding 3px
+ display inline-block
+ .input-group-addon
+ color #000
+ .input-group-btn
+ padding-top 24px
+ .form-control-feedback
+ margin-top 24px
+
+ .form-group p,
+ .form-group span,
+ .form-group .form-control
+ font-size 18px
+
+ .form-group span,
+ .form-group label
+ color #999
+
+ div#calculator
+ #amount
+ width 5em
+ #received
+ font-size 16px
+ width 100%
+ strong#total
+ font-size 30px
+ #address
+ padding-top 10px
+ a
+ color #fff
+ text-decoration none
+ #title
+ display none
+ a
+ color #000
+ text-decoration none
+ #logo
+ display none
+ padding 10px 0
+ margin-bottom 10px
+ max-height 225px
+ width 100%
+ #error
+ margin-top 10px
+ #qr
+ padding-bottom 10px
+ margin-left 20px
+ .numpad
+ .btn-primary
+ background #abc
+
+ td
+ &.total
+ font-weight bold
+ text-align right
+
+ #main-logo
+ background-image url('/static/img/coinos_logo.png')
+ width 300px
+ height 80px
+ margin-top 10px
+ margin-bottom 10px
+
+ .svg
+ #main-logo
+ background-image url('/static/img/coinos_logo.svg') !important
+ background-size 300px 80px
+
+ @page
+ margin 0
+</style>
diff --git a/src/components/SignIn.vue b/src/components/SignIn.vue
new file mode 100644
index 00000000..37a49d5f
--- /dev/null
+++ b/src/components/SignIn.vue
@@ -0,0 +1,44 @@
+<template lang="pug">
+div
+ #main-logo
+ p.alert.alert-danger Invalid username or password, please try again.
+ form.form-signin(action='login', method='post')
+ input.form-control(name='username', type='text', placeholder='Username', value='user', required='', autofocus='')
+ br
+ |
+ input.form-control(name='password', type='password', placeholder='Password', required='')
+ br
+ |
+ button.btn.btn-lg.btn-primary.btn-block(type='submit') Sign in
+ p
+ button#register.btn.btn-lg.btn-secondary.btn-block Register
+ p
+ a(href='http://bitcoincoop.org/coinos') What is this?
+</template>
+
+<script>
+export default {
+ name: 'home'
+}
+</script>
+
+<style lang="stylus">
+ #main-logo
+ margin-bottom 15px
+
+ .form-signin
+ margin-bottom 10px
+ .form-control
+ height auto
+ -webkit-box-sizing border-box
+ -moz-box-sizing border-box
+ box-sizing border-box
+ padding 10px
+ font-size 16px
+
+ .notice
+ border 1px solid gray
+ background #eee
+ padding 10px
+ margin-bottom 15px
+</style>
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 00000000..e4ae5c1a
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,26 @@
+// The Vue build version to load with the `import` command
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+import App from './App'
+import SignIn from './components/SignIn'
+
+const routes = [
+ { path: '/', component: SignIn },
+ { path: '/signin', component: SignIn }
+]
+
+const router = new VueRouter({
+ routes // short for routes: routes
+})
+
+Vue.use(VueRouter)
+
+/* eslint-disable no-new */
+new Vue({
+ el: '#app',
+ template: '<app/>',
+ components: { App },
+ router: router
+})
+
diff --git a/src/routes/sessions.coffee b/src/routes/sessions.coffee
index 6a441c75..70c18b4e 100644
--- a/src/routes/sessions.coffee
+++ b/src/routes/sessions.coffee
@@ -1,9 +1,8 @@
module.exports = (passport) ->
new: (req, res) ->
res.render('sessions/new',
- layout: 'layout',
js: (-> global.js),
- css: (-> global.css)
+ css: (-> global.css)
)
create: (req, res, next) ->
@@ -11,23 +10,12 @@ module.exports = (passport) ->
if (err)
return next(err)
if (!user)
- return res.render('sessions/new',
- layout: 'layout',
- js: (-> global.js),
- css: (-> global.css),
- badpw: true
- )
- req.session.user = user
+ return res.redirect('/login')
req.login(user, (err) ->
if (err)
return next(err)
-
- re = new RegExp(user.username, 'g')
- if req.session.redirect? and re.test(req.session.redirect)
- url = req.session.redirect
-
- url ?= "/#{user.username}"
- delete req.session.redirect
+ url = req.headers['referer']
+ url = "/#{user.username}" unless /edit/.test(url)
return res.redirect(url)
)
)(req, res, next)
diff --git a/src/routes/users.coffee b/src/routes/users.coffee
index 7b2f1f3a..1611f691 100644
--- a/src/routes/users.coffee
+++ b/src/routes/users.coffee
@@ -1,253 +1,31 @@
-Promise = require('bluebird')
-db = require("../redis")
-config = require("../config")
-bcrypt = require('bcryptjs')
-fs = require('fs')
-request = require('request')
+db = require("redis").createClient()
+bcrypt = require('bcrypt')
module.exports = (sessions) ->
exists: (req, res) ->
- db.hgetall("user:"+req.params.user.toLowerCase(), (err, obj) ->
+ db.hgetall(req.params.user, (err, obj) ->
if obj? then res.write('true') else res.write('false')
res.end()
)
- index: (req, res) ->
- result = 'users': []
- db.keysAsync("user:*").then((users) ->
- Promise.all(users.map((key) ->
- db.hgetallAsync(key).then((user) ->
- delete user['password']
- result.users.push(user)
- )
- ))
- ).then(->
- res.writeHead(200, {"Content-Type": "application/json"});
- res.write(JSON.stringify(result))
- res.end()
- )
-
- json: (req, res) ->
- res.end() unless req.params.user
- db.llen("#{req.params.user.toLowerCase()}:transactions", (err, len) ->
- db.hgetall("user:#{req.params.user.toLowerCase()}", (err, obj) ->
- delete obj['password']
- obj['index'] = len
- res.writeHead(200, {"Content-Type": "application/json"});
- res.write(JSON.stringify(obj))
- res.end()
- )
- )
-
- show: (req, res) ->
- db.hgetall("user:"+req.params.user.toLowerCase(), (err, obj) ->
- if obj
- options =
- user: req.params.user.toLowerCase(),
- layout: 'layout',
- navigation: true,
- js: (-> global.js),
- css: (-> global.css)
-
- if req.query.verified?
- options.verified = true
-
- if obj.logo and obj.logo.length > 3
- ext = obj.logo.substr(obj.logo.length - 3)
- path = "public/img/logos/#{obj.username}.#{ext}"
- fs.lstat(path, (err, stats) ->
- if ext in ['jpg', 'png', 'gif'] and (err or not stats.isFile())
- try
- request("#{obj.logo}").pipe(fs.createWriteStream(path))
- )
-
- res.render('users/show', options)
- delete req.session.verified
- else
- res.render('sessions/new',
- notice: true,
- layout: 'layout',
- js: (-> global.js),
- css: (-> global.css)
- )
- )
-
new: (req, res) ->
res.render('users/new',
- layout: 'layout',
js: (-> global.js),
- css: (-> global.css)
+ css: (-> global.css),
)
create: (req, res) ->
- errormsg = ""
- userkey = "user:"+req.body.username
- db.hgetall(userkey, (err, obj) ->
+ db.hgetall(req.body.username, (err, obj) ->
if obj
- errormsg += "Username exists"
-
- if req.body.confirm != req.body.password
- errormsg += "Passwords must match"
-
- if errormsg
- return res.render('users/new',
- layout: 'layout',
- js: (-> global.js),
- css: (-> global.css),
- error: errormsg
- )
-
- bcrypt.hash(req.body.password, 12, (err, hash) ->
- db.sadd("users",userkey)
- db.hmset(userkey,
- username: req.body.username,
- password: hash,
- email: req.body.email,
- commission: req.body.commission
- unit: req.body.unit,
- pubkey: req.body.pubkey,
- privkey: req.body.privkey
- , ->
- req.session.redirect = "/#{req.body.username}/edit"
- sessions.create(req, res)
- )
- )
-
- require('crypto').randomBytes(48, (ex, buf) ->
- token = buf.toString('base64').replace(/\//g,'').replace(/\+/g,'')
- db.set("token:#{token}", req.body.username)
- host = req.hostname
- host += ':3000' if host is 'localhost'
- url = "#{req.protocol}://#{host}/verify/#{token}"
-
- res.render('users/welcome',
- user: req.body.username.toLowerCase(),
- layout: 'mail',
- url: url,
- privkey: req.body.privkey,
- js: (-> global.js),
- css: (-> global.css),
- (err, html) ->
-
- helper = require('sendgrid').mail
- from_email = new helper.Email('[email protected]')
- to_email = new helper.Email(req.body.email)
- subject = 'Welcome to CoinOS'
- content = new helper.Content('text/html', html)
- mail = new helper.Mail(from_email, subject, to_email, content)
-
- sg = require('sendgrid')(config.sendgrid_token)
- request = sg.emptyRequest(
- method: 'POST'
- path: '/v3/mail/send'
- body: mail.toJSON()
- )
-
- sg.API(request, (error, response) ->
- console.log(response.statusCode)
- console.log(response.body)
- console.log(response.headers)
- )
- )
- )
- )
-
- edit: (req, res) ->
- res.render('users/edit',
- user: req.params.user.toLowerCase(),
- layout: 'layout',
- navigation: true,
- js: (-> global.js),
- css: (-> global.css)
- )
-
- profile: (req, res) ->
- res.render('users/profile',
- user: req.params.user.toLowerCase(),
- layout: 'layout',
- navigation: true,
- js: (-> global.js),
- css: (-> global.css)
- )
-
- update: (req, res) ->
- if req.body.password is ''
- delete req.body.password
-
- db.hmset("user:"+req.params.user.toLowerCase(), req.body, ->
- req.session.user = req.body
- delete req.session.user.password
-
- if req.body.password?
- bcrypt.hash(req.body.password, 12, (err, password) ->
- db.hmset("user:#{req.params.user.toLowerCase()}",
- email: email
- password: password
- phone: phone
- , ->
- if req.xhr
- res.send({})
- res.end()
- else
- res.redirect("/#{req.params.user.toLowerCase()}")
- )
- )
+ res.redirect(req.body.username)
else
- if req.xhr
- res.send({})
- res.end()
- else
- res.redirect("/#{req.params.user.toLowerCase()}")
- )
-
- if process.env.NODE_ENV is 'production' and
- req.body.privkey? and
- req.body.privkey != '' and
- req.body.email != ''
- res.render('users/key',
- user: req.params.user.toLowerCase(),
- layout: 'mail',
- key: req.body.privkey,
- js: (-> global.js),
- css: (-> global.css),
- (err, html) ->
- helper = require('sendgrid').mail
- from_email = new helper.Email('[email protected]')
- to_email = new helper.Email(req.body.email)
- subject = 'CoinOS Wallet Key'
- content = new helper.Content('text/html', html)
- mail = new helper.Mail(from_email, subject, to_email, content)
-
- sg = require('sendgrid')(config.sendgrid_token)
- request = sg.emptyRequest(
- method: 'POST'
- path: '/v3/mail/send'
- body: mail.toJSON()
- )
-
- sg.API(request, (error, response) ->
- console.log(response.statusCode)
- console.log(response.body)
- console.log(response.headers)
+ bcrypt.hash(req.body.password, 12, (err, hash) ->
+ db.hmset(
+ req.body.username,
+ {username: req.body.username, password: hash},
+ ->
+ req.headers['referer'] = "/#{req.body.username}/edit"
+ sessions.create(req, res)
)
- )
-
- verify: (req, res) ->
- db.get("token:#{req.params.token}", (err, reply) ->
- if err or !reply
- res.write("Invalid Verification Token")
- res.end()
- else
- db.hset("user:#{reply.toString()}", "verified", "true", ->
- res.redirect("/#{reply.toString()}?verified")
)
)
-
- wallet: (req, res) ->
- res.render('users/wallet',
- user: req.params.user.toLowerCase(),
- layout: 'layout',
- navigation: true,
- js: (-> global.js),
- css: (-> global.css)
- )
diff --git a/public/img/bitcoin.png b/static/img/bitcoin.png
index 148d0c0b..148d0c0b 100644
--- a/public/img/bitcoin.png
+++ b/static/img/bitcoin.png
Binary files differ
diff --git a/public/img/blockchain.png b/static/img/blockchain.png
index 1d620cbe..1d620cbe 100644
--- a/public/img/blockchain.png
+++ b/static/img/blockchain.png
Binary files differ
diff --git a/public/img/coinos_logo.png b/static/img/coinos_logo.png
index d95b54e3..d95b54e3 100644
--- a/public/img/coinos_logo.png
+++ b/static/img/coinos_logo.png
Binary files differ
diff --git a/public/img/coinos_logo.svg b/static/img/coinos_logo.svg
index 18dcf824..18dcf824 100644
--- a/public/img/coinos_logo.svg
+++ b/static/img/coinos_logo.svg
diff --git a/public/img/favicon.ico b/static/img/favicon.ico
index e8aac8a4..e8aac8a4 100644
--- a/public/img/favicon.ico
+++ b/static/img/favicon.ico
Binary files differ
diff --git a/public/img/forever.gif b/static/img/forever.gif
index ac64bf51..ac64bf51 100644
--- a/public/img/forever.gif
+++ b/static/img/forever.gif
Binary files differ
diff --git a/public/img/glyphicons-halflings-white.png b/static/img/glyphicons-halflings-white.png
index 3bf6484a..3bf6484a 100644
--- a/public/img/glyphicons-halflings-white.png
+++ b/static/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/public/img/glyphicons-halflings.png b/static/img/glyphicons-halflings.png
index a9969993..a9969993 100644
--- a/public/img/glyphicons-halflings.png
+++ b/static/img/glyphicons-halflings.png
Binary files differ
diff --git a/public/img/qr.png b/static/img/qr.png
index c17101e7..c17101e7 100644
--- a/public/img/qr.png
+++ b/static/img/qr.png
Binary files differ
diff --git a/public/img/village_books.png b/static/img/village_books.png
index 3743f069..3743f069 100644
--- a/public/img/village_books.png
+++ b/static/img/village_books.png
Binary files differ
diff --git a/public/js/bip38.min.js b/static/js/bip38.min.js
index 9fdc4334..9fdc4334 100644
--- a/public/js/bip38.min.js
+++ b/static/js/bip38.min.js
diff --git a/public/js/bip39.js b/static/js/bip39.js
index 92b075ba..92b075ba 100644
--- a/public/js/bip39.js
+++ b/static/js/bip39.js
diff --git a/public/js/bip39.min.js b/static/js/bip39.min.js
index b60d4cbc..b60d4cbc 100644
--- a/public/js/bip39.min.js
+++ b/static/js/bip39.min.js
diff --git a/public/js/bitcoinjs-lib.min.js b/static/js/bitcoinjs-lib.min.js
index 146e599e..146e599e 100644
--- a/public/js/bitcoinjs-lib.min.js
+++ b/static/js/bitcoinjs-lib.min.js
diff --git a/public/js/buffer.min.js b/static/js/buffer.min.js
index 526af316..526af316 100644
--- a/public/js/buffer.min.js
+++ b/static/js/buffer.min.js
diff --git a/public/js/calculator.coffee b/static/js/calculator.coffee
index 75192596..75192596 100644
--- a/public/js/calculator.coffee
+++ b/static/js/calculator.coffee
diff --git a/public/js/calculator.js b/static/js/calculator.js
index 6e71b56a..6e71b56a 100644
--- a/public/js/calculator.js
+++ b/static/js/calculator.js
diff --git a/public/js/check_email.js b/static/js/check_email.js
index 402b93af..402b93af 100644
--- a/public/js/check_email.js
+++ b/static/js/check_email.js
diff --git a/public/js/check_password.js b/static/js/check_password.js
index f8fc40cc..f8fc40cc 100644
--- a/public/js/check_password.js
+++ b/static/js/check_password.js
diff --git a/public/js/generate_address.coffee b/static/js/generate_address.coffee
index face65ea..face65ea 100644
--- a/public/js/generate_address.coffee
+++ b/static/js/generate_address.coffee
diff --git a/public/js/generate_address.js b/static/js/generate_address.js
index bd592864..bd592864 100644
--- a/public/js/generate_address.js
+++ b/static/js/generate_address.js
diff --git a/public/js/index.coffee b/static/js/index.coffee
index 090abbe2..090abbe2 100644
--- a/public/js/index.coffee
+++ b/static/js/index.coffee
diff --git a/public/js/index.js b/static/js/index.js
index b1a7167c..b1a7167c 100644
--- a/public/js/index.js
+++ b/static/js/index.js
diff --git a/public/js/print_element.js b/static/js/print_element.js
index ee0e7212..ee0e7212 100644
--- a/public/js/print_element.js
+++ b/static/js/print_element.js
diff --git a/public/js/profile.coffee b/static/js/profile.coffee
index a894f275..a894f275 100644
--- a/public/js/profile.coffee
+++ b/static/js/profile.coffee
diff --git a/public/js/profile.js b/static/js/profile.js
index db81832b..db81832b 100644
--- a/public/js/profile.js
+++ b/static/js/profile.js
diff --git a/public/js/qrcode.js b/static/js/qrcode.js
index 9fb20227..9fb20227 100644
--- a/public/js/qrcode.js
+++ b/static/js/qrcode.js
diff --git a/public/js/register.coffee b/static/js/register.coffee
index 98842328..98842328 100644
--- a/public/js/register.coffee
+++ b/static/js/register.coffee
diff --git a/public/js/register.js b/static/js/register.js
index 6e623ae4..6e623ae4 100644
--- a/public/js/register.js
+++ b/static/js/register.js
diff --git a/public/js/report.coffee b/static/js/report.coffee
index 3fee6714..3fee6714 100644
--- a/public/js/report.coffee
+++ b/static/js/report.coffee
diff --git a/public/js/report.js b/static/js/report.js
index 31b3b689..31b3b689 100644
--- a/public/js/report.js
+++ b/static/js/report.js
diff --git a/public/js/setup.coffee b/static/js/setup.coffee
index d796c897..d796c897 100644
--- a/public/js/setup.coffee
+++ b/static/js/setup.coffee
diff --git a/public/js/setup.js b/static/js/setup.js
index e96bd00d..e96bd00d 100644
--- a/public/js/setup.js
+++ b/static/js/setup.js
diff --git a/public/js/signin.coffee b/static/js/signin.coffee
index df7dbb39..df7dbb39 100644
--- a/public/js/signin.coffee
+++ b/static/js/signin.coffee
diff --git a/public/js/signin.js b/static/js/signin.js
index 4a385599..4a385599 100644
--- a/public/js/signin.js
+++ b/static/js/signin.js
diff --git a/public/js/transaction.json b/static/js/transaction.json
index 3b71ae50..3b71ae50 100644
--- a/public/js/transaction.json
+++ b/static/js/transaction.json
diff --git a/public/js/wallet.coffee b/static/js/wallet.coffee
index 1beefd23..1beefd23 100644
--- a/public/js/wallet.coffee
+++ b/static/js/wallet.coffee
diff --git a/public/js/wallet.js b/static/js/wallet.js
index 05e0c2bc..05e0c2bc 100644
--- a/public/js/wallet.js
+++ b/static/js/wallet.js
diff --git a/public/success.wav b/static/success.wav
index 21646005..21646005 100644
--- a/public/success.wav
+++ b/static/success.wav
Binary files differ
diff --git a/test/e2e/custom-assertions/elementCount.js b/test/e2e/custom-assertions/elementCount.js
new file mode 100644
index 00000000..c0d5fe00
--- /dev/null
+++ b/test/e2e/custom-assertions/elementCount.js
@@ -0,0 +1,26 @@
+// A custom Nightwatch assertion.
+// the name of the method is the filename.
+// can be used in tests like this:
+//
+// browser.assert.elementCount(selector, count)
+//
+// for how to write custom assertions see
+// http://nightwatchjs.org/guide#writing-custom-assertions
+exports.assertion = function (selector, count) {
+ this.message = 'Testing if element <' + selector + '> has count: ' + count
+ this.expected = count
+ this.pass = function (val) {
+ return val === this.expected
+ }
+ this.value = function (res) {
+ return res.value
+ }
+ this.command = function (cb) {
+ var self = this
+ return this.api.execute(function (selector) {
+ return document.querySelectorAll(selector).length
+ }, [selector], function (res) {
+ cb.call(self, res)
+ })
+ }
+}
diff --git a/test/e2e/nightwatch.conf.js b/test/e2e/nightwatch.conf.js
new file mode 100644
index 00000000..8fb4d9d2
--- /dev/null
+++ b/test/e2e/nightwatch.conf.js
@@ -0,0 +1,46 @@
+require('babel-register')
+var config = require('../../config')
+
+// http://nightwatchjs.org/guide#settings-file
+module.exports = {
+ src_folders: ['test/e2e/specs'],
+ output_folder: 'test/e2e/reports',
+ custom_assertions_path: ['test/e2e/custom-assertions'],
+
+ selenium: {
+ start_process: true,
+ server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.1.jar',
+ host: '127.0.0.1',
+ port: 4444,
+ cli_args: {
+ 'webdriver.chrome.driver': require('chromedriver').path
+ }
+ },
+
+ test_settings: {
+ default: {
+ selenium_port: 4444,
+ selenium_host: 'localhost',
+ silent: true,
+ globals: {
+ devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
+ }
+ },
+
+ chrome: {
+ desiredCapabilities: {
+ browserName: 'chrome',
+ javascriptEnabled: true,
+ acceptSslCerts: true
+ }
+ },
+
+ firefox: {
+ desiredCapabilities: {
+ browserName: 'firefox',
+ javascriptEnabled: true,
+ acceptSslCerts: true
+ }
+ }
+ }
+}
diff --git a/test/e2e/runner.js b/test/e2e/runner.js
new file mode 100644
index 00000000..2a5c6ef9
--- /dev/null
+++ b/test/e2e/runner.js
@@ -0,0 +1,31 @@
+// 1. start the dev server using production config
+process.env.NODE_ENV = 'testing'
+var server = require('../../build/dev-server.js')
+
+// 2. run the nightwatch test suite against it
+// to run in additional browsers:
+// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
+// 2. add it to the --env flag below
+// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
+// For more information on Nightwatch's config file, see
+// http://nightwatchjs.org/guide#settings-file
+var opts = process.argv.slice(2)
+if (opts.indexOf('--config') === -1) {
+ opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
+}
+if (opts.indexOf('--env') === -1) {
+ opts = opts.concat(['--env', 'chrome'])
+}
+
+var spawn = require('cross-spawn')
+var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
+
+runner.on('exit', function (code) {
+ server.close()
+ process.exit(code)
+})
+
+runner.on('error', function (err) {
+ server.close()
+ throw err
+})
diff --git a/test/e2e/specs/test.js b/test/e2e/specs/test.js
new file mode 100644
index 00000000..a7b1bd92
--- /dev/null
+++ b/test/e2e/specs/test.js
@@ -0,0 +1,19 @@
+// For authoring Nightwatch tests, see
+// http://nightwatchjs.org/guide#usage
+
+module.exports = {
+ 'default e2e tests': function (browser) {
+ // automatically uses dev Server port from /config.index.js
+ // default: http://localhost:8080
+ // see nightwatch.conf.js
+ const devServer = browser.globals.devServerURL
+
+ browser
+ .url(devServer)
+ .waitForElementVisible('#app', 5000)
+ .assert.elementPresent('.hello')
+ .assert.containsText('h1', 'Welcome to Your Vue.js App')
+ .assert.elementCount('img', 1)
+ .end()
+ }
+}
diff --git a/test/unit/.eslintrc b/test/unit/.eslintrc
new file mode 100644
index 00000000..959a4f4b
--- /dev/null
+++ b/test/unit/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "env": {
+ "mocha": true
+ },
+ "globals": {
+ "expect": true,
+ "sinon": true
+ }
+}
diff --git a/test/unit/index.js b/test/unit/index.js
new file mode 100644
index 00000000..9db1ffd5
--- /dev/null
+++ b/test/unit/index.js
@@ -0,0 +1,13 @@
+// Polyfill fn.bind() for PhantomJS
+/* eslint-disable no-extend-native */
+Function.prototype.bind = require('function-bind')
+
+// require all test files (files that ends with .spec.js)
+const testsContext = require.context('./specs', true, /\.spec$/)
+testsContext.keys().forEach(testsContext)
+
+// require all src files except main.js for coverage.
+// you can also change this to match only the subset of files that
+// you want coverage for.
+const srcContext = require.context('src', true, /^\.\/(?!main(\.js)?$)/)
+srcContext.keys().forEach(srcContext)
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
new file mode 100644
index 00000000..048b7290
--- /dev/null
+++ b/test/unit/karma.conf.js
@@ -0,0 +1,75 @@
+// This is a karma config file. For more details see
+// http://karma-runner.github.io/0.13/config/configuration-file.html
+// we are also using it with karma-webpack
+// https://github.com/webpack/karma-webpack
+
+var path = require('path')
+var merge = require('webpack-merge')
+var baseConfig = require('../../build/webpack.base.conf')
+var utils = require('../../build/utils')
+var webpack = require('webpack')
+var projectRoot = path.resolve(__dirname, '../../')
+
+var webpackConfig = merge(baseConfig, {
+ // use inline sourcemap for karma-sourcemap-loader
+ module: {
+ loaders: utils.styleLoaders()
+ },
+ devtool: '#inline-source-map',
+ vue: {
+ loaders: {
+ js: 'isparta'
+ }
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': require('../../config/test.env')
+ })
+ ]
+})
+
+// no need for app entry during tests
+delete webpackConfig.entry
+
+// make sure isparta loader is applied before eslint
+webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || []
+webpackConfig.module.preLoaders.unshift({
+ test: /\.js$/,
+ loader: 'isparta',
+ include: path.resolve(projectRoot, 'src')
+})
+
+// only apply babel for test files when using isparta
+webpackConfig.module.loaders.some(function (loader, i) {
+ if (loader.loader === 'babel') {
+ loader.include = path.resolve(projectRoot, 'test/unit')
+ return true
+ }
+})
+
+module.exports = function (config) {
+ config.set({
+ // to run in additional browsers:
+ // 1. install corresponding karma launcher
+ // http://karma-runner.github.io/0.13/config/browsers.html
+ // 2. add it to the `browsers` array below.
+ browsers: ['PhantomJS'],
+ frameworks: ['mocha', 'sinon-chai'],
+ reporters: ['spec', 'coverage'],
+ files: ['./index.js'],
+ preprocessors: {
+ './index.js': ['webpack', 'sourcemap']
+ },
+ webpack: webpackConfig,
+ webpackMiddleware: {
+ noInfo: true
+ },
+ coverageReporter: {
+ dir: './coverage',
+ reporters: [
+ { type: 'lcov', subdir: '.' },
+ { type: 'text-summary' }
+ ]
+ }
+ })
+}
diff --git a/test/unit/specs/Hello.spec.js b/test/unit/specs/Hello.spec.js
new file mode 100644
index 00000000..a6965db0
--- /dev/null
+++ b/test/unit/specs/Hello.spec.js
@@ -0,0 +1,13 @@
+import Vue from 'vue'
+import Hello from 'src/components/Hello'
+
+describe('Hello.vue', () => {
+ it('should render correct contents', () => {
+ const vm = new Vue({
+ el: document.createElement('div'),
+ render: (h) => h(Hello)
+ })
+ expect(vm.$el.querySelector('.hello h1').textContent)
+ .to.equal('Welcome to Your Vue.js App')
+ })
+})