From 1f4e759225c8505ef6bbbc91e0c410011027c52a Mon Sep 17 00:00:00 2001 From: Linus Miller Date: Wed, 15 Mar 2017 18:03:15 +0100 Subject: [PATCH] Initial commit --- .gitmodules | 3 + bin/initDb.js | 74 ++++++++++ gulp | 1 + gulpfile.js | 1 + package.json | 30 ++++ server/api/parse.js | 95 +++++++++++++ server/api/parseLine.js | 137 ++++++++++++++++++ server/config/dir.js | 7 + server/config/error-handler.js | 41 ++++++ server/config/globals.js | 4 + server/config/membership.js | 78 ++++++++++ server/config/port.js | 10 ++ server/config/postgres.js | 24 ++++ server/config/session.js | 38 +++++ server/config/shim.js | 34 +++++ server/config/site.js | 51 +++++++ server/config/smtp.js | 15 ++ server/db.js | 16 +++ server/middleware.js | 30 ++++ server/render.js | 26 ++++ server/routers/api.js | 251 +++++++++++++++++++++++++++++++++ server/routers/index.js | 7 + server/server.js | 132 +++++++++++++++++ 23 files changed, 1105 insertions(+) create mode 100644 .gitmodules create mode 100755 bin/initDb.js create mode 160000 gulp create mode 120000 gulpfile.js create mode 100644 package.json create mode 100644 server/api/parse.js create mode 100644 server/api/parseLine.js create mode 100644 server/config/dir.js create mode 100644 server/config/error-handler.js create mode 100644 server/config/globals.js create mode 100644 server/config/membership.js create mode 100644 server/config/port.js create mode 100644 server/config/postgres.js create mode 100644 server/config/session.js create mode 100644 server/config/shim.js create mode 100644 server/config/site.js create mode 100644 server/config/smtp.js create mode 100644 server/db.js create mode 100644 server/middleware.js create mode 100644 server/render.js create mode 100644 server/routers/api.js create mode 100644 server/routers/index.js create mode 100644 server/server.js diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..271c5a9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "gulp"] + path = gulp + url = https://github.com/thebitmill/gulp diff --git a/bin/initDb.js b/bin/initDb.js new file mode 100755 index 0000000..ed96eda --- /dev/null +++ b/bin/initDb.js @@ -0,0 +1,74 @@ +#!/bin/env node + +'use strict'; + +const pg = require('../server/db'); + +// pg.schema.dropTableIfExists('transactions') +pg.raw('DROP TABLE IF EXISTS companies CASCADE') + .then(() => pg.raw('DROP TABLE IF EXISTS years CASCADE')) + .then(() => pg.raw('DROP TABLE IF EXISTS accounts CASCADE')) + .then(() => pg.raw('DROP TABLE IF EXISTS account_types CASCADE')) + .then(() => pg.raw('DROP TABLE IF EXISTS tickets CASCADE')) + .then(() => pg.raw('DROP TABLE IF EXISTS ticket_types CASCADE')) + .then(() => pg.raw('DROP TABLE IF EXISTS transactions CASCADE')) + .then(() => pg.raw('DROP TABLE IF EXISTS initial_balance CASCADE')) + + .then(() => pg.schema.createTable('companies', (table) => { + table.increments(); + table.text('name').notNullable().unique(); + table.text('organization_number').notNullable().unique(); + })) + .then(() => pg.schema.createTable('years', (table) => { + table.increments(); + table.integer('company_id').references('companies.id').notNullable().onUpdate('cascade'); + table.integer('previous_year_id').references('id'); + table.date('start_date').notNullable(); + table.date('end_date').notNullable(); + table.text('name'); + })) + .then(() => pg.schema.createTable('account_types', (table) => { + table.increments(); + table.text('name').notNullable().unique(); + })) + .then(() => pg.schema.createTable('accounts', (table) => { + table.increments(); + table.text('name').notNullable(); + table.integer('number').notNullable(); + table.integer('sru'); + table.integer('account_type_id').notNullable().references('account_types.id').onUpdate('cascade'); + })) + .then(() => pg.schema.createTable('initial_balance', (table) => { + table.increments(); + table.integer('year_id').notNullable().references('years.id').onUpdate('cascade'); + table.integer('account_id').notNullable().references('accounts.id').onUpdate('cascade'); + table.decimal('amount').notNullable(); + })) + .then(() => pg.schema.createTable('ticket_types', (table) => { + table.increments(); + table.integer('number').notNullable(); + table.text('name').notNullable().unique(); + })) + .then(() => pg.schema.createTableIfNotExists('tickets', (table) => { + table.increments(); + table.integer('type'); + table.integer('ticket_type_id').references('ticket_types.id').onUpdate('cascade'); + // table.integer('ticket_type_id').notNullable().references('ticket_types.id').onUpdate('cascade'); + table.integer('number').notNullable(); + table.date('date').notNullable(); + table.text('title'); + table.date('date_created').notNullable(); + table.timestamp('uploaded_at').defaultsTo(pg.fn.now()); + })) + .then(() => pg.schema.createTableIfNotExists('transactions', (table) => { + table.increments(); + table.integer('account_id').notNullable().references('accounts.id').onUpdate('cascade'); + table.decimal('amount', 14, 2).notNullable(); + table.integer('ticket_id').notNullable().references('tickets.id').onUpdate('cascade'); + })) + .then(() => pg('account_types').insert([{ name: 'Tillgång' }, { name: 'Skuld' }, { name: 'Intäkt' }, { name: 'Kostnad' }])) + .then(() => pg.destroy()) + .catch((err) => { + console.log(err); + pg.destroy(); + }); diff --git a/gulp b/gulp new file mode 160000 index 0000000..1daad4b --- /dev/null +++ b/gulp @@ -0,0 +1 @@ +Subproject commit 1daad4b4629c3ddef1f60710a0f7f8a67af9d714 diff --git a/gulpfile.js b/gulpfile.js new file mode 120000 index 0000000..0f1e7d6 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1 @@ +gulp/gulpfile.js \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a62ae09 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "vizwiz", + "version": "0.0.1", + "description": "", + "main": "server/server.js", + "scripts": { + "gulp": "gulp", + "gulp:production": "NODE_ENV=production gulp", + "gulp:development": "NODE_ENV=development gulp", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@gitlab.bitmill.co:lohfu/vizwiz.git" + }, + "author": "Linus Miller (https://lohfu.io/)", + "license": "MIT", + "dependencies": { + "body-parser": "^1.16.1", + "busboy": "^0.2.14", + "chalk": "^1.1.3", + "express": "^4.14.1", + "jsx-node": "^0.2.2", + "knex": "^0.12.7", + "midwest": "github:thebitmill/midwest", + "morgan": "^1.8.1", + "multer": "^1.3.0", + "split": "^1.0.0" + } +} diff --git a/server/api/parse.js b/server/api/parse.js new file mode 100644 index 0000000..e0dfc66 --- /dev/null +++ b/server/api/parse.js @@ -0,0 +1,95 @@ +'use strict'; + +function parseAccount(str) { + // str will look like: 'KONTO 1010 "Balanserade utgifter"' + const [, number, ...name] = str.split(/\s+/); + + return { + number: parseInt(number, 10), + name: name.join(' '), + }; +} + +const accountType = { + T: 1, + S: 2, + I: 3, + K: 4, +}; + +function parseAccountType(str) { + const [, number, type] = str.split(/\s+/); + + return { + number: parseInt(number, 10), + type: accountType[type], + }; +} + +function parseDate(str) { + const [, year, month, day] = /(\d\d\d\d)(\d\d)(\d\d)/.exec(str); + + return new Date(`${year}-${month}-${day}T00:00:00.000Z`); +} + +function parseTransaction(str) { + // str looks like: ' #TRANS 6310 {} 1541.00 20150824 ' + const [,, account,, amount, date] = str.split(/\s+/); + + return { + account: parseInt(account, 10), + amount: parseFloat(amount), + date: parseDate(date), + }; +} + +function parseTicket(str) { + const arr = str.split('\r\n'); + // arr[0] will look like: 'VER 5101 147 20150917 "Binero" 20160224' + const [, type, number, date, ...rest] = arr[0].split(/\s+/); + const dateCreated = rest.pop(); + const title = rest.join(' '); + + console.log(type); + const transactions = arr.slice(2, -2).map(parseTransaction); + + return { + type: parseInt(type, 10), + number: parseInt(number, 10), + date: parseDate(date), + dateCreated: parseDate(date), + title, + transactions, + } +} + + +// console.log(string.split('\n#').length) + +module.exports = function parse(string) { + const accounts = []; + const tickets = []; + + string.split(/\n#/g).forEach((str, i) => { + if (str.startsWith('KONTO')) { + accounts.push(parseAccount(str)); + } else if (str.startsWith('KTYP')) { + const { number, type } = parseAccountType(str); + +// const string = fs.readFileSync('./bitmill2015.se', { encoding: 'UTF8' }); + const account = accounts.find((account) => account.number === number); + + account.type = type; + } else if (str.startsWith('VER')) { + tickets.push(parseTicket(str)); + } + }); + + return { + accounts, + tickets, + }; +}; +// console.log(tickets); +// process.exit(0); + diff --git a/server/api/parseLine.js b/server/api/parseLine.js new file mode 100644 index 0000000..4657fd5 --- /dev/null +++ b/server/api/parseLine.js @@ -0,0 +1,137 @@ +'use strict'; + +function parseAccount(str) { + // str will look like: 'KONTO 1010 "Balanserade utgifter"' + const [, number, ...name] = str.split(/\s+/); + + return { + number: parseInt(number, 10), + name: name.join(' ').slice(1, -1), + }; +} + +const accountType = { + T: 1, + S: 2, + I: 3, + K: 4, +}; + +function parseCompanyName(str) { + const [, name] = str.split(/\s+/); + + return name.slice(1, -1); +} + +// str should be `ORGNR "556930-1673"` +function parseOrganizationNumber(str) { + const [, number] = str.split(/\s+/); + return number.slice(1, -1); +} + +function parseYearDates(str) { + const [, current, start, end] = str.split(/\s+/); + + return { + start: parseDate(start), + end: parseDate(end), + current: current === '0', + }; +} + +function parseAccountType(str) { + const [, number, type] = str.split(/\s+/); + + return { + number: parseInt(number, 10), + type: accountType[type], + }; +} + +function parseDate(str) { + const [, year, month, day] = /(\d\d\d\d)(\d\d)(\d\d)/.exec(str); + + return new Date(`${year}-${month}-${day}T00:00:00.000Z`); +} + +function parseSRU(str) { + const [, number, sru] = str.split(/\s+/); + + return { + number: parseInt(number, 10), + sru: parseInt(sru, 10), + }; +} + +function parseTransaction(str) { + // str looks like: ' #TRANS 6310 {} 1541.00 20150824 ' + const [, accountNumber,, amount] = str.split(/\s+/); + + return { + accountNumber: parseInt(accountNumber, 10), + amount: parseFloat(amount), + }; +} + +function parseTicket(str) { + const [, type, number, date, ...rest] = str.split(/\s+/); + const dateCreated = rest.pop(); + const title = rest.join(' '); + + return { + type: parseInt(type, 10), + number: parseInt(number, 10), + date: parseDate(date), + date_created: parseDate(dateCreated), + title: title.slice(1, -1), + }; +} + +module.exports = { + parseAccountType, + parseAccount, + parseCompanyName, + parseOrganizationNumber, + parseSRU, + parseTransaction, + parseTicket, + parseYearDates, +}; + +// console.log(string.split('\n#').length) + +// module.exports = function parseLine(line) { +// line = line.slice(1); + +// if (line.startsWith('KONTO')) { +// const account = parseAccount(line); +// } else if (line.startWith('KTYP') { +// const accountType = parseAccountType(line); +// } + +// const accounts = []; +// const tickets = []; + +// string.split(/\n#/g).forEach((str, i) => { +// if (str.startsWith('KONTO')) { +// accounts.push(parseAccount(str)); +// } else if (str.startsWith('KTYP')) { +// const { number, type } = parseAccountType(str); + +// // const string = fs.readFileSync('./bitmill2015.se', { encoding: 'UTF8' }); +// const account = accounts.find((account) => account.number === number); + +// account.type = type; +// } else if (str.startsWith('VER')) { +// tickets.push(parseTicket(str)); +// } +// }); + +// return { +// accounts, +// tickets, +// }; +//}; +// console.log(tickets); +// process.exit(0); + diff --git a/server/config/dir.js b/server/config/dir.js new file mode 100644 index 0000000..1ad5a4b --- /dev/null +++ b/server/config/dir.js @@ -0,0 +1,7 @@ +'use strict'; + +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 new file mode 100644 index 0000000..a779e56 --- /dev/null +++ b/server/config/error-handler.js @@ -0,0 +1,41 @@ +'use strict'; + +const _ = require('lodash'); + +// const errorTemplate = require('../templates/error.jsx'); +const errorTemplate = () => '

Error

'; + +const defaults = { + post: (req, res, next) => { + res.template = errorTemplate; + + next(); + }, + + mystify: { + properties: ['errors', 'message', 'name', 'status', 'statusText'], + }, + + log: { + // if database = true there has to be a mongoose model name ErrorModel + ignore: [], + }, +}; + +module.exports = _.merge(defaults, { + development: { + log: { + console: true, + }, + }, + testing: { + log: { + console: false, + }, + }, + production: { + log: { + console: false, + }, + }, +}[ENV]); diff --git a/server/config/globals.js b/server/config/globals.js new file mode 100644 index 0000000..667e2f3 --- /dev/null +++ b/server/config/globals.js @@ -0,0 +1,4 @@ +'use strict'; + +// global.LOGIN_USER = 'linus.miller@thecodebureau.com' +// global.LOGIN_USER = 'zarac@zarac.se' diff --git a/server/config/membership.js b/server/config/membership.js new file mode 100644 index 0000000..1d4ece2 --- /dev/null +++ b/server/config/membership.js @@ -0,0 +1,78 @@ +'use strict'; + +const site = require('./site'); + +module.exports = { + invite: { + from: `${site.title} Robot <${site.emails.robot}>`, + subject: `You have been invited to ${site.title}`, + }, + + timeouts: { + // 1 day + changePassword: 24 * 60 * 60 * 1000, + // verify email + verifyEmail: 7 * 24 * 60 * 60 * 1000, + }, + + paths: { + register: '/register', + login: '/login', + forgotPassword: '/forgot-password', + updatePassword: '/change-password', + }, + + redirects: { + login: '/admin', + logout: '/', + 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, + maxAge: 30 * 24 * 60 * 60 * 1000, + }, + + messages: { + login: { + notLocal: 'Account requires external login.', + wrongPassword: 'Wrong password.', + noLocalUser: 'No user registered with that email.', + noExternalUser: 'The account is not connected to this website.', + 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.', + }, + + register: { + missingProperties: 'Oh no missing stuff', + notAuthorized: 'The email is not authorized to create an account.', + duplicateEmail: 'The email has already been registered.', + }, + }, + + passport: { + local: { + usernameField: 'email', + }, + + scope: ['email'], + + //providers: { + // facebook: { + // clientID: 'change-this-fool', + // clientSecret: 'change-this-fool', + // callbackURL: p.join(config.site.domain, '/auth/facebook/callback'), + // passReqToCallback: true + // }, + + }, + + // needs to be even + tokenLength: 64, + // needs to be even + saltLength: 16, +}; diff --git a/server/config/port.js b/server/config/port.js new file mode 100644 index 0000000..35d4419 --- /dev/null +++ b/server/config/port.js @@ -0,0 +1,10 @@ +'use strict'; + +const basePort = 3070; + +module.exports = { + development: basePort, + testing: basePort + 1, + staging: basePort + 2, + production: basePort + 3, +}[ENV]; diff --git a/server/config/postgres.js b/server/config/postgres.js new file mode 100644 index 0000000..a9a48ce --- /dev/null +++ b/server/config/postgres.js @@ -0,0 +1,24 @@ +'use strict'; + +const defaults = { + user: 'vizwiz', //env var: PGUSER + database: 'vizwiz', //env var: PGDATABASE + password: 'secret', //env var: PGPASSWORD + host: 'localhost', // Server hosting the postgres database + port: 5432, //env var: PGPORT + max: 10, // max number of clients in the pool + idleTimeoutMillis: 30000, // how long a client is allowed to remain idle before being closed +}; +// { +// user: 'newseri_supreme', // env var: PGUSER +// database: 'newseri', // env var: PGDATABASE +// password: 'oh-look-it-is-raining-news', // env var: PGPASSWORD +// // host: '192.168.1.11', // Server hosting the postgres database +// host: 'hq.bitmill.co', // Server hosting the postgres database +// // port: 5432, // env var: PGPORT +// port: 6543, // env var: PGPORT +// max: 10, // max number of clients in the pool +// idleTimeoutMillis: 30000, // how long a client is allowed to remain idle before being closed +// }; + +module.exports = defaults; diff --git a/server/config/session.js b/server/config/session.js new file mode 100644 index 0000000..bd825a2 --- /dev/null +++ b/server/config/session.js @@ -0,0 +1,38 @@ +'use strict'; + +const chalk = require('chalk'); +const session = require('express-session'); + +let redisStore; + +const config = { + secret: 'ohohohogogogoONMYTOETOEthisisacompletelyrandomgeneratedstring.thisisonlyacoincidence.', + resave: false, + saveUninitialized: true, +}; + +const redisConfig = { + host: 'localhost', + port: 6379, +}; + +if (ENV === 'production') { + const RedisStore = require('connect-redis')(require('express-session')); + + redisStore = new RedisStore(redisConfig); + + redisStore.on('connect', () => { + console.info(`[${chalk.cyan('INIT')}] Redis connected succcessfully`); + }); + + redisStore.on('disconnect', () => { + throw new Error('Unable to connect to redis. Has it been started?'); + }); + + config.store = redisStore; +} else { + config.store = new session.MemoryStore(); +} + +module.exports = config; + diff --git a/server/config/shim.js b/server/config/shim.js new file mode 100644 index 0000000..494cf40 --- /dev/null +++ b/server/config/shim.js @@ -0,0 +1,34 @@ +'use strict'; + +module.exports = { + 'https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.min.js': [ + 'chrome <= 12', + 'firefox <= 20', + 'ie <= 9', + 'opera <= 12', + 'safari <= 5', + ], + 'https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.min.js': [ + // 'https://cdnjs.cloudflare.com/ajax/libs/core-js/2.4.1/core.min.js': [ + // 'https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.js': [ + 'chrome <= 51', + 'edge <= 13', + 'firefox <= 44', + 'ie <= 11', + 'safari <= 9', + ], + 'https://unpkg.com/es7-shim@latest/dist/es7-shim.min.js': [ + 'chrome <= 50', + 'edge <= 14', + 'firefox <= 46', + 'ie <= 11', + 'safari <= 10', + ], + 'https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js': [ + 'chrome <= 41', + 'edge <= 13', + 'firefox <= 38', + 'ie <= 11', + 'safari <= 9', + ], +}; diff --git a/server/config/site.js b/server/config/site.js new file mode 100644 index 0000000..2f2b50a --- /dev/null +++ b/server/config/site.js @@ -0,0 +1,51 @@ +'use strict'; + +const _ = require('lodash'); + +const domain = 'bitmill.co'; + +const defaults = { + domain, + title: 'Vizwiz', + name: 'vizwiz', + protocol: 'http', + get host() { + return this.port ? `${this.hostname}:${this.port}` : this.hostname; + }, + get url() { + return `${this.protocol}://${this.host}/`; + }, + emails: { + robot: 'no-reply@thecodebureau.com', + info: 'info@thecodebureau.com', + webmaster: 'webmaster@thecodebureau.com', + order: 'info@thecodebureau.com', + }, +}; + +module.exports = _.merge(defaults, { + development: { + hostname: 'localhost', + port: process.env.EXTERNAL_PORT || process.env.PORT || require('./port'), + }, + + testing: { + hostname: 'localhost', + port: process.env.PORT || require('./port'), + }, + + staging: { + hostname: `vizwiz.staging.${domain}`, + }, + + production: { + hostname: `vizwiz.${domain}`, + 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 new file mode 100644 index 0000000..8dadf7c --- /dev/null +++ b/server/config/smtp.js @@ -0,0 +1,15 @@ +'use strict'; + +const _ = require('lodash'); + +const defaults = { + auth: { + user: 'SMTP_Injection', + // dev key + pass: '2eec390c5b3f5d593c9f152179bf51e90b073784', + }, + host: 'smtp.sparkpostmail.com', + port: 587, +}; + +module.exports = _.merge(defaults, {}[ENV]); diff --git a/server/db.js b/server/db.js new file mode 100644 index 0000000..e9334b8 --- /dev/null +++ b/server/db.js @@ -0,0 +1,16 @@ +'use strict'; + +// const factory = require('midwest/util/db'); +const conf = require('./config/postgres'); + +// module.exports = factory(conf); +// +const knex = require('knex'); + +const pg = knex({ + client: 'pg', + connection: conf, +}); + +module.exports = pg; + diff --git a/server/middleware.js b/server/middleware.js new file mode 100644 index 0000000..4f525d1 --- /dev/null +++ b/server/middleware.js @@ -0,0 +1,30 @@ +'use strict'; + +const db = require('./db'); + +module.exports = { + result(req, res, next) { + db.select().sum('transactions.amount as sum').from('transactions') + .innerJoin('accounts', 'transactions.account_id', 'accounts.id') + .innerJoin('tickets', 'transactions.ticket_id', 'tickets.id') + .whereBetween('tickets.date', ['2015-03-01', '2015-03-31]']) + .andWhereBetween('accounts.account_type_id', [3, 4]) + .then((res) => { + console.log(res); + // console.log(res); + // const result = res.reduce((result, t) => { + // console.log(t.amount); + // console.log(parseFloat(t.amount, 2)); + // result += parseFloat(t.amount, 2); + + // return result; + + // }, 0); + // console.log(result); + }); + // db.schema.raw(`SELECT * FROM transactions INNER JOIN accounts ON accounts.id = transactions.account_id + // WHERE accounts.account_type_id = 3;`, (result) => { + // console.log(result); + // }) + } +}; diff --git a/server/render.js b/server/render.js new file mode 100644 index 0000000..f0dabb2 --- /dev/null +++ b/server/render.js @@ -0,0 +1,26 @@ +const { h } = require('jsx-node'); + +module.exports = function (Component, Master) { + const locals = Object.assign({ query: this.req.query }, this.app.locals, this.locals); + + if (typeof Master === 'function') { + if (typeof Component === 'function') { + return this.send( + h(Master, locals, + h(Component, locals) + ) + ); + } + + Component = Master; + } + + if (typeof Component !== 'function') { + throw new Error('Not a Component'); + } else if (Component.prototype && Component.prototype.render) { + const i = new Component(locals); + this.send(i.render(i.props, i.state)); + } else { + this.send(Component(locals)); + } +}; diff --git a/server/routers/api.js b/server/routers/api.js new file mode 100644 index 0000000..b36f8e0 --- /dev/null +++ b/server/routers/api.js @@ -0,0 +1,251 @@ +'use strict'; + +const _ = require('lodash'); +const router = new (require('express').Router)(); +const Busboy = require('busboy'); +const split = require('split'); + +const db = require('../db'); + +const parse = require('../api/parse'); + +const { + parseAccount, + parseAccountType, + parseCompanyName, + parseSRU, + parseTransaction, + parseTicket, + parseYearDates, + parseOrganizationNumber, +} = require('../api/parseLine'); + +const queries = { + saveAccount(account) { + db('accounts').insert(h) + } +} + +const handlers = require('../handlers'); + +router.get('/parse', (req, res) => res.send('

Parse

')); +router.post('/sie', (req, res) => { + const busboy = new Busboy({ headers: req.headers }); + + busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { + let accounts = []; + let ticketId = null; + let companyDone = false; + let yearDone = false; + let accountsDone = false; + let accountIds = []; + let details = {}; + const transactions = []; + let currentYearDates = []; + let currentYear; + let previousYearDates = []; + let company; + + // let saving = false; + const splitStream = file.pipe(split()) + .on('data', async (line) => { + line = line.trim(); + + if (line[0] !== '#') return; + + const lineType = line.slice(1, line.indexOf(' ')); + console.log(lineType); + + if (!companyDone) { + switch (lineType) { + case 'ORGNR': { + const organizationNumber = parseOrganizationNumber(line); + + splitStream.pause(); + + company = await handlers.getCompanyByOrganizationNumber(organizationNumber) || { organization_number: organizationNumber }; + + splitStream.resume(); + + return; + } + case 'FNAMN': { + const companyName = parseCompanyName(line); + + if (!company.name || company.name !== companyName) { + company.name = companyName; + } + + return; + } + case 'RAR': { + if (!company.id) { + splitStream.pause(); + + company = await handlers.createCompany(company); + + companyDone = true; + + splitStream.resume(); + } + } + } + } + + if (companyDone && !yearDone) { + switch (lineType) { + case 'RAR': { + const dates = parseYearDates(line); + + if (dates.current) { + currentYearDates = _.omit(dates, 'current'); + } else { + previousYearDates = _.omit(dates, 'current'); + } + + return; + } + case 'KONTO': { + splitStream.pause(); + + const previousYear = await handlers.getYearByCompanyIdAndDates(company.id, previousYearDates); + + // currentYear = await handlers.getYearByCompanyIdAndDates(company.id, currentYearDates) || Object.assign({ company_id: company.id }, _.mapKeys(currentYearDates, (value, key) => `${key}_date`); + currentYear = Object.assign({ company_id: company.id }, _.mapKeys(currentYearDates, (value, key) => `${key}_date`)); + + if (previousYear) currentYear.previous_year_id = previousYear.id; + + // TODO remove all entries for year + currentYear = await handlers.createYear(currentYear); + + yearDone = true; + + splitStream.resume(); + } + } + } + + if (companyDone && yearDone && !accountsDone) { + console.log(lineType); + console.log(line); + switch (lineType) { + case 'KONTO': + const account = parseAccount(line); + if (!account || !account.number) console.log(account); + accounts.push(account); + + return; + case 'KTYP': { + const { number, type } = parseAccountType(line); + + const account = accounts.find((account) => account.number === number) || { number }; + + if (account) account.account_type_id = type; + + return; + } + case 'SRU': { + const { number, sru } = parseSRU(line); + + const account = accounts.find((account) => account.number === number) || { number }; + + // if (account) + account.sru = sru; + + return; + } + case 'VER': + console.log('shhould be saviung accounts'); + splitStream.pause(); + // console.log(accounts); + accounts.forEach((account) => { + if (!account.account_type_id) console.log(account); + }); + + await db('accounts').returning(['id', 'number']).insert(accounts).then((res) => { + accounts = res; + }).catch((err) => { + console.log(err); + }); + + accountsDone = true; + + splitStream.resume(); + } + } + + return; + + + if (companyDone && yearDone && accountsDone) { + switch (lineType) { + case 'VER': { + splitStream.pause(); + + const ticket = parseTicket(line); + + await db('tickets').returning('id').insert(_.omit(ticket, 'transactions')).then((res) => { + ticketId = res[0]; + }).catch((err) => console.log(err)); + + splitStream.resume(); + + return; + } + case 'TRANS': { + const transaction = parseTransaction(line); + + transaction.ticket_id = ticketId; + + const account = accounts.find((account) => account.number === transaction.accountNumber); + + delete transaction.accountNumber; + + transaction.account_id = account.id; + + transactions.push(transaction); + + return; + } + } + } + }) + .on('end', () => { + db('transactions').insert(transactions) + .catch((err) => console.log(err)); + }); + }); + + busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) { + console.log('Field [' + fieldname + ']: value: ' + inspect(val)); + }); + + busboy.on('finish', function() { + console.log('Done parsing form!'); + // res.writeHead(303, { Connection: 'close', Location: '/' }); + // res.end(); + + res.sendStatus(204); + }); + + req.pipe(busboy); + + // tickets.forEach(({ type, number, date, dateCreated, title, transactions }) => { + // db.query(`INSERT INTO tickets (number, type, title, date, date_created) VALUES ('${number}', '${type}', '${title}', '2016-11-01', '2016-11-01') RETURNING id`, (err, result) => { + // if (err) throw err; + + // console.log(result.rows[0]); + // const ticketId = result.rows[0].id; + + // transactions.forEach(({ account, amount }) => { + // db.query(`INSERT INTO transactions (account, amount, ticket) VALUES ('${account}', '${amount}', '${ticketId}')`, (err, result) => { + // console.log(result); + // }); + // }); + // }); + // }) + +}); + +module.exports = router; + diff --git a/server/routers/index.js b/server/routers/index.js new file mode 100644 index 0000000..d112d89 --- /dev/null +++ b/server/routers/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const router = new (require('express').Router)(); + +router.get('/', (req, res) => res.send('

Hello

')); + +module.exports = router; diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..3497ac4 --- /dev/null +++ b/server/server.js @@ -0,0 +1,132 @@ +'use strict'; + +/* + * The main file that sets up the Express instance and node + * + * @module server/server + * @type {Express instance} + */ + +// 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(); + +// modules > native +const p = require('path'); + +// output filename in console log and colour console.dir +if (ENV === 'development') { + 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(); + +// modules > 3rd party +const chalk = require('chalk'); +const express = require('express'); +// 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 > midwest +const colorizeStack = require('midwest/util/colorize-stack'); + +const config = requireDir('./config', { camelcase: true }); + +// make error output stack pretty +process.on('uncaughtException', (err) => { + console.error(chalk.red('UNCAUGHT EXCEPTION')); + if (err.stack) { + console.error(colorizeStack(err.stack)); + } else { + console.error(err); + } + process.exit(1); +}); + +const prewares = [ + express.static(config.dir.static, ENV === 'production' ? { maxAge: '1 year' } : null), + bodyParser.json(), + bodyParser.urlencoded({ extended: true }), + // cookieParser(), + // session(config.session), + // passport.initialize(), + // passport.session(), +]; + +if (ENV === 'development') { + // only log requests to console in development mode + 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/factories/error-handler')(config.errorHandler), + // respond + require('midwest/middleware/responder'), +]; + +const server = express(); + +// get IP & whatnot from nginx proxy +server.set('trust proxy', true); + +Object.assign(server.locals, { + site: require('./config/site'), +}); + +// override default response render method for +server.response.render = require('./render.js'); + +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); + +// routes +server.use(require('./routers/index')); +server.use('/api', require('./routers/api')); + +// 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')); + +// load all postwares +server.use(...postwares); + +// mpromise (built in mongoose promise library) is deprecated, +// tell mongoose to use native Promises instead + +// 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(ENV)})`); + }); +} + +const mw = require('./middleware'); + +mw.result(); + +module.exports = server;