diff options
| -rw-r--r-- | .babelrc | 5 | ||||
| -rw-r--r-- | .editorconfig | 9 | ||||
| -rw-r--r-- | .eslintignore | 2 | ||||
| -rw-r--r-- | .eslintrc.js | 22 | ||||
| -rw-r--r-- | .gitignore | 18 | ||||
| -rw-r--r-- | Gruntfile.coffee | 48 | ||||
| -rw-r--r-- | README.md | 28 | ||||
| -rw-r--r-- | app.js | 291 | ||||
| -rw-r--r-- | bower.json | 43 | ||||
| -rw-r--r-- | build/build.js | 36 | ||||
| -rw-r--r-- | build/check-versions.js | 45 | ||||
| -rw-r--r-- | build/dev-client.js | 9 | ||||
| -rw-r--r-- | build/dev-server.js | 69 | ||||
| -rw-r--r-- | build/utils.js | 61 | ||||
| -rw-r--r-- | build/webpack.base.conf.js | 94 | ||||
| -rw-r--r-- | build/webpack.dev.conf.js | 36 | ||||
| -rw-r--r-- | build/webpack.prod.conf.js | 102 | ||||
| -rw-r--r-- | config/dev.env.js | 6 | ||||
| -rw-r--r-- | config/index.js | 32 | ||||
| -rw-r--r-- | config/prod.env.js | 3 | ||||
| -rw-r--r-- | config/test.env.js | 6 | ||||
| -rw-r--r-- | index.html | 44 | ||||
| -rw-r--r-- | package.json | 121 | ||||
| -rw-r--r-- | public/css/coinos.css | 222 | ||||
| -rw-r--r-- | public/css/coinos.less | 123 | ||||
| -rw-r--r-- | public/css/signin.css | 20 | ||||
| -rw-r--r-- | public/css/signin.less | 16 | ||||
| -rw-r--r-- | settings.js | 8 | ||||
| -rw-r--r-- | settings.js.sample (renamed from config.js.sample) | 0 | ||||
| -rw-r--r-- | src/App.vue | 200 | ||||
| -rw-r--r-- | src/components/SignIn.vue | 44 | ||||
| -rw-r--r-- | src/main.js | 26 | ||||
| -rw-r--r-- | src/routes/sessions.coffee | 20 | ||||
| -rw-r--r-- | src/routes/users.coffee | 248 | ||||
| -rw-r--r-- | static/img/bitcoin.png (renamed from public/img/bitcoin.png) | bin | 17755 -> 17755 bytes | |||
| -rw-r--r-- | static/img/blockchain.png (renamed from public/img/blockchain.png) | bin | 1150 -> 1150 bytes | |||
| -rw-r--r-- | static/img/coinos_logo.png (renamed from public/img/coinos_logo.png) | bin | 8316 -> 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) | bin | 1150 -> 1150 bytes | |||
| -rw-r--r-- | static/img/forever.gif (renamed from public/img/forever.gif) | bin | 840447 -> 840447 bytes | |||
| -rw-r--r-- | static/img/glyphicons-halflings-white.png (renamed from public/img/glyphicons-halflings-white.png) | bin | 8777 -> 8777 bytes | |||
| -rw-r--r-- | static/img/glyphicons-halflings.png (renamed from public/img/glyphicons-halflings.png) | bin | 12799 -> 12799 bytes | |||
| -rw-r--r-- | static/img/qr.png (renamed from public/img/qr.png) | bin | 1090 -> 1090 bytes | |||
| -rw-r--r-- | static/img/village_books.png (renamed from public/img/village_books.png) | bin | 121526 -> 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) | bin | 13092 -> 13092 bytes | |||
| -rw-r--r-- | test/e2e/custom-assertions/elementCount.js | 26 | ||||
| -rw-r--r-- | test/e2e/nightwatch.conf.js | 46 | ||||
| -rw-r--r-- | test/e2e/runner.js | 31 | ||||
| -rw-r--r-- | test/e2e/specs/test.js | 19 | ||||
| -rw-r--r-- | test/unit/.eslintrc | 9 | ||||
| -rw-r--r-- | test/unit/index.js | 13 | ||||
| -rw-r--r-- | test/unit/karma.conf.js | 75 | ||||
| -rw-r--r-- | test/unit/specs/Hello.spec.js | 13 |
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 + } +} @@ -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']) @@ -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 @@ -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, + from: '[email protected]', + 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 Binary files differindex 148d0c0b..148d0c0b 100644 --- a/public/img/bitcoin.png +++ b/static/img/bitcoin.png diff --git a/public/img/blockchain.png b/static/img/blockchain.png Binary files differindex 1d620cbe..1d620cbe 100644 --- a/public/img/blockchain.png +++ b/static/img/blockchain.png diff --git a/public/img/coinos_logo.png b/static/img/coinos_logo.png Binary files differindex d95b54e3..d95b54e3 100644 --- a/public/img/coinos_logo.png +++ b/static/img/coinos_logo.png 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 Binary files differindex e8aac8a4..e8aac8a4 100644 --- a/public/img/favicon.ico +++ b/static/img/favicon.ico diff --git a/public/img/forever.gif b/static/img/forever.gif Binary files differindex ac64bf51..ac64bf51 100644 --- a/public/img/forever.gif +++ b/static/img/forever.gif diff --git a/public/img/glyphicons-halflings-white.png b/static/img/glyphicons-halflings-white.png Binary files differindex 3bf6484a..3bf6484a 100644 --- a/public/img/glyphicons-halflings-white.png +++ b/static/img/glyphicons-halflings-white.png diff --git a/public/img/glyphicons-halflings.png b/static/img/glyphicons-halflings.png Binary files differindex a9969993..a9969993 100644 --- a/public/img/glyphicons-halflings.png +++ b/static/img/glyphicons-halflings.png diff --git a/public/img/qr.png b/static/img/qr.png Binary files differindex c17101e7..c17101e7 100644 --- a/public/img/qr.png +++ b/static/img/qr.png diff --git a/public/img/village_books.png b/static/img/village_books.png Binary files differindex 3743f069..3743f069 100644 --- a/public/img/village_books.png +++ b/static/img/village_books.png 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 Binary files differindex 21646005..21646005 100644 --- a/public/success.wav +++ b/static/success.wav 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') + }) +}) |
