User Tools

Site Tools


Differences

This shows you the differences between two versions of the page.

Link to this comparison view

wcm-text [2019-06-21 14:14]
wcm-text [2020-02-15 00:57] (current)
Line 1: Line 1:
 +
 +===== Sources =====
 +
 +===expresso/​backend/​migrations/​20181214085626_stable.js===
 +
 +<code javascript expresso/​backend/​migrations/​20181214085626_stable.js>​
 +
 +exports.up = function(knex,​ Promise) {
 +    return knex.schema
 +        .createTable('​customers',​ function(table) {
 +            table.increments('​id'​).primary().unique()
 +            table.string('​name'​)
 +            table.string('​phone1'​)
 +            table.string('​phone2'​)
 +            table.string('​city'​)
 +            table.string('​agreement'​)
 +            table.timestamp('​created_at'​).defaultTo(knex.fn.now())
 +            table.timestamp('​updated_at'​).defaultTo(knex.fn.now())
 +        })
 +        .createTable('​users',​ function(table) {
 +            table.increments('​id'​).primary().unique()
 +            table.string('​name'​)
 +            table.string('​password'​)
 +            table.boolean('​superuser'​)
 +            table.string('​gecos'​)
 +            table.timestamp('​created_at'​).defaultTo(knex.fn.now())
 +            table.timestamp('​updated_at'​).defaultTo(knex.fn.now())
 +        })
 +}
 +
 +exports.down = function(knex,​ Promise) {
 +    return knex.schema
 +        .dropTable('​users'​)
 +        .dropTable('​customers'​)
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​models/​customers.js===
 +
 +<code javascript expresso/​backend/​models/​customers.js>​
 +'use strict'​
 +
 +module.exports = function(knex) {
 +
 +    var find = function(params) {
 +        return knex
 +            .select([
 +                '​customers.*',​
 +            ])
 +            .from('​customers'​)
 +            .where(
 +                '​customers.phone1',​ '​~',​ params.phone1
 +            )
 +            .orWhere(
 +                '​customers.phone2',​ '​~',​ params.phone2
 +            )
 +            .orWhere(
 +                '​customers.name',​ '​~',​ params.name
 +            )
 +            .limit(50)
 +
 +    }
 +
 +    var get = function(params) {
 +        return knex
 +            .select([
 +                '​customers.*',​
 +            ])
 +            .from('​customers'​)
 +            .where({
 +                '​customers.id':​ params.id,
 +            })
 +    }
 +
 +    var create = function(params) {
 +        return knex
 +            .insert({
 +                name: params.name,​
 +                phone1: params.phone1,​
 +                phone2: params.phone2,​
 +                city: params.city,​
 +                agreement: params.agreement,​
 +            })
 +            .into('​customers'​)
 +    }
 +
 +
 +    return {
 +        modelName: "​customers",​
 +        find: find,
 +        get: get,
 +        create: create
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​models/​login.js===
 +
 +<code javascript expresso/​backend/​models/​login.js>​
 +'use strict'​
 +
 +module.exports = function(knex) {
 +
 +    var login = function(params) {
 +        return knex
 +            .select([
 +                '​users.name',​
 +                '​users.gecos',​
 +                '​users.id',​
 +                '​users.superuser'​
 +            ])
 +            .from('​users'​)
 +            .where({
 +                '​users.name':​ params.name,​
 +                '​users.password':​ params.password,​
 +            })
 +    }
 +
 +    var check = function(params) {
 +        return knex
 +            .select([
 +                '​users.id',​
 +            ])
 +            .from('​users'​)
 +            .where({
 +                '​users.id':​ params.id
 +            })
 +    }
 +
 +    return {
 +        modelName: "​login",​
 +        login: login,
 +        check: check
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​models/​users.js===
 +
 +<code javascript expresso/​backend/​models/​users.js>​
 +'use strict'​
 +
 +module.exports = function(knex) {
 +
 +    var list = function(params) {
 +        return knex
 +            .select([
 +                '​users.*',​
 +            ])
 +            .from('​users'​)
 +            .orderBy('​users.name'​)
 +    }
 +
 +    var find = function(params) {
 +        return knex
 +            .select([
 +                '​users.*',​
 +            ])
 +            .from('​users'​)
 +            .where({
 +                '​users.name':​ params.name,​
 +            })
 +            .orderBy('​users.name'​)
 +    }
 +
 +    var get = function(params) {
 +        return knex
 +            .select([
 +                '​users.*',​
 +            ])
 +            .from('​users'​)
 +            .where({
 +                '​users.id':​ params.id,
 +            })
 +            .orderBy('​users.name'​)
 +    }
 +
 +    var create = function(params) {
 +        return knex
 +            .insert({
 +                gecos: params.gecos,​
 +                name: params.name,​
 +                password: params.password,​
 +                superuser: params.superuser,​
 +            })
 +            .into('​users'​)
 +    }
 +
 +    var update = function(params) {
 +        return knex
 +            .update({
 +                gecos: params.gecos,​
 +                name: params.name,​
 +                password: params.password,​
 +                superuser: params.superuser,​
 +            })
 +            .from('​users'​)
 +            .where({
 +                id: params.id
 +            })
 +    }
 +
 +    var drop = function(params) {
 +        return knex
 +            .del()
 +            .from('​users'​)
 +            .where({
 +                id: params.id
 +            })
 +    }
 +
 +    return {
 +        modelName: "​users",​
 +        list: list,
 +        find: find,
 +        create: create,
 +        update: update,
 +        drop: drop
 +    }
 +}</​code>​
 +
 +
 +===expresso/​backend/​plugins/​excors.js===
 +
 +<code javascript expresso/​backend/​plugins/​excors.js>​
 +
 +module.exports = function nocors() {
 +    return function _nocors(req,​ res, next) {
 +        res.header('​Access-Control-Allow-Origin',​ '​*'​)
 +        res.header('​Access-Control-Allow-Headers',​ '​Origin,​ X-Requested-With,​ Content-Type,​ Accept'​)
 +        next()
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​routers/​customers.js===
 +
 +<code javascript expresso/​backend/​routers/​customers.js>​
 +'use strict'​
 +
 +module.exports = function(knex) {
 +    const model = require('​models/​customers'​)(knex)
 +    const router = require('​routers/​exrouter'​)(model)
 +    return router
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​routers/​exrouter.js===
 +
 +<code javascript expresso/​backend/​routers/​exrouter.js>​
 +'use strict'​
 +
 +
 +const lodash = require('​lodash'​)
 +
 +const error = {
 +    invalidRequest:​ {
 +        jsonrpc: "​2.0",​
 +        error: {
 +            code: -32600,
 +            message: "​Invalid Request"​
 +        },
 +        id: null
 +    },
 +    parseError: {
 +        jsonrpc: "​2.0",​
 +        error: {
 +            code: -32700,
 +            message: "Parse error"
 +        },
 +        id: null
 +    },
 +    methodNotFound:​ {
 +        jsonrpc: "​2.0",​
 +        error: {
 +            code: -32601,
 +            message: "​Method not found"
 +        },
 +        id: 1
 +    },
 +    internalError:​ {
 +        jsonrpc: "​2.0",​
 +        error: {
 +            code: -32603,
 +            message: "​Internal error"
 +        },
 +        id: 0
 +    }
 +}
 +
 +module.exports = function(model) {
 +
 +    const express = require('​express'​)
 +    const router = express.Router()
 +
 +    function responder(req,​ res) {
 +
 +        console.log({ body: req.body })
 +
 +        if(!lodash.has(req,​ '​body.method'​)) {
 +            res.send(error.invalidRequest)
 +            return
 +        }
 +
 +        if(!lodash.has(req,​ '​body.id'​)) {
 +            res.send(error.invalidRequest)
 +            return
 +        }
 +
 +        if(!lodash.has(req,​ '​body.params'​)) {
 +            res.send(error.invalidRequest)
 +            return
 +        }
 +
 +        if (!lodash.isString(req.body.method)) {
 +            res.send(error.invalidRequest)
 +            return
 +        }
 +
 +        if (!lodash.isString(req.body.id) && !lodash.isNumber(req.body.id)) {
 +            res.send(error.invalidRequest)
 +            return
 +        }
 +
 +        if (typeof(req.body.params) === '​undefined'​) {
 +            res.send(error.invalidRequest)
 +            return
 +        }
 +
 +        if (typeof(model[req.body.method]) !== '​function'​) {
 +            res.send(error.methodNotFound)
 +            return
 +        }
 +
 +        const method = req.body.method
 +        var params = req.body.params
 +        const id = req.body.id
 +
 +        if (method === '​check'​) {
 +            if (lodash.has(req,​ '​session.userId'​)) {
 +                params = { id: req.session.userId }
 +            } else {
 +                params = { id: -1 }
 +            }
 +        }
 +
 +        var modelPromise = model[method](params)
 +
 +        modelPromise
 +            .then(function(result) {
 +
 +                if (method === '​login'​ && lodash.has(result,​ '​[0].id'​)) {
 +                        req.session.userId = result[0].id
 +                        req.session.userProfile = result[0]
 +                }
 +
 +                if (method === '​check'​) {
 +                    console.log({ checkResult:​ result })
 +                    if (lodash.has(req,​ "​session.userId"​) && lodash.has(result,​ "​[0].id"​)) {
 +                        result = true
 +                    } else {
 +                        result = false
 +                    }
 +                }
 +
 +                res.send({
 +                    jsonrpc: "​2.0",​
 +                    result: result,
 +                    id: id
 +                })
 +            })
 +            .catch(function(err) {
 +                console.log(err)
 +                res.send({
 +                    jsonrpc: "​2.0",​
 +                    error: {
 +                        code: -32603,
 +                        message: "​Internal error"
 +                    },
 +                    id: id
 +                })
 +            })
 +    }
 +
 +    router.post('/',​ responder)
 +    return router
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​routers/​login.js===
 +
 +<code javascript expresso/​backend/​routers/​login.js>​
 +'use strict'​
 +
 +module.exports = function(knex) {
 +    const model = require('​models/​login'​)(knex)
 +    const router = require('​routers/​exrouter'​)(model)
 +
 +    return router
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​routers/​users.js===
 +
 +<code javascript expresso/​backend/​routers/​users.js>​
 +'use strict'​
 +
 +module.exports = function(knex) {
 +    const model = require('​models/​users'​)(knex)
 +    const router = require('​routers/​exrouter'​)(model)
 +    return router
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​seeds/​20181214085527_customers.js===
 +
 +<code javascript expresso/​backend/​seeds/​20181214085527_customers.js>​
 + 
 +exports.seed = function(knex,​ Promise) {
 +    var rows = []
 +
 +    rows.push({"​id":​0,"​name":"​Borodin Oleg","​phone1":"​79520587264","​phone2":"​79520587232","​city":"​Kenigsberg","​agreement":"​SPN1645","​created_at":"​2018-12-13T23:​36:​20.925Z","​updated_at":"​2018-12-13T23:​36:​20.925Z"​})
 +
 +
 +    console.log(rows.length)
 +    var chunkSize = 50
 +    return knex('​customers'​).del()
 +        .then(function() {
 +            return knex.batchInsert('​customers',​ rows, chunkSize)
 +        })
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​backend/​seeds/​20181214085527_users.js===
 +
 +<code javascript expresso/​backend/​seeds/​20181214085527_users.js>​
 + 
 +exports.seed = function(knex,​ Promise) {
 +    var rows = []
 +
 +    rows.push({"​id":​5,"​name":"​qwerty","​password":"​12345","​superuser":​true,"​gecos":"​Zurb Gnorb","​created_at":"​2018-11-25T08:​36:​32.344Z","​updated_at":"​2018-11-25T08:​36:​32.344Z"​})
 +    rows.push({"​id":​2,"​name":"​kokkolo","​password":"​12345","​superuser":​false,"​gecos":"​Kokkolo Lokkalo","​created_at":"​2018-12-14T01:​36:​38.706Z","​updated_at":"​2018-12-14T01:​36:​38.706Z"​})
 +    rows.push({"​id":​1,"​name":"​user","​password":"​12345","​superuser":​false,"​gecos":"​Anre Brown","​created_at":"​2018-12-15T08:​55:​23.180Z","​updated_at":"​2018-12-15T08:​55:​23.180Z"​})
 +
 +
 +    console.log(rows.length)
 +    var chunkSize = 50
 +    return knex('​users'​).del()
 +        .then(function() {
 +            return knex.batchInsert('​users',​ rows, chunkSize)
 +        })
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​backend/​utils/​exdaemon.js===
 +
 +<code javascript expresso/​backend/​utils/​exdaemon.js>​
 +const child_process = require('​child_process'​)
 +
 +
 +module.exports = function(nodeBin) {
 +
 +    function child(exe, args, env) {
 +        const child = child_process.spawn(exe,​ args, {
 +            detached: true,
 +            stdio: ['​ignore',​ '​ignore',​ '​ignore'​],​
 +            env: env
 +        })
 +        child.unref()
 +        return child
 +    }
 +
 +    if (process.env.__daemon) {
 +        return process.pid
 +    }
 +    process.env.__daemon = true
 +
 +    var args = [].concat(process.argv)
 +    var node = args.shift()
 +    var env = process.env
 +    child(node, args, env)
 +    return process.exit()
 +}</​code>​
 +
 +
 +===expresso/​backend/​utils/​exlog.js===
 +
 +<code javascript expresso/​backend/​utils/​exlog.js>​
 +
 +const path = require('​path'​)
 +const fs = require('​fs'​)
 +const util = require('​util'​)
 +const exmkdir = require('​exmkdir'​)
 +
 +module.exports = function(logDir) {
 +
 +    exmkdir(logDir)
 +
 +    if (!exmkdir(logDir)) {
 +        console.log('​Cannot write to log directory ' + logDir + '. Exit process.'​)
 +        process.exit(1)
 +    }
 +
 +    var consoleLog = function(data) {
 +        var date = new Date().toISOString()
 +        const logFile = fs.createWriteStream(logDir + '/​debug.log',​ {flags : '​a'​})
 +        const logStdout = process.stdout
 +        logFile.write(date + ' ' + util.format(data) + '​\n'​)
 +        logStdout.write(date + ' ' + util.format(data) + '​\n'​)
 +    }
 +
 +    return consoleLog
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​utils/​exmkdir.js===
 +
 +<code javascript expresso/​backend/​utils/​exmkdir.js>​
 +
 +var fs = require('​fs'​)
 +
 +module.exports = function(dir) {
 +        var dummyFile = dir + '/​.dummy'​
 +
 +        if (fs.existsSync(dir)) {
 +            try {
 +                var fd = fs.openSync(dummyFile,​ '​w'​)
 +                fs.writeSync(fd,​ process.pid)
 +                fs.closeSync(fd)
 +                fs.unlinkSync(dummyFile)
 +                return true
 +            } catch (err) {
 +                return false
 +            }
 +        }
 +
 +        if (!fs.existsSync(dir)) {
 +            try { 
 +                fs.mkdirSync(dir)
 +                return true
 +            } catch {
 +                return false
 +            }
 +        }
 +    }
 +</​code>​
 +
 +
 +===expresso/​backend/​utils/​expid.js===
 +
 +<code javascript expresso/​backend/​utils/​expid.js>​
 +'use strict'​
 +
 +var fs = require('​fs'​)
 +var path = require('​path'​)
 +var exmkdir = require('​exmkdir'​)
 +
 +var create = function(pidFile) {
 +    var pidDir = path.basename(path.dirname(pidFile))
 +
 +    var _write = function(path) {
 +        try {
 +            var fd = fs.openSync(path,​ '​w'​)
 +        } catch (err) {
 +            return false;
 +        }
 +        fs.writeSync(fd,​ process.pid)
 +        fs.closeSync(fd)
 +        return true
 +    }
 +
 +    if (!exmkdir(pidDir)) {
 +        console.log('​Cannot write to  directory ' + pidDir + '. Exit process.'​)
 +        process.exit(1)
 +    }
 +
 +    if (!_write(pidFile)) {
 +        console.log('​Cannot write pid file ' + pidFile)
 +        process.exit(1)
 +    }
 +}
 +
 +var drop = function(path) {
 +    try {
 +        fs.unlinkSync(path);​
 +        return true;
 +    } catch (err) {
 +        return false;
 +    }
 +}
 +
 +
 +module.exports = {
 +    create: create,
 +    drop: drop
 +}</​code>​
 +
 +
 +===expresso/​backend/​utils/​exsig.js===
 +
 +<code javascript expresso/​backend/​utils/​exsig.js>​
 +
 +const expid = require('​expid'​)
 +
 +module.exports = function(pidFile) {
 +
 +    function _exit() {
 +        expid.drop(pidFile)
 +        setTimeout(function() { 
 +            process.exit(0)
 +        }, 100)
 +    }
 +
 +    process.on('​SIGINT',​ _exit)
 +    process.on('​SIGTERM',​ _exit)
 +
 +    process.on('​uncaughtException',​ function (err) {
 +        if (err) {
 +            console.log("​caughtException but no error msg" + err.stack)
 +            setTimeout(function() { 
 +                process.exit(1)
 +            }, 500)
 +        }
 +    })
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​exconfig.js===
 +
 +<code javascript expresso/​backend/​exconfig.js>​
 +
 +module.exports = {
 +    nodeBin: '/​usr/​local/​bin/​node',​
 +    appDir: '/​home/​user/​expresso/​backend',​
 +    publicDir: '/​home/​user/​expresso/​backend/​public',​
 +    uploadDir: '/​home/​user/​expresso/​backend/​uploads',​
 +    port: 3100,
 +    address: '​127.0.0.1',​
 +    runUser: '​ziggi',​
 +    runGroup: '​wheel',​
 +    logDir: '/​home/​user/​expresso/​backend/​logs',​
 +    pidFile: '/​home/​user/​expresso/​backend/​run/​pid',​
 +    runDir: '/​home/​user/​expresso/​backend/​run'​
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​csv2knex.js===
 +
 +<code javascript expresso/​backend/​csv2knex.js>​
 +#​!/​usr/​bin/​env node
 +
 +'use strict'​
 +
 +const fs = require('​fs'​)
 +const readline = require('​readline'​)
 +
 +const knexfile = require('​./​knexfile'​)
 +const knex = require('​knex'​)(
 +    knexfile.development
 +)
 +
 +const customers = require('​./​models/​customers'​)(knex)
 +
 +var rd = readline.createInterface({
 +    input: fs.createReadStream('​customers.csv'​),​
 +    console: false
 +})
 +
 +function phoneNormalize(phone) {
 +    //if (phone.length === 10) {
 +    //    phone = '​7'​ + phone
 +    //}
 +    phone = phone.replace(/​[\-.()]/​g,​ ''​)
 +    phone = phone.split('​ ')[0]
 +    return phone
 +}
 +
 +var rows = []
 +
 +//City,Full Name,​Phone2,​Phone1,​Id
 +// 0          1      2       ​3 ​    4
 +rd.on('​line',​ (line) => {
 +    var arr = line.split(','​)
 +    var customer = {
 +        agreement: arr[4].trim(),​
 +        name: arr[1].trim(),​
 +        phone1: arr[2].trim(),//​.replace(/​[()\-.+]/​g,​ ''​),​
 +        phone2: arr[3].trim(),//​.replace(/​[()\-.+]/​g,​ ''​),​
 +        city: arr[0].trim() + '',​
 +        //email: arr[6].trim() + ''​
 +    }
 +
 +    customer.phone1 = phoneNormalize(customer.phone1)
 +    customer.phone2 = phoneNormalize(customer.phone2)
 +    //​console.log(customer)
 +
 +    rows.push(customer)
 +})
 +
 +
 +
 +rd.on('​close',​ () => {
 +    console.log({
 +        len: rows.length
 +    })
 +
 +    var chunkSize = 50
 +    knex.batchInsert('​customers',​ rows, chunkSize)
 +        .returning('​id'​)
 +        .then(function(ids) {
 +        })
 +        .catch(function(err) {
 +            console.log(err)
 +        })
 +        .finally(function() {
 +            knex.destroy();​
 +        })
 +})
 +</​code>​
 +
 +
 +===expresso/​backend/​exserv.js===
 +
 +<code javascript expresso/​backend/​exserv.js>​
 +
 +'use strict'​
 +
 +const exconfig = require('​exconfig'​)
 +
 +const path = require('​path'​)
 +const fs = require('​fs'​)
 +const util = require('​util'​)
 +const lodash = require('​lodash'​)
 +
 +const minimist = require('​minimist'​)
 +var argv = minimist(process.argv.slice(2))
 +
 +if (argv.help) {
 +    console.log('​Expresso sample web application'​)
 +    console.log('​Usage:​ expresso [options]'​)
 +    console.log(' ​   --daemon daemonize process'​)
 +    process.exit()
 +}
 +
 +//*** make log ***=
 +var exlog = require('​exlog'​)(exconfig.logDir)
 +console.log = exlog
 +console.error = exlog
 +
 +// *** throw ***
 +//throw new Error('​Boum!'​)
 +
 +// *** attach express plugins ***
 +const express = require('​express'​)
 +const helmet = require('​helmet'​)
 +const cookieParser = require('​cookie-parser'​)
 +const bodyParser = require('​body-parser'​)
 +const compression = require('​compression'​)
 +const responseTime = require('​response-time'​)
 +const morgan = require('​morgan'​)
 +const lowercasePaths = require('​express-lowercase-paths'​)
 +const excors = require('​excors'​)
 +
 +const app = express()
 +
 +var formatStr = ':​date[iso] :​remote-addr :method :url :status :​res[content-length] :​res[content-type] :​response-time ms'
 +var accessLog = fs.createWriteStream(exconfig.logDir + '/​access.log',​ { flags: '​a'​ })
 +app.use(morgan(formatStr,​ { stream: accessLog }))
 +app.use(morgan(formatStr))
 +
 +app.use(excors())
 +app.use(compression())
 +app.use(lowercasePaths())
 +app.use(helmet())
 +app.use(express.static(exconfig.publicDir))
 +app.use(cookieParser())
 +app.use(bodyParser.json())
 +app.use(responseTime())
 +
 +// *** create session *** 
 +const session = require('​express-session'​)
 +
 +const FileStore = require('​session-file-store'​)(session)
 +app.use(session({
 +    store: new FileStore({ path: exconfig.runDir }),
 +    name: '​session',​
 +    secret: '​efwe987ysdf9fsd69f9ds',​
 +    resave: false,
 +    rolling: true,
 +    saveUninitialized:​ true,
 +    cookie: { 
 +            secure: false,
 +            maxAge: 3600 * 60 * 1000,
 +            httpOnly: false
 +    }
 +}))
 +
 +// *** create knex shared object ***
 +const knexfile = require('​knexfile'​)
 +const knex = require('​knex'​)(knexfile.development)
 +
 +// *** set routes ***
 +
 +var login = require('​./​routers/​login'​)(knex)
 +app.use('/​api/​login',​ login)
 +
 +app.use(function(req,​ res, next) {
 +    if (typeof(req.session.userId) != '​undefined'​) {
 +        req.session.touch()
 +        next()
 +    } else {
 +         ​res.sendFile(path.join(exconfig.appDir,​ '/​public/​index.html'​))
 +    }
 +})
 +
 +var users = require('​./​routers/​users'​)(knex)
 +app.use('/​api/​users',​ users)
 +
 +var customers = require('​./​routers/​customers'​)(knex)
 +app.use('/​api/​customers',​ customers)
 +
 +app.get('/​*',​ function(req,​ res) {
 +     ​res.sendFile(path.join(exconfig.appDir,​ '/​public/​index.html'​))
 +})
 +
 +// *** daemonize process *** 
 +const exdaemon = require('​exdaemon'​)
 +if(argv.daemon) {
 +    exdaemon()
 +}
 +
 +// *** write pid file *** 
 +const expid = require('​expid'​)
 +expid.create(exconfig.pidFile)
 +
 +// *** signal ans exeption handling ***
 +const exsig = require('​exsig'​)
 +exsig(exconfig.pidFile)
 +
 +// *** listen socket ***
 +const cluster = require('​cluster'​)
 +if (cluster.isMaster) {
 +    var cpuCount = require('​os'​).cpus().length + 1
 +    for (var i = 0; i < cpuCount; i += 1) {
 +        cluster.fork();​
 +    }
 +} else {
 +    app.listen(exconfig.port,​ exconfig.address,​ null, function() {
 +        try {
 +            process.setgid(exconfig.runGroup)
 +            process.setuid(exconfig.runUser)
 +        } catch (err) {
 +        console.log('​Cannot change process user and group'​)
 +            process.exit(1)
 +        }
 +    })
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​backend/​knexfile.js===
 +
 +<code javascript expresso/​backend/​knexfile.js>​
 +
 +var path = require('​path'​)
 +
 +module.exports = {
 +    development:​ {
 +        //debug: true,
 +        client: '​pg',​
 +        connection:'​postgres://​pgsql@localhost/​expresso',​
 +        useNullAsDefault:​ true,
 +        migrations: {
 +            directory: __dirname + '/​migrations'​
 +        },
 +        seeds: {
 +            directory: __dirname + '/​seeds'​
 +        },
 +        //pool: { 
 +        //    min: 0,
 +        //    max: 10
 +        //},
 +        //​acquireConnectionTimeout:​ 10000
 +    },
 +    staging: {
 +        client: '​pg',​
 +        connection:'​postgres://​localhost/​expresso',​
 +        useNullAsDefault:​ true,
 +        migrations: {
 +            directory: __dirname + '/​migrations'​
 +        },
 +        seeds: {
 +            directory: __dirname + '/​seeds'​
 +        }
 +    },
 +    production: {
 +        client: '​pg',​
 +        connection:'​postgres://​localhost/​expresso',​
 +        useNullAsDefault:​ true,
 +        migrations: {
 +            directory: __dirname + '/​migrations'​
 +        },
 +        seeds: {
 +            directory: __dirname + '/​seeds'​
 +        }
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​backend/​knexport.js===
 +
 +<code javascript expresso/​backend/​knexport.js>​
 +#​!/​usr/​bin/​env node
 +
 +'use strict'​
 +
 +
 +Object.defineProperty(Date.prototype,​ '​timestamp',​ {
 +    value: function() {
 +        function pad2(n) {
 +            return (n < 10 ? '​0'​ : ''​) + n;
 +        }
 +
 +        return this.getFullYear() +
 +               ​pad2(this.getMonth() + 1) + 
 +               ​pad2(this.getDate()) +
 +               ​pad2(this.getHours()) +
 +               ​pad2(this.getMinutes()) +
 +               ​pad2(this.getSeconds());​
 +    }
 +});
 +
 +const timestamp = new Date().timestamp()
 +
 +const exconfig = require('​./​exconfig'​)
 +const knexfile = require(exconfig.appDir + '/​knexfile'​)
 +const knex = require('​knex'​)(knexfile.development)
 +const fs = require('​fs'​)
 +
 +//var tableName = '​domains'​
 +var header = function(tableName) {
 +  return ` 
 +exports.seed = function(knex,​ Promise) {
 +    var rows = []
 +`
 +}
 +
 +var footer = function(tableName) {
 +    return `
 +
 +    console.log(rows.length)
 +    var chunkSize = 50
 +    return knex('​${tableName}'​).del()
 +        .then(function() {
 +            return knex.batchInsert('​${tableName}',​ rows, chunkSize)
 +        })
 +}
 +`
 +}
 +
 +var tables = [ '​users',​ '​customers'​ ]
 +
 +tables.forEach(function(item) {
 +    console.log('​export table ' + item)
 +    knex
 +        .select()
 +        .from(item)
 +        .then(function(result) {
 +            var out = fs.createWriteStream('​./​seeds/'​ + timestamp + '​_'​ + item + '​.js',​ {flags : '​w'​})
 +            out.write(header(item) + '​\n'​)
 +            result.forEach(function(item) {
 +                out.write(' ​   rows.push('​ + JSON.stringify(item) + '​)\n'​)
 +            })
 +            out.write(footer(item) + '​\n'​)
 +            out.close()
 +
 +        })
 +        .finally(function() {
 +            knex.destroy();​
 +        })
 +})
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app-footer/​app-footer.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​app-footer/​app-footer.component.html>​
 +<div id="​app-footer">​
 +    <div class="​grid-container">​
 +        <div class="​grid-x grid-margin-x align-center">​
 +            <div class="​cell medium-8">​
 +                <hr />
 +
 +                <p class="​text-center">​
 +                    <​small>​Made by <a href="​http://​wiki.unix7.org">​Borodin Oleg</​a></​small>​
 +                </p>
 +
 +            </​div>​
 +        </​div>​
 +    </​div>​
 +</​div>​
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app-footer/​app-footer.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​app-footer/​app-footer.component.ts>​
 +import { Component, OnInit } from '​@angular/​core';​
 +
 +@Component({
 +  selector: '​app-footer',​
 +  templateUrl:​ '​./​app-footer.component.html',​
 +  styleUrls: ['​./​app-footer.component.scss'​]
 +})
 +export class AppFooterComponent implements OnInit {
 +
 +  constructor() { }
 +
 +  ngOnInit() {
 +  }
 +
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app-header/​app-header.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​app-header/​app-header.component.html>​
 +<div id="​app-header"​ class="​margin-bottom-2">​
 +
 +    <div class="​top-bar"​ id="​top-bar">​
 +
 +        <div class="​top-bar-left"​ *ngIf="​loginService.isSuperuser()">​
 +
 +            <ul class="​dropdown menu" data-dropdown-menu>​
 +                <​li><​a href="#"><​i class="​my-menu-icon"></​i></​a></​li>​
 +                <li class="​menu-text">​NgII <i class="​fi-sheriff-badge"​ style="​font-size:​ 1.2em;"></​i></​li>​
 +                <li>
 +                    <a href="#">​Units</​a>​
 +                    <ul class="​menu hover">​
 +
 +                        <​li><​a routerLink="/​customers">​Customers</​a></​li>​
 +                        <​li><​a routerLink="/​users">​Users</​a></​li>​
 +                        <​li><​a routerLink="/">​Home</​a></​li>​
 +                    </ul>
 +                </li>
 +                <li>
 +                    <a (click)="​loginService.logout()">​
 +                        <i class="​fi-power"​ style="​font-size:​ 1.3em;"></​i>​
 +                    </a>
 +                </li>
 +            </ul>
 +
 +        </​div>​
 +
 +        <div class="​top-bar-left"​ *ngIf="​loginService.isUser() && !loginService.isSuperuser()">​
 +
 +            <ul class="​dropdown menu" data-dropdown-menu>​
 +                <​li><​a href="#"><​i class="​my-menu-icon"></​i></​a></​li>​
 +                <li class="​menu-text">​NgII</​li>​
 +                <li>
 +                    <a href="#">​Units</​a>​
 +                    <ul class="​menu hover">​
 +
 +                        <​li><​a routerLink="/​customers">​Customers</​a></​li>​
 +                        <​li><​a routerLink="/">​Home</​a></​li>​
 +                    </ul>
 +                </li>
 +                <li>
 +                    <a (click)="​loginService.logout()">​
 +                        <i class="​fi-power"​ style="​font-size:​ 1.3em;"></​i>​
 +                    </a>
 +                </li>
 +            </ul>
 +
 +        </​div>​
 +
 +
 +        <div class="​top-bar-left" ​ *ngIf="​!loginService.isLogin()">​
 +            <ul class="​dropdown menu" data-dropdown-menu>​
 +                <​li><​a href="#"><​i class="​my-menu-icon"></​i></​a></​li>​
 +                <li class="​menu-text">​NgII</​li>​
 +                <li>
 +                    <a href="#">​Units</​a>​
 +                    <ul class="​menu hover">​
 +                        <​li><​a routerLink="/">​Home</​a></​li>​
 +                    </ul>
 +                </li>
 +
 +                <​li><​a routerLink="/​login">​Login</​a></​li>​
 +            </ul>
 +        </​div>​
 +
 +    </​div>​
 +
 +</​div></​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app-header/​app-header.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​app-header/​app-header.component.ts>​
 +import { Component, OnInit, OnChanges, SimpleChanges } from '​@angular/​core';​
 +
 +import { LoginService } from '​../​login.service'​
 +
 +declare var $ : any
 +
 +@Component({
 +    selector: '​app-header',​
 +    templateUrl:​ '​./​app-header.component.html',​
 +    styleUrls: ['​./​app-header.component.scss'​]
 +})
 +export class AppHeaderComponent implements OnInit, OnChanges {
 +
 +    constructor(
 +        public loginService:​ LoginService
 +    ) {}
 +
 +    ngOnInit() {
 +        $('#​app-header'​).foundation()
 +    }
 +
 +    ngAfterViewInit() {
 +        $('#​app-header'​).foundation()
 +    }
 +
 +    ngOnChanges(changes:​ SimpleChanges) {
 +        $('#​app-header'​).foundation()
 +    }
 +
 +    ngAfterContentChecked() {
 +        $('#​app-header'​).foundation()
 +    }
 +
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​customers/​customers.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​customers/​customers.component.html>​
 +<div>
 +    <​app-header></​app-header>​
 +
 +    <div class="​margin-left-2 margin-right-2">​
 +        <div class="​grid-container">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-6">​
 +
 +                    <div>
 +                        <form accept-charset="​UTF-8" ​ [formGroup]="​phoneForm"​ (ngSubmit)="​searchByPhone(phoneForm)">​
 +
 +                            <div class="​input-group">​
 +                                <span class="​input-group-label">​Search</​span>​
 +                                <input class="​input-group-field"​ type="​text"​ formControlName="​phone">​
 +                                <div class="​input-group-button">​
 +                                    <input type="​submit"​ class="​button"​ value="​Submit">​
 +                                </​div>​
 +                            </​div>​
 +
 +                        </​form>​
 +
 +                    </​div>​
 +
 +                    <div *ngIf="​phone && !(customerList.length > 0) ">
 +                        <​p>​Yet not found.</​p>​
 +                    </​div>​
 +
 +                    <div *ngIf="​customerList.length > 0">
 +
 +                        <​h6>​{{ timestamp() }}, total <b>{{ customerList.length }}</​b></​h6>​
 +
 +                        <div *ngFor="​let item of customerList;​ let i = index">​
 +                            <hr />
 +                            <​h6>​{{ i + 1 }}. <b>{{ item.name }}</​b></​h6>​
 +
 +                            <​table>​
 +                                <​tbody>​
 +                                    <tr>
 +                                        <td width="​14em">​Location</​td>​
 +                                        <​td><​b>​{{ item.city }}</​b></​td>​
 +                                    </tr>
 +                                    <tr>
 +                                        <​td>​Order</​td>​
 +                                        <​td>​{{ item.agreement }}</​td>​
 +                                    </tr>
 +                                    <tr>
 +                                        <​td>​Phone 1</​td>​
 +                                        <​td><​a (click)="​search(item.phone1)">​{{ item.phone1 }}</​a></​td>​
 +                                    </tr>
 +                                    <tr>
 +                                        <​td>​Phone 2</​td>​
 +                                        <​td><​a (click)="​search(item.phone2)">​{{ item.phone2 }}</​a></​td>​
 +                                    </tr>
 +
 +                                </​tbody>​
 +                            </​table>​
 +
 +                        </​div>​
 +
 +
 +                        <p>
 +                            <a (click)="​router.navigate([ '/'​ ])">​
 +                                <i class="​fi-arrow-left"></​i>​ Back to home
 +                            </a>
 +                        </p>
 +
 +                    </​div>​
 +                </​div>​
 +            </​div>​
 +
 +        </​div>​
 +    </​div>​
 +
 +    <​app-footer></​app-footer>​
 +</​div></​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​customers/​customers.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​customers/​customers.component.ts>​
 +import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges,​ Input, Output, EventEmitter } from '​@angular/​core'​
 +
 +import { Router, ActivatedRoute } from '​@angular/​router'​
 +import { FormGroup, FormControl,​ FormBuilder,​ Validators } from '​@angular/​forms'​
 +
 +import { AppHeaderComponent } from '​../​app-header/​app-header.component'​
 +import { AppFooterComponent } from '​../​app-footer/​app-footer.component'​
 +
 +import { RPCService, RPCResponce,​ RPCError } from '​../​rpc.service'​
 +import { CustomersService } from '​../​customers.service'​
 +import { Customer } from '​../​models/​customer.model'​
 +
 +import * as moment from '​moment-mini'​
 +
 +@Component({
 +  selector: '​domain',​
 +  templateUrl:​ '​./​customers.component.html',​
 +  styleUrls: ['​./​customers.component.scss'​]
 +})
 +export class CustomersComponent implements OnInit {
 +
 +    phoneForm: FormGroup
 +
 +    phone: string = ''​
 +
 +    customer: Customer = {
 +        id: -1,
 +        name: '',​
 +        phone1: '',​
 +        phone2: '',​
 +        city: '',​
 +        agreement: ''​
 +    }
 +
 +    customerList:​ Customer[] = []
 +
 +    constructor(
 +        private formBuilder:​ FormBuilder,​
 +        private router: Router,
 +        private route: ActivatedRoute,​
 +        private customersService:​ CustomersService
 +    ) {}
 +
 +    timestamp() {
 +        return moment().format('​hh:​mm:​ss a')
 +    }
 +
 +    searchByPhone(form) {
 +        let phone = form.value.phone
 +        this.router.navigate([ '/​customers/'​ + phone ])
 +    }
 +
 +    search(some) {
 +        this.router.navigate([ '/​customers/'​ + some ])
 +    }
 +
 +    ngOnChanges(changes:​ SimpleChanges) {
 +    }
 +
 +    ngOnInit() {
 +        this.phoneForm = this.formBuilder.group({
 +            phone: [ this.phone ],
 +        })
 +
 +        this.route.paramMap.subscribe(params => {
 +            this.phone = params.get('​phone'​)
 +
 +            this.phone = this.phone.replace(/​[\-.()+]/​g,​ ''​)
 +
 +            this.phoneForm = this.formBuilder.group({
 +                phone: [ this.phone ],
 +            })
 +
 +            if (this.phone) {
 +                let customer : Customer = {
 +                    phone1: this.phone,
 +                    phone2: this.phone,
 +                    name: this.phone
 +                }
 +                this.customersService
 +                    .find(customer)
 +                    .subscribe((res:​ RPCResponce<​Customer[]>​) => {
 +                        if (res.result.length > 0) {
 +                            this.customerList = res.result
 +                        }
 +                    })
 +            }
 +
 +        })
 +    }
 +
 +
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​guards/​superlogin.guard.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​guards/​superlogin.guard.ts>​
 +import { Injectable } from '​@angular/​core'​
 +import { Router, CanActivate,​ ActivatedRouteSnapshot,​ RouterStateSnapshot } from "​@angular/​router"​
 +import { Observable } from "​rxjs"​
 +
 +import * as Cookies from '​es-cookie'​
 +
 +import { LoginService } from '​../​login.service'​
 +
 +
 +@Injectable()
 +export class SuperloginGuard implements CanActivate {
 +
 +    cookieName: string = '​session'​
 +
 +    constructor(
 +        private loginService:​ LoginService,​
 +        private router: Router
 +    ) {}
 +
 +    canActivate(
 +        route: ActivatedRouteSnapshot,​
 +        state: RouterStateSnapshot
 +    ) : Observable<​boolean>​ | boolean {
 +
 +        if (this.loginService.isSuperuser()) {
 +            return true
 +        }
 +        this.loginService.returnUrl = state.url
 +        this.router.navigate(['/​login'​])
 +        return false
 +    }
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​guards/​login.guard.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​guards/​login.guard.ts>​
 +import { Injectable } from '​@angular/​core'​
 +import { Router, CanActivate,​ ActivatedRouteSnapshot,​ RouterStateSnapshot } from "​@angular/​router"​
 +import { Observable } from "​rxjs"​
 +
 +import * as Cookies from '​es-cookie'​
 +
 +import { LoginService } from '​../​login.service'​
 +
 +
 +@Injectable()
 +export class LoginGuard implements CanActivate {
 +
 +
 +    constructor(
 +        private loginService:​ LoginService,​
 +        private router: Router
 +    ) {}
 +
 +    canActivate(
 +        route: ActivatedRouteSnapshot,​
 +        state: RouterStateSnapshot
 +    ) : Observable<​boolean>​ | boolean {
 +
 +
 +        if (this.loginService.isUser()) {
 +            return true
 +        }
 +        this.loginService.returnUrl = state.url
 +        this.router.navigate(['/​login'​])
 +        return false
 +    }
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​home/​home.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​home/​home.component.html>​
 +<div>
 +    <​app-header></​app-header>​
 +
 +    <div class="​margin-left-2 margin-right-2">​
 +
 +
 +        <div id="​lorem"​ class="​grid-container">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-8">​
 +
 +                    <​h5>​Sample call from:</​h5>​
 +
 +                    <ul class="​list-square">​
 +                        <li *ngFor="​let item of samplePhoneList;​ let i = index">​
 +                            <a target="​_blank"​ rel="​noopener noreferrer"​ href='#/​customers/​{{ item }}'>​{{ item }}</​a>​
 +                        </li>
 +                    </ul>
 +
 +                    <​h5>​Sample search from parial string:</​h5>​
 +
 +                    <ul class="​list-square">​
 +                        <li *ngFor="​let item of partialSamples;​ let i = index">​
 +                            <a target="​_blank"​ rel="​noopener noreferrer"​ href='#/​customers/​{{ item }}'>​{{ item }}</​a>​
 +                        </li>
 +                    </ul>
 +
 +
 +                </​div>​
 +            </​div>​
 +        </​div>​
 +
 +    </​div>​
 +
 +    <​app-footer></​app-footer>​
 +
 +</​div></​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​home/​home.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​home/​home.component.ts>​
 +import { Component, OnInit } from '​@angular/​core';​
 +
 +@Component({
 +  selector: '​app-home',​
 +  templateUrl:​ '​./​home.component.html',​
 +  styleUrls: ['​./​home.component.scss'​]
 +})
 +export class HomeComponent implements OnInit {
 +
 +    samplePhoneList:​ string[] = [
 +        '​16192188689',​
 +        '​1141855692',​
 +        '​7118221699',​
 +        '​6315369258',​
 +        '​18549937810',​
 +        '​16192',​
 +    ]
 +
 +    partialSamples:​ string[] = [
 +        '​16192',​
 +        '​7911',​
 +        '​Anre',​
 +        '​Princess',​
 +    ]
 +
 +    ngOnInit() {
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​login/​login.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​login/​login.component.html>​
 +<div>
 +
 +    <div class="​top-bar">​
 +        <div class="​top-bar-left padding-left-2">​
 +            <ul class="​menu">​
 +                <li class="​menu-text"><​i class="​fi-shield"​ style="​font-size:​ 1.3em;"></​i></​li>​
 +                <li class="​menu-text">​Login</​li>​
 +            </ul>
 +        </​div>​
 +    </​div>​
 +
 +
 +    <div class="​grid-container">​
 +        <div class="​grid-x grid-padding-x align-center">​
 +            <div class="​cell small-8 medium-4 large-3">​
 +
 +                <div class="​card padding-2 margin-top-3">​
 +
 +                    <form accept-charset="​UTF-8"​ [formGroup]="​loginForm"​ (ngSubmit)="​login(loginForm)">​
 +                        <​label>​Login name (demo: user)
 +                            <input type="​text"​ formControlName="​name">​
 +                        </​label>​
 +                        <​label>​Password (demo: 12345)
 +                            <input type="​password"​ formControlName="​password">​
 +                        </​label>​
 +                        <div class="​text-center">​
 +                            <button class="​button small" type="​submit">​Submit</​button>​
 +                        </​div>​
 +                    </​form>​
 +
 +                    <div class="​margin-2">​
 +                        <div class="​text-center">​
 +                            {{ message }}
 +                        </​div>​
 +                    </​div>​
 +
 +                </​div>​
 +
 +            </​div>​
 +        </​div>​
 +    </​div>​
 +
 +</​div></​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​login/​login.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​login/​login.component.ts>​
 +import { Component, OnInit, OnDestroy } from '​@angular/​core'​
 +import { FormGroup, FormControl,​ FormBuilder,​ Validators } from '​@angular/​forms'​
 +import { HttpClient } from '​@angular/​common/​http'​
 +
 +import { LoginService } from '​../​login.service'​
 +
 +@Component({
 +    selector: '​login',​
 +    templateUrl:​ '​./​login.component.html',​
 +    styleUrls: [ '​./​login.component.scss'​ ]
 +})
 +export class LoginComponent implements OnInit {
 +
 +    loginForm: FormGroup
 +    message: string = ''​
 +    attemptCount:​ number = 0
 +
 +    constructor(
 +        private formBuilder:​ FormBuilder,​
 +        private loginService:​ LoginService
 +    ) {}
 +
 +    login(event) {
 +        if (this.loginService.login(event.value.name,​ event.value.password)) {
 +            this.message = `Wow! Login successful`
 +            return
 +        }
 +        setTimeout(() => {
 +            this.attemptCount++
 +            this.message = `Login incorrect. Attempt ${this.attemptCount}`
 +        }, 1000 )
 +
 +    }
 +
 +    ngOnInit() {
 +        this.loginForm = this.formBuilder.group({
 +            name: [ ''​ ],
 +            password: [ ''​ ]
 +        })
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​models/​customer.model.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​models/​customer.model.ts>​
 +export interface Customer {
 +    id?: number
 +    name?: string
 +    password?: string
 +    phone1?: string
 +    phone2?: string
 +    agreement?: string
 +    city?: string
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​models/​user.model.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​models/​user.model.ts>​
 +export interface User {
 +    id?: number
 +    name?: string
 +    password?: string
 +    superuser?: boolean
 +    gecos?: string
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​not-found/​not-found.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​not-found/​not-found.component.html>​
 +<div>
 +
 +    <​app-header></​app-header>​
 +
 +    <div class="​margin-left-2 margin-right-2">​
 +
 +        <div id="​lorem"​ class="​grid-container">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-8">​
 +
 +                    <​h5>​Page not found</​h5>​
 +
 +                </​div>​
 +            </​div>​
 +        </​div>​
 +
 +    </​div>​
 +
 +    <​app-footer></​app-footer>​
 +
 +</​div>​
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​not-found/​not-found.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​not-found/​not-found.component.ts>​
 +import { Component, OnInit } from '​@angular/​core';​
 +
 +import { AppHeaderComponent } from '​../​app-header/​app-header.component'​
 +import { AppFooterComponent } from '​../​app-footer/​app-footer.component'​
 +
 +
 +@Component({
 +  selector: '​not-found',​
 +  templateUrl:​ '​./​not-found.component.html',​
 +  styleUrls: ['​./​not-found.component.scss'​]
 +})
 +export class NotFoundComponent implements OnInit {
 +
 +  ngOnInit() {
 +  }
 +
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​user-create/​user-create.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​user-create/​user-create.component.html>​
 +<div *ngIf="​show">​
 +
 +    <form accept-charset="​UTF-8"​ class="​callout"​ [formGroup]="​userForm"​ (ngSubmit)="​create(userForm)" ​ >
 +
 +        <h5 class="​text-center">​Do create user?</​h5>​
 +
 +        <​label>​Login
 +            <input type="​text"​ formControlName="​name"​ />
 +        </​label>​
 +
 +        <​label>​Password
 +            <input type="​text"​ formControlName="​password"​ />
 +        </​label>​
 +
 +        <​label>​Name
 +            <input type="​text"​ formControlName="​gecos"​ />
 +        </​label>​
 +
 +        <​fieldset class="​cell margin-bottom-1 text-center">​
 +            <input id="​checkbox-user-as"​ type="​checkbox"​ formControlName="​superuser">​
 +            <label for="​checkbox-user-as">​as Superuser</​label>​
 +        </​fieldset>​
 +
 +        <div class="​text-center">​
 +            <button class="​button small margin-left-1 margin-right-1"​ type="​submit">​Accept</​button>​
 +            <div class="​button small margin-left-1 margin-right-1"​ (click)="​escape()">​Escape</​div>​
 +        </​div>​
 +    </​form>​
 +
 +</​div></​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​user-create/​user-create.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​user-create/​user-create.component.ts>​
 +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '​@angular/​core'​
 +import { FormGroup, FormControl,​ FormBuilder,​ Validators } from '​@angular/​forms'​
 +import { state, query, useAnimation,​ transition, style, trigger, animate, animateChild } from '​@angular/​animations';​
 +
 +import { RPCService, RPCResponce,​ RPCError } from '​../​rpc.service'​
 +import { UsersService } from '​../​users.service'​
 +import { User } from '​../​models/​user.model'​
 +
 +
 +@Component({
 +    selector: '​user-create',​
 +    templateUrl:​ '​./​user-create.component.html',​
 +    styleUrls: ['​./​user-create.component.scss'​],​
 +    animations: [
 +    ]
 +})
 +export class UserCreateComponent implements OnInit {
 +
 +    userForm: FormGroup
 +    user: User
 +    alertMessage:​ string = ''​
 +    message: string = ''​
 +
 +
 +    @Input() show: boolean = false
 +    @Output() escapeEvent = new EventEmitter<​boolean>​();​
 +    @Output() successEvent = new EventEmitter<​boolean>​();​
 +
 +    constructor(
 +        private formBuilder:​ FormBuilder,​
 +        private usersService:​ UsersService
 +    ) {}
 +
 +    create(item) {
 +        this.user = item.value
 +                this.usersService
 +                    .create(this.user)
 +                    .subscribe((res:​ RPCResponce<​any>​) => {
 +                        if (res.result.rowCount > 0) {
 +                            this.show = false
 +                            this.successEvent.emit(true)
 +                        } else {
 +                            this.show = false
 +                            this.successEvent.emit(false)
 +                        }
 +                    })
 +    }
 +
 +    escape() {
 +        this.show = false
 +        this.escapeEvent.emit(true)
 +    }
 +
 +    ngOnInit() {
 +        this.userForm = this.formBuilder.group({
 +            name: [ ''​ ],
 +            password: [ ''​ ],
 +            gecos: [ ''​ ],
 +            superuser: [ false ]
 +        })
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​user-drop/​user-drop.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​user-drop/​user-drop.component.html>​
 +<div *ngIf="​show">​
 +
 +    <form accept-charset="​UTF-8"​ class="​callout"​ [formGroup]="​userForm"​ (ngSubmit)="​update(userForm)" ​ >
 +
 +        <h5 class="​text-center">​Do drop user {{ user.name }}?</​h5>​
 +
 +        <div class="​text-center">​
 +            <button class="​button small margin-left-1 margin-right-1"​ type="​submit">​Accept</​button>​
 +            <div class="​button small margin-left-1 margin-right-1"​ (click)="​escape()">​Escape</​div>​
 +        </​div>​
 +    </​form>​
 +
 +</​div>​
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​user-drop/​user-drop.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​user-drop/​user-drop.component.ts>​
 +import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges,​ Input, Output, EventEmitter } from '​@angular/​core'​
 +import { FormGroup, FormControl,​ FormBuilder,​ Validators } from '​@angular/​forms'​
 +
 +import { RPCService, RPCResponce,​ RPCError } from '​../​rpc.service'​
 +import { UsersService } from '​../​users.service'​
 +import { User } from '​../​models/​user.model'​
 +
 +@Component({
 +  selector: '​user-drop',​
 +  templateUrl:​ '​./​user-drop.component.html',​
 +  styleUrls: ['​./​user-drop.component.scss'​]
 +})
 +export class UserDropComponent implements OnInit {
 +
 +    userForm: FormGroup
 +    alertMessage:​ string = ''​
 +    message: string = ''​
 +
 +    @Input() show: boolean = false
 +    @Input() user: User = {
 +        id: -1,
 +        name: '',​
 +        password: '',​
 +        gecos: ''​
 +    }
 +
 +    @Output() escapeEvent = new EventEmitter<​boolean>​();​
 +    @Output() successEvent = new EventEmitter<​boolean>​();​
 +
 +    constructor(
 +        private formBuilder:​ FormBuilder,​
 +        private usersService:​ UsersService
 +
 +    ) {}
 +
 +    create(item) {
 +        this.user = item.value
 +    }
 +
 +    escape() {
 +        this.show = false
 +        this.escapeEvent.emit(true)
 +    }
 +
 +    update(item) {
 +        this.user = item.value
 +                this.usersService
 +                    .drop(this.user)
 +                    .subscribe((res:​ RPCResponce<​any>​) => {
 +                        if (res.result.rowCount > 0) {
 +                            this.show = false
 +                            this.successEvent.emit(true)
 +                        } else {
 +                            this.show = false
 +                            this.successEvent.emit(false)
 +                        }
 +                    })
 +    }
 +
 +    ngOnChanges(changes:​ SimpleChanges) {
 +        if (changes['​user'​]) {
 +            this.userForm = this.formBuilder.group({
 +                id: [ this.user.id ],
 +            })
 +        }
 +    }
 +
 +    ngOnInit() {
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​user-update/​user-update.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​user-update/​user-update.component.html>​
 +<div *ngIf="​show">​
 +
 +    <form accept-charset="​UTF-8"​ class="​callout"​ [formGroup]="​userForm"​ (ngSubmit)="​update(userForm)" ​ >
 +
 +        <h5 class="​text-center">​Do update user?</​h5>​
 +
 +        <​label>​Login
 +            <input type="​text"​ formControlName="​name"​ />
 +        </​label>​
 +
 +        <​label>​Password
 +            <input type="​text"​ formControlName="​password"​ />
 +        </​label>​
 +
 +        <​label>​Name
 +            <input type="​text"​ formControlName="​gecos"​ />
 +        </​label>​
 +
 +        <​fieldset class="​cell margin-bottom-1 text-center">​
 +            <input id="​checkbox-user-as"​ type="​checkbox"​ formControlName="​superuser">​
 +            <label for="​checkbox-user-as">​as Superuser</​label>​
 +        </​fieldset>​
 +
 +        <div class="​text-center">​
 +            <button class="​button small margin-left-1 margin-right-1"​ type="​submit">​Accept</​button>​
 +            <div class="​button small margin-left-1 margin-right-1"​ (click)="​escape()">​Escape</​div>​
 +        </​div>​
 +    </​form>​
 +
 +</​div>​
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​user-update/​user-update.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​user-update/​user-update.component.ts>​
 +import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges,​ Input, Output, EventEmitter } from '​@angular/​core'​
 +import { FormGroup, FormControl,​ FormBuilder,​ Validators } from '​@angular/​forms'​
 +
 +import { RPCService, RPCResponce,​ RPCError } from '​../​rpc.service'​
 +import { UsersService } from '​../​users.service'​
 +import { User } from '​../​models/​user.model'​
 +
 +@Component({
 +  selector: '​user-update',​
 +  templateUrl:​ '​./​user-update.component.html',​
 +  styleUrls: ['​./​user-update.component.scss'​]
 +})
 +export class UserUpdateComponent implements OnInit {
 +
 +    userForm: FormGroup
 +    alertMessage:​ string = ''​
 +    message: string = ''​
 +
 +    @Input() show: boolean = false
 +    @Input() user: User = {
 +        id: -1,
 +        name: '',​
 +        password: '',​
 +        gecos: ''​
 +    }
 +
 +    @Output() escapeEvent = new EventEmitter<​boolean>​();​
 +    @Output() successEvent = new EventEmitter<​boolean>​();​
 +
 +    constructor(
 +        private formBuilder:​ FormBuilder,​
 +        private usersService:​ UsersService
 +
 +    ) {}
 +
 +    create(item) {
 +        this.user = item.value
 +    }
 +
 +    escape() {
 +        this.show = false
 +        this.escapeEvent.emit(true)
 +    }
 +
 +    update(item) {
 +        this.user = item.value
 +                this.usersService
 +                    .update(this.user)
 +                    .subscribe((res:​ RPCResponce<​any>​) => {
 +                        if (res.result.rowCount > 0) {
 +                            this.show = false
 +                            this.successEvent.emit(true)
 +                        } else {
 +                            this.show = false
 +                            this.successEvent.emit(false)
 +                        }
 +                    })
 +    }
 +
 +    ngOnChanges(changes:​ SimpleChanges) {
 +        if (changes['​user'​]) {
 +            this.userForm = this.formBuilder.group({
 +                id: [ this.user.id ],
 +                name: [ this.user.name ],
 +                password: [ this.user.password ],
 +                gecos: [ this.user.gecos ],
 +                superuser: [ this.user.superuser ]
 +            })
 +        }
 +    }
 +
 +    ngOnInit() {
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​users/​users.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​users/​users.component.html>​
 +<div id="​users">​
 +
 +    <​app-header></​app-header>​
 +
 +    <div class="​margin-left-2 margin-right-2">​
 +
 +        <div id="​lorem"​ class="​grid-container">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-4">​
 +
 +                    <​user-create [show]="​showCreateForm"​ (escapeEvent)="​escapeForm()" ​ (successEvent)="​successForm($event)"></​user-create>​
 +
 +                </​div>​
 +            </​div>​
 +        </​div>​
 +
 +        <div class="​grid-container">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-4">​
 +
 +                    <​user-update [user]="​currentUser"​ [show]="​showUpdateForm"​ (escapeEvent)="​escapeForm()"​ (successEvent)="​successForm($event)"></​user-update>​
 +
 +                </​div>​
 +            </​div>​
 +        </​div>​
 +
 +
 +        <div class="​grid-container">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-4">​
 +
 +                    <​user-drop [user]="​currentUser"​ [show]="​showDropForm"​ (escapeEvent)="​escapeForm()"​ (successEvent)="​successForm($event)"></​user-drop>​
 +
 +                </​div>​
 +            </​div>​
 +        </​div>​
 +
 +
 +        <div class="​grid-container"​ *ngIf="​showListRecords">​
 +            <div class="​grid-x grid-margin-x align-center">​
 +                <div class="​cell medium-8">​
 +
 +                    <​h5>​Users <a (click)="​getList()"><​i class="​fi-refresh"​ style="​font-size:​ 1.3em;"></​i></​a>​
 +                        <button class="​float-right small button"​ (click)="​showCreate()"><​i class="​fi-plus"​ style="​font-size:​ 1.3em;"></​i></​button>​
 +                    </h5>
 +
 +                    <​table>​
 +                        <​thead>​
 +                            <tr>
 +                                <​th>#</​th>​
 +                                <​th>​name</​th>​
 +                                <​th>​login</​th>​
 +                                <​th>​right</​th>​
 +                                <​th><​i class="​fi-pencil"></​i></​th>​
 +                                <​th><​i class="​fi-trash"></​i></​th>​
 +                            </tr>
 +                        </​thead>​
 +                        <​tbody>​
 +                            <tr *ngFor="​let item of list; let i = index">​
 +                                <​td>​{{ i + 1 }}</​td>​
 +                                <​td>​{{ item.gecos ​ }}</​td>​
 +                                <​td>​{{ item.name ​ }}</​td>​
 +                                <​td><​span *ngIf="​item.superuser"><​i class="​fi-sheriff-badge"​ style="​font-size:​ 1.2em;"></​i></​span></​td>​
 +                                <​td><​a (click)="​updateItem(item)"><​i class="​fi-pencil"></​i></​a></​td>​
 +                                <​td><​a (click)="​dropItem(item)"><​i class="​fi-trash"></​i></​a></​td>​
 +                            </tr>
 +                        </​tbody>​
 +                    </​table>​
 +
 +                </​div>​
 +            </​div>​
 +        </​div>​
 +
 +    </​div>​
 +
 +    <​app-footer></​app-footer>​
 +
 +</​div></​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​users/​users.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​users/​users.component.ts>​
 +import { Component, OnInit, OnDestroy } from '​@angular/​core';​
 +import { Router } from '​@angular/​router'​
 +
 +import { AppHeaderComponent } from '​../​app-header/​app-header.component'​
 +import { AppFooterComponent } from '​../​app-footer/​app-footer.component'​
 +
 +import { UserCreateComponent } from '​../​user-create/​user-create.component'​
 +import { UserUpdateComponent } from '​../​user-update/​user-update.component'​
 +import { UserDropComponent } from '​../​user-drop/​user-drop.component'​
 +
 +import { RPCService, RPCResponce,​ RPCError } from '​../​rpc.service'​
 +import { UsersService } from '​../​users.service'​
 +import { User } from '​../​models/​user.model'​
 +
 +@Component({
 +    selector: '​users',​
 +    templateUrl:​ '​./​users.component.html',​
 +    styleUrls: ['​./​users.component.scss'​],​
 +})
 +export class UsersComponent implements OnInit {
 +
 +    showCreateForm:​ boolean = false
 +    showUpdateForm:​ boolean = false
 +    showDropForm:​ boolean = false
 +    showListRecords:​ boolean = true
 +
 +    list: User[] = []
 +
 +    currentUser : User = {
 +        id: -1,
 +        name: '',​
 +        password: '',​
 +        gecos: ''​
 +    }
 +
 +    constructor(
 +        private usersService:​ UsersService,​
 +    ) {}
 +
 +    dropItem(item:​ User) {
 +        this.currentUser = item
 +        this.showDrop()
 +    }
 +
 +    updateItem(item:​ User) {
 +        this.currentUser = item
 +        this.showUpdate()
 +    }
 +
 +    getList() {
 +        this.usersService
 +            .list()
 +            .subscribe((res:​ RPCResponce<​User[]>​) => {
 +                this.list = res.result
 +            })
 +    }
 +
 +    ngOnInit() {
 +        this.getList()
 +    }
 +
 +    escapeForm() {
 +        this.showList()
 +    }
 +
 +    successForm($event) {
 +        this.getList()
 +        this.showList()
 +    }
 +
 +    showCreate() {
 +        this.showCreateForm = true
 +        this.showUpdateForm = false
 +        this.showDropForm = false
 +        this.showListRecords = false
 +    }
 +
 +    showUpdate() {
 +        this.showCreateForm = false
 +        this.showUpdateForm = true
 +        this.showDropForm = false
 +        this.showListRecords = false
 +    }
 +
 +    showDrop() {
 +        this.showCreateForm = false
 +        this.showUpdateForm = false
 +        this.showDropForm = true
 +        this.showListRecords = false
 +    }
 +
 +    showList() {
 +        this.showCreateForm = false
 +        this.showUpdateForm = false
 +        this.showDropForm = false
 +        this.showListRecords = true
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​customers.service.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​customers.service.ts>​
 +import { Injectable } from '​@angular/​core'​
 +import { Observable } from '​rxjs'​
 +import { RPCService, RPCResponce,​ RPCError } from '​./​rpc.service'​
 +
 +import { Customer } from '​./​models/​customer.model'​
 +
 +@Injectable({
 +  providedIn: '​root'​
 +})
 +export class CustomersService {
 +
 +    constructor(
 +        private rpcService: RPCService
 +    ) {}
 +
 +    find(customer:​ Customer) {
 +        return this.rpcService
 +            .request<​Customer,​ Customer[]>​('/​api/​customers',​ '​find',​ customer)
 +    }
 +
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app.component.html===
 +
 +<code html expresso/​frontend/​src/​app/​app.component.html>​
 +<div id="​app">​
 +    <​router-outlet></​router-outlet>​
 +</​div>​
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app.component.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​app.component.ts>​
 +import { Component, OnInit, OnDestroy } from '​@angular/​core'​
 +
 +declare var $ : any
 +
 +@Component({
 +    selector: '​app',​
 +    templateUrl:​ '​./​app.component.html',​
 +    styleUrls: ['​./​app.component.scss'​]
 +})
 +export class AppComponent implements OnInit {
 +    ngOnInit() {
 +        $(document).foundation()
 +    }
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​app.module.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​app.module.ts>​
 +
 +import { NgModule } from '​@angular/​core'​
 +import { LocationStrategy,​ HashLocationStrategy } from '​@angular/​common'​
 +import { FormsModule } from '​@angular/​forms'​
 +import { ReactiveFormsModule } from '​@angular/​forms'​
 +import { BrowserModule } from '​@angular/​platform-browser'​
 +import { BrowserAnimationsModule } from '​@angular/​platform-browser/​animations'​
 +import { HttpClientModule } from '​@angular/​common/​http'​
 +
 +import { RoutingModule } from '​./​routing.module'​
 +
 +import { AppComponent } from '​./​app.component'​
 +
 +import { AppHeaderComponent } from '​./​app-header/​app-header.component'​
 +import { AppFooterComponent } from '​./​app-footer/​app-footer.component'​
 +
 +import { HomeComponent } from '​./​home/​home.component'​
 +import { CustomersComponent } from '​./​customers/​customers.component'​
 +
 +import { LoginComponent } from '​./​login/​login.component'​
 +import { NotFoundComponent } from '​./​not-found/​not-found.component'​
 +
 +import { RPCService } from '​./​rpc.service'​
 +import { LoginService } from '​./​login.service'​
 +import { UsersService } from '​./​users.service'​
 +import { CustomersService } from '​./​customers.service'​
 +
 +import { LoginGuard } from '​./​guards/​login.guard'​
 +import { SuperloginGuard } from '​./​guards/​superlogin.guard'​
 +
 +import { UsersComponent } from '​./​users/​users.component'​
 +import { UserCreateComponent } from '​./​user-create/​user-create.component'​
 +import { UserUpdateComponent } from '​./​user-update/​user-update.component'​
 +import { UserDropComponent } from '​./​user-drop/​user-drop.component'​
 +
 +@NgModule({
 +    declarations:​ [
 +        AppHeaderComponent,​
 +        AppFooterComponent,​
 +        AppComponent,​
 +        HomeComponent,​
 +        CustomersComponent,​
 +        UsersComponent,​
 +        LoginComponent,​
 +        NotFoundComponent,​
 +        UserCreateComponent,​
 +        UserUpdateComponent,​
 +        UserDropComponent
 +    ],
 +    imports: [
 +        ReactiveFormsModule,​
 +        FormsModule,​
 +        BrowserModule,​
 +        BrowserAnimationsModule,​
 +        RoutingModule,​
 +        HttpClientModule
 +    ],
 +    providers: [
 +        {provide: LocationStrategy,​ useClass: HashLocationStrategy},​
 +        LoginService,​
 +        RPCService,
 +        UsersService,​
 +        CustomersService,​
 +        LoginGuard,
 +        SuperloginGuard
 +    ],
 +    bootstrap: [ AppComponent ]
 +})
 +export class AppModule {
 +}
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​login.service.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​login.service.ts>​
 +import { Injectable, OnInit } from '​@angular/​core'​
 +import { Router, ActivatedRoute } from '​@angular/​router'​
 +import * as Cookies from '​es-cookie'​
 +
 +import { RPCService } from '​./​rpc.service'​
 +
 +import { User } from '​./​models/​user.model'​
 +
 +export interface UserLogin {
 +    name: string
 +    password: string
 +}
 +
 +//export enum AccessLevel { Superuser = '​superuser',​ User = '​user'​ }
 +
 +@Injectable({
 +  providedIn: '​root'​
 +})
 +export class LoginService {
 +
 +    cookieName = '​session'​
 +    returnUrl: string = '/'​
 +
 +    loginState: boolean = false
 +    user: User = { 
 +        id: -1,
 +        name: '',​
 +        superuser: false,
 +        gecos: ''​
 +    }
 +
 +    constructor(
 +        private route: ActivatedRoute,​
 +        private router: Router,
 +        private rpc: RPCService
 +    ) {}
 +
 +
 +    isLogin() : boolean {
 +        if (Cookies.get(this.cookieName)) {
 +            return true
 +        }
 +        return false
 +    }
 +
 +    isUser() : boolean {
 +        return this.isLogin()
 +    }
 +
 +    isSuperuser() : boolean {
 +        if (this.isLogin() && this.user.superuser) {
 +            return true
 +        }
 +        return false
 +    }
 +
 +    login(name: string, password: string) : boolean {
 +        let params : User = {
 +            name: name,
 +            password: password,
 +            id: -1
 +        }
 +
 +        this.rpc.request<​User,​ User[]>​('/​api/​login',​ '​login',​ params)
 +            .subscribe((res) => {
 +                if (res.result[0].id >= 0) {
 +
 +                    this.user.id = res.result[0].id
 +                    this.user.superuser = res.result[0].superuser
 +                    this.user.gecos = res.result[0].gecos
 +                    this.router.navigate([ this.returnUrl ])
 +                }
 +            })
 +        if (this.user.id >= 0) return true
 +        return false
 +    }
 +
 +    logout() {
 +        Cookies.remove(this.cookieName)
 +        this.router.navigate(['/'​])
 +    }
 +
 +    ngOnInit() {
 +    }
 +
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​routing.module.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​routing.module.ts>​
 +import { NgModule } from '​@angular/​core'​
 +import { Routes, RouterModule } from '​@angular/​router'​
 +
 +import { HomeComponent } from '​./​home/​home.component'​
 +import { LoginComponent } from '​./​login/​login.component'​
 +import { CustomersComponent } from '​./​customers/​customers.component'​
 +import { UsersComponent } from '​./​users/​users.component'​
 +import { NotFoundComponent } from '​./​not-found/​not-found.component'​
 +
 +import { LoginGuard } from '​./​guards/​login.guard'​
 +import { SuperloginGuard } from '​./​guards/​superlogin.guard'​
 +
 +const routes: Routes = [
 +    {
 +        path: '​login',​
 +        component: LoginComponent
 +    },
 +    {
 +        path: '​customers',​
 +        component: CustomersComponent,​
 +        canActivate:​ [ LoginGuard ]
 +    },
 +    {
 +        path: '​customers/:​phone',​
 +        component: CustomersComponent,​
 +        canActivate:​ [ LoginGuard ]
 +    },
 +    {
 +        path: '​users',​
 +        component: UsersComponent,​
 +        canActivate:​ [ SuperloginGuard ]
 +    },
 +    {
 +        path: '',​
 +        //​redirectTo:​ '​customers',​
 +        //​pathMatch:​ '​full'​
 +        component: HomeComponent,​
 +        //​canActivate:​ [ LoginGuard ]
 +    },
 +    {
 +        path: '​**', ​
 +        component: NotFoundComponent,​
 +        //​canActivate:​ [ LoginGuard ]
 +    }
 +]
 +
 +@NgModule({
 +    imports: [ RouterModule.forRoot(routes) ],
 +    exports: [ RouterModule ]
 +})
 +export class RoutingModule {}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​rpc.service.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​rpc.service.ts>​
 +import { Injectable } from '​@angular/​core'​
 +import { HttpClient } from '​@angular/​common/​http'​
 +import { Observable } from '​rxjs'​
 +
 +import { v4 as uuid } from '​uuid'​
 +
 +export interface RPCRequest<​TParam>​ {
 +    jsonrpc: string
 +    method: string
 +    params: TParam
 +    id: string
 +}
 +
 +export interface RPCError {
 +    code?: number
 +    message?: string
 +}
 +
 +export interface RPCResponce<​TResult>​ {
 +    jsonrpc: string
 +    error?: RPCError
 +    result?: TResult
 +    id: string
 +}
 +
 +@Injectable({
 +  providedIn: '​root'​
 +})
 +export class RPCService {
 +
 +    constructor(
 +        private httpClient: HttpClient
 +    ) {}
 +
 +    request<​TParam,​ TResult>​(url:​ string, method: string, params: TParam) : Observable<​RPCResponce<​TResult>>​ {
 +
 +        let rpcRequest : RPCRequest<​TParam>​ = {
 +            jsonrpc: '​2.0',​
 +            method: method,
 +            params: params,
 +            id: uuid()
 +        }
 +        return this.httpClient.post<​RPCResponce<​TResult>>​(url,​ rpcRequest)
 +    }
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​app/​users.service.ts===
 +
 +<code javascript expresso/​frontend/​src/​app/​users.service.ts>​
 +import { Injectable } from '​@angular/​core'​
 +import { Observable } from '​rxjs'​
 +import { RPCService, RPCResponce,​ RPCError } from '​./​rpc.service'​
 +
 +import { User } from '​./​models/​user.model'​
 +
 +@Injectable({
 +  providedIn: '​root'​
 +})
 +export class UsersService {
 +
 +    constructor(
 +        private rpcService: RPCService
 +    ) {}
 +
 +    list() {
 +        return this.rpcService
 +            .request<​null,​ User[]>​('/​api/​users',​ '​list',​ null)
 +    }
 +
 +    create(user:​ User) {
 +        return this.rpcService
 +            .request<​User,​ number>​('/​api/​users',​ '​create',​ user)
 +    }
 +
 +    update(user:​ User) {
 +        return this.rpcService
 +            .request<​User,​ number>​('/​api/​users',​ '​update',​ user)
 +    }
 +
 +    drop(user: User) {
 +        return this.rpcService
 +            .request<​User,​ number>​('/​api/​users',​ '​drop',​ user)
 +    }
 +
 +}
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​environments/​environment.prod.ts===
 +
 +<code javascript expresso/​frontend/​src/​environments/​environment.prod.ts>​
 +
 +export const environment = {
 +  production: true
 +};
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​environments/​environment.ts===
 +
 +<code javascript expresso/​frontend/​src/​environments/​environment.ts>​
 +export const environment = {
 +  production: false
 +};
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​polyfills.ts===
 +
 +<code javascript expresso/​frontend/​src/​polyfills.ts>​
 +import '​zone.js/​dist/​zone';​
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​index.html===
 +
 +<code html expresso/​frontend/​src/​index.html>​
 +<​!doctype html>
 +<html class="​no-js"​ lang="​en"​ dir="​ltr">​
 +
 +<​head>​
 +    <meta charset="​utf-8">​
 +    <meta http-equiv="​x-ua-compatible"​ content="​ie=edge">​
 +    <meta name="​viewport"​ content="​width=device-width,​ initial-scale=1.0">​
 +
 +    <link rel="​stylesheet"​ href="/​foundation-icons.css">​
 +    <​title>​NgII</​title>​
 +    <base href="/">​
 +
 +    <link rel="​shortcut icon" href="/​favicon.ico">​
 +</​head>​
 +
 +<body id="​body">​
 +        <​app></​app>​
 +</​body>​
 +
 +</​html></​code>​
 +
 +
 +===expresso/​frontend/​src/​main.ts===
 +
 +<code javascript expresso/​frontend/​src/​main.ts>​
 +
 +import { enableProdMode } from '​@angular/​core'​
 +import { platformBrowserDynamic } from '​@angular/​platform-browser-dynamic'​
 +
 +import { AppModule } from '​./​app/​app.module'​
 +
 +import { environment } from '​./​environments/​environment'​
 +
 +if (environment.production) {
 +  enableProdMode()
 +}
 +
 +platformBrowserDynamic()
 +    .bootstrapModule(AppModule)
 +    .catch(err => console.error(err))
 +
 +</​code>​
 +
 +
 +===expresso/​frontend/​src/​typings.d.ts===
 +
 +<code javascript expresso/​frontend/​src/​typings.d.ts>​
 +
 +declare var module: NodeModule;
 +
 +interface NodeModule {
 +    id: string;
 +}</​code>​
 +