diff --git a/.babelrc b/.babelrc index b7ba315..342a80c 100644 --- a/.babelrc +++ b/.babelrc @@ -1,15 +1,31 @@ { "env": { - "server": { + "node": { "presets": [ "es2015-node6" ], "plugins": [ "add-module-exports" ] }, - "client": { - "presets": [ "es2015", "react" ], + "node-jsx": { + "presets": [ "es2015-node6" ], "plugins": [ - "add-module-exports" + "add-module-exports", + ["transform-react-jsx", { "pragma": "h" }], + ["jsx-node/babel/alias", { + "lowline": "lodash", + "preact": "jsx-node" + }], + ["jsx-node/babel/stringify"] + ] + }, + "rollup": { + "include": [ + "node_modules/preact/**", + "client/**" + ], + "presets": [ "es2015-rollup" ], + "plugins": [ + ["transform-react-jsx", { "pragma":"h" }] ] } } diff --git a/.eslintrc b/.eslintrc index ca97434..709de85 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,52 +1,14 @@ { - "extends": "airbnb-base", - - "parserOptions": { - "sourceType": "strict" - }, - - "env": { - "mocha": true - }, - "rules": { - "semi": [ 2, "never" ], - "import/no-extraneous-dependencies": ["error", {"devDependencies": true, "optionalDependencies": false, "peerDependencies": false}], - "new-cap": 0, - "no-mixed-operators": 0, - "no-cond-assign": 0, - "guard-for-in": 0, - "global-require": 0, - "no-underscore-dangle": 0, - "object-shorthand": 0, - "default-case": 0, - "one-var": 0, - "prefer-rest-params": 0, - "no-unused-vars": [ 2, { "args": "none" } ], - "no-alert": 0, - "quote-props": 0, - "no-nested-ternary": 0, - "no-use-before-define": [2, { "functions": false, "classes": true }], - "consistent-return": 0, - "no-eval": 0, - "prefer-arrow-callback": 0, - "array-bracket-spacing": 0, - "no-console": 0, - "indent": [ 2, 2, { "SwitchCase": 1 }], - "max-len": 0, - "comma-dangle": 0, - "no-param-reassign": 0, - "prefer-template": 0, - "curly": 0, + "arrow-parens": [2, "always"], + "class-methods-use-this": 0, "func-names": 0, + "import/no-unresolved": [2, { "ignore": ["^[^./]"] }], + "no-console": [1, { "allow": ["warn", "error", "info"] }], + "no-empty": [2, { "allowEmptyCatch": true }], + "no-mixed-operators": 0, + "no-param-reassign": 0, "no-shadow": 0, - "spaced-comment": 0, - "strict": [ 2, "global" ] - }, - - "globals": { - "PWD": true, - "ENV": true, - "location": true + "no-underscore-dangle": 0 } } diff --git a/.gitignore b/.gitignore index 76772cd..0aa8205 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,9 @@ npm-debug.log # Bower dependency directory bower_components -*.marko.js +.tern-port + +/build /dump diff --git a/client/.eslintrc b/client/.eslintrc index 2349eb6..5601a6f 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -1,49 +1,25 @@ { - "extends": "airbnb", + "extends": [ + "airbnb", + "../.eslintrc" + ], "rules": { - "semi": [ 2, "never" ], - "no-confusing-arrow": 0, + "react/react-in-jsx-scope": 0, "react/prop-types": 0, - "guard-for-in": 0, - "global-require": 0, - "no-underscore-dangle": 0, - "object-shorthand": 0, - "default-case": 0, - "one-var": 0, - "prefer-rest-params": 0, - "no-unused-vars": [ 2, { "args": "none" } ], - "no-alert": 0, - "quote-props": 0, - "no-nested-ternary": 0, - "no-use-before-define": [2, { "functions": false, "classes": true }], - "consistent-return": 0, - "no-eval": 0, - "prefer-arrow-callback": 0, - "array-bracket-spacing": 0, - "no-console": 0, - "indent": [ 2, 2, { "SwitchCase": 1 }], - "max-len": 0, - "comma-dangle": 0, - "no-param-reassign": 0, - "prefer-template": 0, - "curly": 0, - "func-names": 0, - "no-shadow": 0, - "spaced-comment": 0, - "strict": [ 2, "global" ] + "react/no-unknown-property": 0, + "jsx-a11y/label-has-for": 0 }, - + "env": { "browser": true }, "globals": { - "ENV": true, - "google": true, - "INITIAL_STATE": true, - "INITIAL_CONTEXT": true, - "PWD": true, - "$": true - } + "ga": true, + "$": true, + "_": true, + "Backbone": true, + "INITIAL_CONTEXT": true + } } diff --git a/client/app.js b/client/app.js deleted file mode 100644 index 0d71957..0000000 --- a/client/app.js +++ /dev/null @@ -1,20 +0,0 @@ -import { $ } from 'dollr' - -import './process' - -import Timer from './components/Timer' - -$(() => { - // request permission on page load - if (!Notification) { - alert('Desktop notifications not available in your browser. Try Chromium, Chrome or Firefox.') - return - } - - if (Notification.permission !== 'granted') - Notification.requestPermission() - - const timer = $('.timer') - - Timer.render().replace(timer).getWidget() -}) diff --git a/client/app.jsx b/client/app.jsx new file mode 100644 index 0000000..a8e237f --- /dev/null +++ b/client/app.jsx @@ -0,0 +1,15 @@ +import { h, render } from 'preact'; +import Layout from './components/Layout.jsx'; + +// request permission on page load +if (!Notification) { + alert('Desktop notifications not available in your browser. Try Chromium, Chrome or Firefox.'); +} + +if (Notification.permission !== 'granted') { + Notification.requestPermission(); +} + +render(( + +), null, document.getElementById('app')); diff --git a/client/components/Layout.jsx b/client/components/Layout.jsx new file mode 100644 index 0000000..4387010 --- /dev/null +++ b/client/components/Layout.jsx @@ -0,0 +1,12 @@ +import { h } from 'preact'; + +import Timer from './Timer.jsx'; + +export default () => ( +
+
+

Pomodoro Time!

+ +
+
+); diff --git a/client/components/Timer.jsx b/client/components/Timer.jsx new file mode 100644 index 0000000..3a31efd --- /dev/null +++ b/client/components/Timer.jsx @@ -0,0 +1,109 @@ +import { h, Component } from 'preact'; + +import { bindAll } from 'lowline'; + +import timeFilter from '../util/time-filter'; + +const length = 25 * 60 * 1000; + +export default class Timer extends Component { + constructor() { + super(); + + this.state = { + time: length, + totalTime: length, + startTime: undefined, + }; + + bindAll(this, ['start', 'pause', 'reset', 'updateTimer', 'end']); + } + + componentDidMount() { + this.reset(); + } + + start() { + this.setState({ + startTime: Date.now(), + }); + + this.interval = setInterval(this.updateTimer.bind(this), 10); + } + + pause() { + clearInterval(this.interval); + } + + reset() { + clearInterval(this.interval); + + this.setState({ + time: length, + totalTime: length, + startTime: undefined, + }); + } + + updateTimer() { + let time = this.state.totalTime - (Date.now() - this.state.startTime); + + if (time <= 0) { + this.pause(); + + this.end(); + + time = 0; + } + + document.title = timeFilter(time).slice(0, 5); + + this.setState({ + time, + }); + } + + end() { + const data = { + startTime: this.state.startTime, + endTime: new Date(), + }; + + document.title = 'Pling!'; + + if (Notification.permission !== 'granted') { + Notification.requestPermission(); + } else { + const notification = new Notification('Pomodoro complete!', { + icon: 'https://www.planetnatural.com/wp-content/uploads/2014/03/tomato-supplies.jpg', + body: 'Time for a break!', + }); + } + + fetch('/api/pomodoros', { + method: 'POST', + body: JSON.stringify(data), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }) + .then((response) => response.json()) + .then((json) => { + console.log('succes'); + }); + } + + render(props, state) { + return ( +
+
{timeFilter(state.time || 0)}
+
+ + + +
+
+ ); + } +} diff --git a/client/components/Timer/index.js b/client/components/Timer/index.js deleted file mode 100644 index d5b0ffd..0000000 --- a/client/components/Timer/index.js +++ /dev/null @@ -1,93 +0,0 @@ -import markoWidgets from 'marko-widgets' -import template from './template.marko' - -import timeFilter from '../../util/time-filter' - -const length = 25 * 60 * 1000 - -export default markoWidgets.defineComponent({ - template, - - componentDidMount() { - this.reset() - }, - - start(e) { - this.setState({ - startTime: Date.now() - }) - - this.interval = setInterval(this.updateTimer.bind(this), 10) - }, - - pause(e) { - clearInterval(this.interval) - }, - - reset(e) { - clearInterval(this.interval) - - this.setState({ - time: length, - totalTime: length, - startTime: undefined - }) - }, - - updateTimer() { - let time = this.state.totalTime - (Date.now() - this.state.startTime) - - if (time <= 0) { - this.pause() - - this.end() - - time = 0 - } - - document.title = timeFilter(time).slice(0, 5) - - this.setState({ - time - }) - }, - - end(time) { - const data = { - startTime: this.state.startTime, - endTime: new Date() - } - - document.title = 'Pling!' - - if (Notification.permission !== 'granted') - Notification.requestPermission() - else { - const notification = new Notification('Pomodoro complete!', { - icon: 'https://www.planetnatural.com/wp-content/uploads/2014/03/tomato-supplies.jpg', - body: 'Time for a break!', - }) - } - - fetch('/api/pomodoros', { - method: 'POST', - body: JSON.stringify(data), - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }) - .then((response) => response.json()) - .then((json) => { - console.log('succes') - }) - }, - - getInitialState(input) { - return { - time: length, - totalTime: length, - startTime: undefined - } - } -}) diff --git a/client/components/Timer/template.marko b/client/components/Timer/template.marko deleted file mode 100644 index b4e394a..0000000 --- a/client/components/Timer/template.marko +++ /dev/null @@ -1,11 +0,0 @@ - -
-
${time(data.time || 0)}
-
- - - -
-
diff --git a/client/error.jsx b/client/error.jsx new file mode 100644 index 0000000..1aff65f --- /dev/null +++ b/client/error.jsx @@ -0,0 +1,5 @@ +export default function () { +
+

Error!

+
+} diff --git a/client/error.marko b/client/error.marko deleted file mode 100644 index e183c9c..0000000 --- a/client/error.marko +++ /dev/null @@ -1,3 +0,0 @@ -
-

Error!

-
diff --git a/client/marko.json b/client/marko.json deleted file mode 100644 index 30247b3..0000000 --- a/client/marko.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "tags-dir": [ "./components" ] -} diff --git a/client/master.jsx b/client/master.jsx new file mode 100644 index 0000000..eac0c3a --- /dev/null +++ b/client/master.jsx @@ -0,0 +1,25 @@ +import Layout from './components/Layout.jsx'; + +export default ({ css, js }) => ( + '' + ( + + + Pomodoro + + + + + + + + + + + + + + - - diff --git a/client/process.js b/client/process.js deleted file mode 100644 index 52f3b4b..0000000 --- a/client/process.js +++ /dev/null @@ -1,7 +0,0 @@ -window.process = { - browser: true, - env: {}, - nextTick: (fnc) => { - setTimeout(fnc) - } -} diff --git a/client/routes.js b/client/routes.js deleted file mode 100644 index 8144921..0000000 --- a/client/routes.js +++ /dev/null @@ -1,45 +0,0 @@ -export default { - '': { - name: '', - //view: PageView, - template: require('./pages/index.marko'), - //subviews: { - // newsletterForm: [ '.newsletter', require('./views/newsletter-form') ] - //} - widget: require('./pages/index') - }, - - 'samarbetspartners': { - //view: 'Page' - }, - - 'om-oss': { - name: 'om-oss', - template: require('./pages/om-oss.marko'), - //view: PageView, - widget: require('./pages/om-oss') - }, - - 'nyhetsbrev': { - name: 'nyhetsbrev', - template: require('./pages/nyhetsbrev.marko'), - //view: PageView, - widget: require('./pages/nyhetsbrev'), - //subviews: { - // newsletterForm: [ '.newsletter-form', require('./components/newsletter-form/view') ] - //}, - }, - - - 'kontakt': { - name: 'kontakt', - //view: PageView, - template: require('./pages/kontakt.marko'), - widget: require('./pages/kontakt'), - //subviews: { - // googleMap: [ '#map-canvas', require('./views/google-map') ], - // contactForm: [ '.contact-form', require('./components/contact-form/view') ], - // newsletterForm: [ '.nyhetsbrev', require('./views/newsletter-form') ] - //} - } -}; diff --git a/client/util/split-time.js b/client/util/split-time.js index 5ec196f..a77c992 100644 --- a/client/util/split-time.js +++ b/client/util/split-time.js @@ -1,17 +1,17 @@ -const divs = [ 60, 100, 10 ] +const divs = [60, 100, 10]; export default function (time) { - const arr = [] + const arr = []; for (let i = 0; i < divs.length; i++) { - const nbr = divs.slice(i).reduce((a, b) => a * b) + const nbr = divs.slice(i).reduce((a, b) => a * b); - const result = Math.floor(time / nbr) + const result = Math.floor(time / nbr); - arr.push(result) + arr.push(result); - time = time - result * nbr - //this.timerElements[i].textContent = result + time = time - result * nbr; + // this.timerElements[i].textContent = result } - return arr + return arr; } diff --git a/client/util/time-filter.js b/client/util/time-filter.js index eb6e67f..90883c1 100644 --- a/client/util/time-filter.js +++ b/client/util/time-filter.js @@ -1,8 +1,8 @@ -import splitTime from './split-time' -import twoDigits from './two-digits' +import splitTime from './split-time'; +import twoDigits from './two-digits'; export default function (time) { - const arr = splitTime(time).map(twoDigits) + const arr = splitTime(time).map(twoDigits); - return arr.join(':') + return arr.join(':'); } diff --git a/client/util/two-digits.js b/client/util/two-digits.js index b8500b6..87b7ffc 100644 --- a/client/util/two-digits.js +++ b/client/util/two-digits.js @@ -1,3 +1,3 @@ export default function twoDigits(val) { - return val >= 10 ? val : '0' + val + return val >= 10 ? val : `0${val}`; } diff --git a/gulp b/gulp index f69b449..349bb1f 160000 --- a/gulp +++ b/gulp @@ -1 +1 @@ -Subproject commit f69b4494ab2bc55edb09d503396f94bf8b457496 +Subproject commit 349bb1f3a91a4675b5ec3e8b907d8ae2daf39129 diff --git a/gulpconfig.js b/gulpconfig.js new file mode 100644 index 0000000..d682633 --- /dev/null +++ b/gulpconfig.js @@ -0,0 +1,13 @@ +'use strict'; + +// modules > native +module.exports = { + rollup: { + entries: [ + 'client/app.jsx', + ], + outputs: [ + 'app.js', + ], + }, +}; diff --git a/gulpfile.js b/gulpfile.js index 4c7951b..0f1e7d6 120000 --- a/gulpfile.js +++ b/gulpfile.js @@ -1 +1 @@ -gulp/index.js \ No newline at end of file +gulp/gulpfile.js \ No newline at end of file diff --git a/package.json b/package.json index 517975c..4151643 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "budbilar", + "name": "pomodoro", "version": "0.0.1", "description": "När du vill få något budat till rätt pris.", "main": "server/server.js", "private": true, "repository": { "type": "git", - "url": "git@gitlab.thecodebureau.com:axel/budbilar.git" + "url": "git@gitlab.bitmill.co:lohfu/pomodoro.git" }, "scripts": { "gulp": "gulp", @@ -16,67 +16,30 @@ "start": "node server/server.js" }, "dependencies": { - "babel-preset-es2015-node6": "^0.3.0", - "babel-register": "^6.14.0", + "babel-preset-es2015-node6": "^0.4.0", + "babel-register": "^6.18.0", "body-parser": "^1.15.2", "chalk": "^1.1.3", "connect-redis": "^3.1.0", "cookie-parser": "^1.4.3", - "dollr": "0.0.9", + "dollr": "^0.1.1", "express": "^4.14.0", "express-service-errors": "github:thecodebureau/express-service-errors", - "express-session": "^1.14.1", - "lodash": "^4.15.0", - "marko": "^3.10.1", - "marko-widgets": "^6.3.4", - "midwest": "0.0.2", - "midwest-module-membership": "github:thebitmill/midwest-module-membership", - "midwest-service-errors": "github:thebitmill/midwest-service-errors", - "mongoose": "^4.6.0", + "express-session": "^1.14.2", + "hyperscript-jsx": "0.0.2", + "jsx-node": "^0.1.0", + "lodash": "^4.16.6", + "lowline": "^0.1.1", + "midwest": "^0.2.1", + "midwest-module-membership": "^0.1.0", + "midwest-service-errors": "^0.0.3", + "mongoose": "^4.6.6", "mongopot": "github:lohfu/mongopot", "morgan": "^1.7.0", - "nodemailer": "^2.6.0", + "nodemailer": "^2.6.4", "passport": "^0.3.2", "passport-local": "^1.0.0", - "require-dir": "^0.3.0" - }, - "devDependencies": { - "autoprefixer": "^6.4.1", - "babel-plugin-add-module-exports": "^0.2.1", - "babel-preset-es2015": "^6.14.0", - "babel-preset-react": "^6.11.1", - "browser-sync": "^2.16.0", - "chalk": "^1.1.3", - "csswring": "^5.1.0", - "eslint": "^3.5.0", - "eslint-config-airbnb": "^11.1.0", - "eslint-config-airbnb-base": "^7.1.0", - "eslint-plugin-import": "^1.15.0", - "eslint-plugin-jsx-a11y": "^2.2.2", - "eslint-plugin-react": "^6.2.2", - "event-stream": "^3.3.4", - "gulp": "github:gulpjs/gulp#4.0", - "gulp-less": "^3.1.0", - "gulp-postcss": "^6.2.0", - "gulp-rename": "^1.2.2", - "gulp-sourcemaps": "^1.6.0", - "gulp-svgmin": "^1.2.2", - "gulp-uglify": "^2.0.0", - "gulp-util": "^3.0.7", - "marko": "^3.10.1", - "mkdirp": "^0.5.1", - "nodemon": "^1.10.2", - "postcss": "^5.2.0", - "pretty-hrtime": "^1.0.2", - "rimraf": "^2.5.4", - "rollup": "^0.35.10", - "rollup-plugin-babel": "^2.6.1", - "rollup-plugin-commonjs": "^3.3.0", - "rollup-plugin-marko": "0.0.2", - "rollup-plugin-node-resolve": "^2.0.0", - "rollup-plugin-replace": "^1.1.1", - "through2": "^2.0.1", - "vinyl-buffer": "^1.0.0", - "vinyl-source-stream": "^1.1.0" + "preact": "^6.4.0", + "require-dir": "^0.3.1" } } diff --git a/server/.eslintrc b/server/.eslintrc new file mode 100644 index 0000000..ae582ee --- /dev/null +++ b/server/.eslintrc @@ -0,0 +1,24 @@ +{ + "extends": [ + "airbnb-base", + "../.eslintrc" + ], + + "parserOptions": { + "sourceType": "strict" + }, + + "env": { + "node": true + }, + + "rules": { + "global-require": 0 + }, + + "globals": { + "ENV": true, + "LOGIN_USER": true, + "PWD": true + } +} diff --git a/server/config/dir.js b/server/config/dir.js index 20c1347..1ad5a4b 100644 --- a/server/config/dir.js +++ b/server/config/dir.js @@ -1,7 +1,7 @@ -'use strict' +'use strict'; -const p = require('path') +const p = require('path'); module.exports = { static: p.join(PWD, 'public'), -} +}; diff --git a/server/config/error-handler.js b/server/config/error-handler.js index 938fbdf..0419b55 100644 --- a/server/config/error-handler.js +++ b/server/config/error-handler.js @@ -1,47 +1,47 @@ -'use strict' +'use strict'; -const _ = require('lodash') +const _ = require('lodash'); -const errorTemplate = require('../templates/error.marko') +const errorTemplate = require('../../build/error.jsx'); const defaults = { post: (req, res, next) => { - res.template = errorTemplate + res.template = errorTemplate; - next() + next(); }, mystify: { - properties: ['errors', 'message', 'name', 'status', 'statusText'] + properties: ['errors', 'message', 'name', 'status', 'statusText'], }, log: { // if database = true there has to be a mongoose model name ErrorModel ignore: [], - } -} + }, +}; -const ErrorModel = require('midwest-service-errors/model') +const ErrorModel = require('midwest-service-errors/model'); function store(error) { ErrorModel.create(error, (err) => { // TODO handle errors in error handler better if (err) { - console.error('ERROR WRITING TO DATABASE') - console.error(err) - console.log(err.errors) - console.error('ORIGINAL ERROR') - console.error(error) + console.error('ERROR WRITING TO DATABASE'); + console.error(err); + console.error(err.errors); + console.error('ORIGINAL ERROR'); + console.error(error); } - }) + }); } module.exports = _.merge(defaults, { development: { log: { - store: store, + store, console: true, - } + }, }, testing: { log: { @@ -51,8 +51,8 @@ module.exports = _.merge(defaults, { }, production: { log: { - store: store, + store, console: false, - } + }, }, -}[ENV]) +}[ENV]); diff --git a/server/config/globals.js b/server/config/globals.js index c6c45c0..4cfdf48 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; -global.LOGIN_USER = 'lohfu@lohfu.io' -//global.LOGIN_USER = 'zarac@zarac.se' +global.LOGIN_USER = 'lohfu@lohfu.io'; +// global.LOGIN_USER = 'zarac@zarac.se' diff --git a/server/config/membership.js b/server/config/membership.js index d9b236e..2c0f2f0 100644 --- a/server/config/membership.js +++ b/server/config/membership.js @@ -1,39 +1,40 @@ -'use strict' +'use strict'; const config = { - site: require('./site') -} + site: require('./site'), +}; module.exports = { invite: { - from: config.site.title + ' Robot <' + config.site.emails.robot + '>', - subject: 'You have been invited to ' + config.site.title + from: `${config.site.title} Robot <${config.site.emails.robot}>`, + subject: `You have been invited to ${config.site.title}`, }, timeouts: { // 1 day changePassword: 24 * 60 * 60 * 1000, // verify email - verifyEmail: 7 * 24 * 60 * 60 * 1000 + verifyEmail: 7 * 24 * 60 * 60 * 1000, }, paths: { - register: '/register', - login: '/login', - forgotPassword: '/forgot-password', - updatePassword: '/change-password' + register: '/admin/register', + login: '/admin/login', + forgotPassword: '/admin/forgot-password', + updatePassword: '/admin/change-password', + verifyEmail: '/admin/verify-email', }, redirects: { login: '/admin', logout: '/', - register: '/admin' + register: '/admin', }, remember: { // if expires is defined, it will be used. otherwise maxage expires: new Date('2038-01-19T03:14:07.000Z'), - //expires: Date.now() - 1, + // expires: Date.now() - 1, maxAge: 30 * 24 * 60 * 60 * 1000, }, @@ -46,22 +47,22 @@ module.exports = { externalLoginFailed: 'External login failed.', unverified: 'This account has not been verified.', banned: 'User is banned.', - blocked: 'User is blocked due to too many login attempts.' + blocked: 'User is blocked due to too many login attempts.', }, register: { missingProperties: 'Oh no missing stuff', notAuthorized: 'The email is not authorized to create an account.', - duplicateEmail: 'The email has already been registered.' - } + duplicateEmail: 'The email has already been registered.', + }, }, passport: { local: { - usernameField: 'email' + usernameField: 'email', }, - scope: [ 'email' ], + scope: ['email'], //providers: { // facebook: { @@ -76,5 +77,5 @@ module.exports = { // needs to be even tokenLength: 64, // needs to be even - saltLength: 16 -} + saltLength: 16, +}; diff --git a/server/config/mongo.js b/server/config/mongo.js index 747a25b..2549856 100644 --- a/server/config/mongo.js +++ b/server/config/mongo.js @@ -1,11 +1,11 @@ -'use strict' +'use strict'; const defaults = { - uri: 'mongodb://pomodoro-supreme:lets-work-our-asses-off-poop-poop@mongo.bitmill.co/pomodoro' -} + uri: 'mongodb://pomodoro-supreme:lets-work-our-asses-off-poop-poop@mongo.bitmill.co/pomodoro', +}; module.exports = Object.assign(defaults, { production: { - //uri: 'mongodb://pomodoro-supreme:lets-work-our-asses-off-poop-poop@localhost/pomodoro' - } -}[ENV]) + // uri: 'mongodb://pomodoro-supreme:lets-work-our-asses-off-poop-poop@localhost/pomodoro' + }, +}[ENV]); diff --git a/server/config/port.js b/server/config/port.js index dcb3848..3c517ed 100644 --- a/server/config/port.js +++ b/server/config/port.js @@ -1,10 +1,10 @@ -'use strict' +'use strict'; -const basePort = 3050 +const basePort = 3050; module.exports = { development: basePort, testing: basePort + 1, staging: basePort + 2, - production: basePort + 3 -}[ENV] + production: basePort + 3, +}[ENV]; diff --git a/server/config/session.js b/server/config/session.js index cb50cc2..7daf198 100644 --- a/server/config/session.js +++ b/server/config/session.js @@ -1,37 +1,38 @@ -'use strict' +'use strict'; -const session = require('express-session') +const chalk = require('chalk'); +const session = require('express-session'); -let redisStore +let redisStore; const config = { - secret: 'asdf1h918798&(*&ijh21kj4hk123j45h2k34jh52k3g45)thisisacompletelyrandomgeneratedstring...whatastrangecoincidence...', + secret: 'sometimespoopfeelslikeheaveninawarmbun.:L:AKJSHFKJh12349087ashabbasbasbbasdbfas w', resave: false, - saveUninitialized: true -} + saveUninitialized: true, +}; const redisConfig = { host: 'localhost', - port: 6379 -} + port: 6379, +}; if (ENV === 'production') { - const RedisStore = require('connect-redis')(require('express-session')) + const RedisStore = require('connect-redis')(require('express-session')); - redisStore = new RedisStore(redisConfig) + redisStore = new RedisStore(redisConfig); - redisStore.on('connect', function () { - console.info('Redis connected succcessfully') - }) + redisStore.on('connect', () => { + console.info(`[${chalk.cyan('INIT')}] Redis connected succcessfully`); + }); - redisStore.on('disconnect', function () { - throw new Error('Unable to connect to redis. Has it been started?') - }) + redisStore.on('disconnect', () => { + throw new Error('Unable to connect to redis. Has it been started?'); + }); - config.store = redisStore + config.store = redisStore; } else { - config.store = new session.MemoryStore() + config.store = new session.MemoryStore(); } -module.exports = config +module.exports = config; diff --git a/server/config/shim.js b/server/config/shim.js new file mode 100644 index 0000000..a6f1fc9 --- /dev/null +++ b/server/config/shim.js @@ -0,0 +1,59 @@ +'use strict'; + +module.exports = { + 'https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.min.js': [ + 'chrome <= 12', + 'chrome mobile <= 12', + 'chrome mobile ios <= 12', + 'firefox <= 20', + 'firefox mobile <= 20', + 'ie <= 9', + 'ie mobile <= 9', + 'opera <= 12', + 'safari <= 5', + 'mobile safari <= 5', + 'samsung internet <= 1', + ], + 'https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.min.js': [ + 'chrome <= 51', + 'chrome mobile <= 51', + 'chrome mobile ios <= 51', + 'edge <= 13', + 'edge mobile <= 13', + 'firefox <= 44', + 'firefox mobile <= 44', + 'ie <= 11', + 'ie mobile <= 11', + 'safari <= 9', + 'mobile safari <= 9', + 'samsung internet <= 2', + ], + 'https://unpkg.com/es7-shim@latest/dist/es7-shim.min.js': [ + 'chrome <= 50', + 'chrome mobile <= 50', + 'chrome mobile ios <= 50', + 'edge <= 14', + 'edge mobile <= 14', + 'firefox <= 46', + 'firefox mobile <= 46', + 'ie <= 11', + 'ie mobile <= 11', + 'safari <= 10', + 'mobile safari <= 10', + 'samsung internet <= 3', + ], + 'https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js': [ + 'chrome <= 41', + 'chrome mobile <= 41', + 'chrome mobile ios <= 41', + 'edge <= 13', + 'edge mobile <= 13', + 'firefox <= 38', + 'firefox mobile <= 38', + 'ie <= 11', + 'ie mobile <= 11', + 'safari <= 9', + 'mobile safari <= 9', + 'samsung internet <= 3', + ], +}; diff --git a/server/config/site.js b/server/config/site.js index 83ac59d..d8393c6 100644 --- a/server/config/site.js +++ b/server/config/site.js @@ -1,51 +1,51 @@ -'use strict' +'use strict'; -const _ = require('lodash') +const _ = require('lodash'); -const domain = 'pomodoro.bitmill.co' +const domain = 'pomodoro.bitmill.co'; const defaults = { - domain: domain, + domain, title: 'Pomodoro', - name: 'pomodoro', + name: 'pomdoro', protocol: 'http', get host() { - return this.port ? this.hostname + ':' + this.port : this.hostname + return this.port ? `${this.hostname}:${this.port}` : this.hostname; }, get url() { - return this.protocol + '://' + this.host + '/' + return `${this.protocol}://${this.host}/`; }, emails: { robot: 'no-reply@thecodebureau.com', info: 'info@thecodebureau.com', webmaster: 'webmaster@thecodebureau.com', - order: 'info@thecodebureau.com' - } -} + order: 'info@thecodebureau.com', + }, +}; module.exports = _.merge(defaults, { development: { hostname: 'localhost', - port: process.env.EXTERNAL_PORT || process.env.PORT || require('./port') + port: process.env.EXTERNAL_PORT || process.env.PORT || require('./port'), }, testing: { hostname: 'localhost', - port: process.env.PORT || require('./port') + port: process.env.PORT || require('./port'), }, staging: { - hostname: 'staging.' + domain + hostname: 'staging.pomodoro.bitmill.co', }, production: { hostname: domain, - protocol: 'https', - emails: { - robot: 'no-reply@bitmill.co', - info: 'info@bitmill.co', - webmaster: 'webmaster@bitmill.co', - order: 'order@bitmill.co' - } - } -}[ENV]) + // protocol: 'https', + // emails: { + // robot: `no-reply@${domain}`, + // info: `info@${domain}`, + // webmaster: `webmaster@${domain}`, + // order: `order@${domain}`, + // }, + }, +}[ENV]); diff --git a/server/config/smtp.js b/server/config/smtp.js index 44763e1..8dadf7c 100644 --- a/server/config/smtp.js +++ b/server/config/smtp.js @@ -1,15 +1,15 @@ -'use strict' +'use strict'; -const _ = require('lodash') +const _ = require('lodash'); const defaults = { auth: { user: 'SMTP_Injection', // dev key - pass: '2eec390c5b3f5d593c9f152179bf51e90b073784' + pass: '2eec390c5b3f5d593c9f152179bf51e90b073784', }, host: 'smtp.sparkpostmail.com', - port: 587 -} + port: 587, +}; -module.exports = _.merge(defaults, {}[ENV]) +module.exports = _.merge(defaults, {}[ENV]); diff --git a/server/pages.js b/server/pages.js deleted file mode 100644 index c2197e3..0000000 --- a/server/pages.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' - -const masterTemplate = require('../client/master.marko') - -const router = new (require('express')).Router() - -router.get('/', (req, res, next) => { - res.template = masterTemplate - - next() -}) - -module.exports = router diff --git a/server/render.jsx b/server/render.jsx new file mode 100644 index 0000000..2a48ccd --- /dev/null +++ b/server/render.jsx @@ -0,0 +1,31 @@ +module.exports = function (Component, Master) { + const locals = Object.assign({}, this.app.locals, this.locals); + + let preamble = ''; + let html; + + if (typeof Master === 'function') { + preamble = ''; + + if (typeof Component === 'function') { + return this.send(preamble + ( + + + + )); + } + + Component = Master; + } + + if (typeof Component !== 'function') { + throw new Error('Not a Component'); + } else if (Component.prototype && Component.prototype.render) { + const instance = new Component(locals); + html = instance.render(instance.props, instance.state); + } else { + html = Component(locals); + } + + this.send(preamble ? preamble + html : html); +}; diff --git a/server/routers/index.js b/server/routers/index.js new file mode 100644 index 0000000..b5911d1 --- /dev/null +++ b/server/routers/index.js @@ -0,0 +1,13 @@ +'use strict'; + +const masterTemplate = require('../../build/master.jsx'); + +const router = new (require('express')).Router(); + +router.get('/', (req, res, next) => { + res.template = masterTemplate; + + next(); +}); + +module.exports = router; diff --git a/server/server.js b/server/server.js index 47ccf8e..dc684b3 100644 --- a/server/server.js +++ b/server/server.js @@ -1,6 +1,5 @@ -'use strict' +'use strict'; -require('babel-register') /* * The main file that sets up the Express instance and node * @@ -9,58 +8,55 @@ require('babel-register') */ // set up some globals (these are also set in Epiphany if not already set) -global.ENV = process.env.NODE_ENV || 'development' -global.PWD = process.env.NODE_PWD || process.cwd() - -// make node understand `*.marko` files -require('marko/node-require').install() +global.ENV = process.env.NODE_ENV || 'development'; +global.PWD = process.env.NODE_PWD || process.cwd(); // modules > native -const p = require('path') +const p = require('path'); + +if (ENV === 'development') { + // output filename in console log and colour console.dir + require('midwest/util/console'); + // needed so symlinked modules get access to main projects node_modules/ + require('app-module-path').addPath(p.join(PWD, 'node_modules')); +} + +// make node understand `*.jsx` files +require('jsx-node').install({ + replace: { + lowline: 'lodash', + preact: 'jsx-node', + }, +}); // modules > 3rd party -const requireDir = require('require-dir') -const _ = require('lodash') -const express = require('express') -const mongoose = require('mongoose') -const passport = require('passport') +const _ = require('lodash'); +const chalk = require('chalk'); +const express = require('express'); +const mongoose = require('mongoose'); +const passport = require('passport'); +const requireDir = require('require-dir'); // modules > express middlewares -const bodyParser = require('body-parser') -const session = require('express-session') -const cookieParser = require('cookie-parser') - -// modules > 3rd party -const chalk = require('chalk') +const bodyParser = require('body-parser'); +const session = require('express-session'); +const cookieParser = require('cookie-parser'); // modules > midwest -const colorizeStack = require('midwest/util/colorize-stack') +const colorizeStack = require('midwest/util/colorize-stack'); -const config = requireDir('./config') +const config = requireDir('./config', { camelcase: true }); // make error output stack pretty process.on('uncaughtException', (err) => { - console.error(chalk.red('UNCAUGHT EXCEPTION')) + console.error(chalk.red('UNCAUGHT EXCEPTION')); if (err.stack) { - console.error(colorizeStack(err.stack)) + console.error(colorizeStack(err.stack)); } else { - console.error(err) + console.error(err); } - process.exit(1) -}) - -// mongoose mpromise library is being deprecated -mongoose.Promise = Promise - -// connect to mongodb -mongoose.connect(config.mongo.uri, _.omit(config.mongo, 'uri'), (err) => { - if (err) { - console.error(err) - process.exit() - } - - console.info('[' + chalk.cyan('INIT') + '] Mongoose is connected.') -}) + process.exit(1); +}); const prewares = [ express.static(config.dir.static, ENV === 'production' ? { maxAge: '1 year' } : null), @@ -70,70 +66,86 @@ const prewares = [ session(config.session), passport.initialize(), passport.session(), -] +]; if (ENV === 'development') { // only log requests to console in development mode - prewares.unshift(require('morgan')('dev')) - prewares.push(require('midwest-module-membership/passport/automatic-login')) + prewares.unshift(require('morgan')('dev')); + // automatically login global.LOGIN_USER + prewares.push(require('midwest-module-membership/passport/automatic-login')); } const postwares = [ require('midwest/middleware/ensure-found'), // transform and log error - require('midwest/middleware/error-handler'), + require('midwest/factories/error-handler')(config.errorHandler), // respond require('midwest/middleware/responder'), -] +]; -const server = express() +const server = express(); // get IP & whatnot from nginx proxy -server.set('trust proxy', true) +server.set('trust proxy', true); -server.locals.site = require('./config/site') - -if (ENV === 'production') { - Object.assign(server.locals, { - js: require(p.join(PWD, 'public/js.json')), - css: require(p.join(PWD, 'public/css.json')) - }) -} +Object.assign(server.locals, { + site: require('./config/site'), +}); // override default response render method for -// more convenient use with marko -server.response.render = function (template) { - const locals = _.extend({}, server.locals, this.locals) +// more convenient use with jsx +server.response.render = require('./render.jsx'); - template.render(locals, this) -} +try { + server.locals.js = require(p.join(PWD, 'public/js.json')); +} catch (e) {} + +try { + server.locals.css = require(p.join(PWD, 'public/css.json')); +} catch (e) {} // load all prewares -server.use(...prewares) +server.use(...prewares); // routes > pages -server.use(require('./pages')) +server.use(require('./routers/index')); // routes > authentication -server.use('/auth', require('midwest-module-membership/passport/router')) - -// routes > api > membership -server.use('/api/roles', require('midwest-module-membership/services/roles/router')) -server.use('/api/permissions', require('midwest-module-membership/services/permissions/router')) -server.use('/api/invites', require('midwest-module-membership/services/invites/router')) -server.use('/api/users', require('midwest-module-membership/services/users/router')) - -// routes > api > errors -server.use('/api/errors', require('midwest-service-errors/router')) +server.use('/auth', require('midwest-module-membership/passport/router')); // routes > api > pomodoro -server.use('/api/pomodoros', require('./services/pomodoros/router')) +server.use('/api/pomodoros', require('./services/pomodoros/router')); + +// routes > api > errors +server.use('/api/errors', require('midwest-service-errors/router')); + +// routes > api > membership +server.use('/api/roles', require('midwest-module-membership/services/roles/router')); +server.use('/api/permissions', require('midwest-module-membership/services/permissions/router')); +server.use('/api/invites', require('midwest-module-membership/services/invites/router')); +server.use('/api/users', require('midwest-module-membership/services/users/router')); // load all postwares -server.use(...postwares) +server.use(...postwares); -server.listen(config.port, () => { - console.info('[' + chalk.cyan('INIT') + '] HTTP Server listening on port ' + chalk.magenta('%s') + ' (' + chalk.yellow('%s') + ')', config.port, ENV) -}) +// mpromise (built in mongoose promise library) is deprecated, +// tell mongoose to use native Promises instead +mongoose.Promise = Promise; +// connect to mongodb +mongoose.connect(config.mongo.uri, _.omit(config.mongo, 'uri'), (err) => { + if (err) { + console.error(err); + process.exit(); + } -module.exports = server + console.info(`[${chalk.cyan('INIT')}] Mongoose is connected.`); +}); + +// Only start Express server when it is the main module (ie not required by test) +if (require.main === module) { + server.http = server.listen(config.port, () => { + console.info(`[${chalk.cyan('INIT')}] HTTP Server listening on port ${chalk.magenta(config.port)} (${chalk.yellow(server.get('env'))})`); + }); +} + +module.exports = server; diff --git a/server/services/pomodoros/middleware.js b/server/services/pomodoros/middleware.js index 915f72f..5ce1ac7 100644 --- a/server/services/pomodoros/middleware.js +++ b/server/services/pomodoros/middleware.js @@ -1,48 +1,48 @@ -'use strict' +'use strict'; -const rest = require('midwest/middleware/rest') -const formatQuery = require('midwest/middleware/format-query') -const paginate = require('midwest/middleware/paginate') +const rest = require('midwest/factories/rest'); +const formatQuery = require('midwest/factories/format-query'); +const paginate = require('midwest/factories/paginate'); -const Pomodoro = require('./model') +const Pomodoro = require('./model'); function create(req, res, next) { Pomodoro.create(Object.assign(req.body, { user: req.user && req.user.id || '57b04f50a1eaaf354f3b96a6', ip: req.ip, - userAgent: req.headers['user-agent'] + userAgent: req.headers['user-agent'], }), (err, pomodoro) => { - res.locals.pomodoro = pomodoro + res.locals.pomodoro = pomodoro; - next(err) - }) + next(err); + }); } function end(req, res, next) { Pomodoro.findByIdAndUpdate(req.params.id, { endDate: new Date() }, (err, pomodoro) => { - if (err) return next(err) + if (err) return next(err); - res.locals.pomodoro = pomodoro + res.locals.pomodoro = pomodoro; - next() - }) + next(); + }); } function getActive(req, res, next) { Pomodoro.find({ user: req.user.id, endDate: { $eq: null } }, (err, pomodoros) => { - res.locals.pomodoros = pomodoros + res.locals.pomodoros = pomodoros; - return next(err) - }) + return next(err); + }); } module.exports = Object.assign(rest(Pomodoro), { create, end, - formatQuery: formatQuery([ 'limit', 'sort' ], { - endDate: 'exists' + formatQuery: formatQuery(['limit', 'sort'], { + endDate: 'exists', }), getActive, - paginate: paginate(Pomodoro, 50) -}) + paginate: paginate(Pomodoro, 50), +}); diff --git a/server/services/pomodoros/model.js b/server/services/pomodoros/model.js index e7a9339..a09adc5 100644 --- a/server/services/pomodoros/model.js +++ b/server/services/pomodoros/model.js @@ -1,31 +1,31 @@ -'use strict' +'use strict'; -const mongoose = require('mongoose') +const mongoose = require('mongoose'); const PomodoroSchema = new mongoose.Schema({ startTime: { type: Date, - required: true + required: true, }, endTime: { type: Date, - required: true + required: true, }, name: String, location: String, user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', - required: true + required: true, }, ip: { type: String, - required: true + required: true, }, userAgent: { type: String, - required: true + required: true, }, -}) +}); -module.exports = mongoose.model('Pomodoro', PomodoroSchema) +module.exports = mongoose.model('Pomodoro', PomodoroSchema); diff --git a/server/services/pomodoros/router.js b/server/services/pomodoros/router.js index a96c300..35636e2 100644 --- a/server/services/pomodoros/router.js +++ b/server/services/pomodoros/router.js @@ -1,19 +1,19 @@ -'use strict' +'use strict'; -const router = new (require('express')).Router() +const router = new (require('express')).Router(); -const mw = require('./middleware') +const mw = require('./middleware'); -const { isAuthenticated } = require('midwest-module-membership/passport/authorization-middleware') +const { isAuthenticated } = require('midwest-module-membership/passport/authorization-middleware'); router.route('/') .get(isAuthenticated, mw.formatQuery, mw.paginate, mw.query) - .post(isAuthenticated, mw.create) + .post(isAuthenticated, mw.create); router.route('/:id') .get(isAuthenticated, mw.findById) .patch(isAuthenticated, mw.update) .put(isAuthenticated, mw.replace) - .delete(isAuthenticated, mw.remove) + .delete(isAuthenticated, mw.remove); -module.exports = router +module.exports = router; diff --git a/server/templates/error.marko b/server/templates/error.jsx similarity index 100% rename from server/templates/error.marko rename to server/templates/error.jsx diff --git a/server/templates/index.marko b/server/templates/index.jsx similarity index 100% rename from server/templates/index.marko rename to server/templates/index.jsx