Node API attempt #1

Merged
danieljsummers merged 4 commits from node-api-attempt into master 2017-09-21 23:47:59 +00:00
12 changed files with 1179 additions and 293 deletions
Showing only changes of commit 794a365f08 - Show all commits

22
src/api/app.js Normal file
View File

@ -0,0 +1,22 @@
'use strict'
const chalk = require('chalk')
const env = process.env.NODE_ENV || 'dev'
if ('dev' === env) require('babel-register')
const fullEnv = ('dev' === env) ? 'Development' : 'Production'
/** Configuration for the application */
const appConfig = require('./appsettings.json')
/** Express app */
const app = require('./index').default
// Ensure the database exists...
require('./db').default.verify().then(() =>
// ...and start it up!
app.listen(appConfig.port, () => {
console.log(chalk`{reset myPrayerJournal | Port: {bold ${appConfig.port}} | Mode: {bold ${fullEnv}}}`)
}))

View File

@ -1,6 +1,6 @@
'use strict'
const { Pool } = require('pg')
import { Pool } from 'pg'
/**
* SQL to check the existence of a table in the mpj schema
@ -14,7 +14,7 @@ const tableSql = table => `SELECT 1 FROM pg_tables WHERE schemaname='mpj' AND ta
* @param {string} index The name of the index
*/
const indexSql = (table, index) =>
`SELECT 1 FROM pg_indexes WHERE schemaname='mpj' AND tablename='${table}' AND indexname='${index}'`
`SELECT 1 FROM pg_indexes WHERE schemaname='mpj' AND tablename='${table}' AND indexname='${index}'`
const ddl = [
{
@ -55,7 +55,7 @@ const ddl = [
}
]
module.exports = query => {
export default function (query) {
return {
/**
* Ensure that the database schema, tables, and indexes exist

View File

@ -1,9 +1,13 @@
'use strict'
const { Pool } = require('pg')
import { Pool } from 'pg'
import appConfig from '../appsettings.json'
import ddl from './ddl'
import request from './request'
/** Pooled PostgreSQL instance */
const pool = new Pool(require('../appsettings.json').pgPool)
const pool = new Pool(appConfig.pgPool)
/**
* Run a SQL query
@ -12,8 +16,8 @@ const pool = new Pool(require('../appsettings.json').pgPool)
*/
const query = (text, params) => pool.query(text, params)
module.exports = {
export default {
query: query,
request: require('./request')(query),
verify: require('./ddl')(query).ensureDatabase
request: request(query),
verify: ddl(query).ensureDatabase
}

View File

@ -1,8 +1,9 @@
'use strict'
const { Pool } = require('pg')
import { Pool } from 'pg'
import cuid from 'cuid'
module.exports = query => {
export default function (query) {
return {
/**
* Get the current requests for a user (i.e., their complete current journal)
@ -10,6 +11,24 @@ module.exports = query => {
* @return The requests that make up the current journal
*/
journal: async userId =>
(await query('SELECT "requestId" FROM request WHERE "userId" = $1', [userId])).rows
(await query('SELECT "requestId" FROM request WHERE "userId" = $1', [userId])).rows,
/**
* Add a new prayer request
* @param {string} userId The Id of the user
* @param {string} requestText The text of the request
* @return {string} The Id of the created request
*/
addNew: async (userId, requestText) => {
const id = cuid()
const enteredOn = Date.now()
await query(`
BEGIN;
INSERT INTO request ("requestId", "enteredOn", "userId") VALUES ($1, $2, $3);
INSERT INTO history ("requestId", "asOf", "status", "text") VALUES ($1, $2, 'Created', $4);
COMMIT;`,
[ id, enteredOn, userId, requestText ])
return id
}
}
}

View File

@ -1,35 +1,33 @@
'use strict'
const express = require('express')
import Koa from 'koa'
import morgan from 'koa-morgan'
import send from 'koa-send'
import serveFrom from 'koa-static'
/** Configuration for the application */
const appConfig = require('./appsettings.json')
import appConfig from './appsettings.json'
import router from './routes'
/** Express app */
const app = express()
/** Koa app */
const app = new Koa()
// Serve the Vue files from /public
app.use(express.static('public'))
// Logging FTW!
app.use(require('morgan')('dev'))
// Tie in all the API routes
require('./routes').mount(app)
// Send the index.html file for what would normally get a 404
app.use(async (req, res, next) => {
try {
await res.sendFile('index.html', { root: __dirname + '/public/', dotfiles: 'deny' })
}
catch (err) {
return next(err)
}
})
// Ensure the database exists...
require('./db').verify().then(() =>
// ...and start it up!
app.listen(appConfig.port, () => {
console.log(`Listening on port ${appConfig.port}`)
}))
export default app
// Logging FTW!
.use(morgan('dev'))
// Serve the Vue files from /public
.use(serveFrom('public'))
// Tie in all the routes
.use(router.routes())
.use(router.allowedMethods())
// Send the index.html file for what would normally get a 404
.use(async (ctx, next) => {
if (ctx.url.indexOf('/api') === -1) {
try {
await send(ctx, 'index.html', { root: __dirname + '/public/' })
}
catch (err) {
return await next(err)
}
}
return await next()
})

View File

@ -7,16 +7,37 @@
"author": "Daniel J. Summers <daniel@djs-consulting.com>",
"license": "MIT",
"dependencies": {
"chalk": "^2.1.0",
"cuid": "^1.3.8",
"express": "^4.15.4",
"express-jwt": "^5.3.0",
"express-promise-router": "^2.0.0",
"jwks-rsa": "^1.2.0",
"morgan": "^1.8.2",
"jwks-rsa-koa": "^1.1.3",
"koa": "^2.3.0",
"koa-bodyparser": "^4.2.0",
"koa-jwt": "^3.2.2",
"koa-morgan": "^1.0.1",
"koa-router": "^7.2.1",
"koa-send": "^4.1.0",
"koa-static": "^4.0.1",
"pg": "^7.3.0"
},
"scripts": {
"start": "node index.js",
"vue": "cd ../app && node build/build.js prod && cd ../api && node index.js"
"start": "node app.js",
"vue": "cd ../app && node build/build.js prod && cd ../api && node app.js"
},
"devDependencies": {
"babel": "^6.23.0",
"babel-preset-env": "^1.6.0",
"babel-register": "^6.26.0"
},
"babel": {
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
]
}
}

View File

@ -1,31 +1,39 @@
'use strict'
const jwt = require('express-jwt')
const jwksRsa = require('jwks-rsa')
const config = require('../appsettings.json')
import jwt from 'koa-jwt'
import jwksRsa from 'jwks-rsa-koa'
import Router from 'koa-router'
// Authentication middleware. When used, the
// access token must exist and be verified against
// the Auth0 JSON Web Key Set
import appConfig from '../appsettings.json'
import journal from './journal'
import request from './request'
/** Authentication middleware to verify the access token against the Auth0 JSON Web Key Set */
const checkJwt = jwt({
// Dynamically provide a signing key
// based on the kid in the header and
// the singing keys provided by the JWKS endpoint.
secret: jwksRsa.expressJwtSecret({
secret: jwksRsa.koaJwt2Key({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${config.auth0.domain}/.well-known/jwks.json`
jwksUri: `https://${appConfig.auth0.domain}/.well-known/jwks.json`
}),
// Validate the audience and the issuer.
audience: config.auth0.clientId,
issuer: `https://${config.auth0.domain}/`,
audience: appConfig.auth0.clientId,
issuer: `https://${appConfig.auth0.domain}/`,
algorithms: ['RS256']
})
module.exports = {
mount: app => {
app.use('/api/journal', require('./journal')(checkJwt))
}
}
/** /api/journal routes */
const journalRoutes = journal(checkJwt)
/** /api/request routes */
const requestRoutes = request(checkJwt)
/** Combined router */
const router = new Router({ prefix: '/api' })
router.use('/journal', journalRoutes.routes(), journalRoutes.allowedMethods())
router.use('/request', requestRoutes.routes(), requestRoutes.allowedMethods())
export default router

View File

@ -1,15 +1,16 @@
'use strict'
const Router = require('express-promise-router')
const db = require('../db')
import Router from 'koa-router'
import db from '../db'
module.exports = checkJwt => {
let router = new Router()
const router = new Router()
router.get('/', checkJwt, async (req, res) => {
const reqs = await db.request.journal(req.user.sub)
res.json(reqs)
export default function (checkJwt) {
router.get('/', checkJwt, async (ctx, next) => {
const reqs = await db.request.journal(ctx.state.user.sub)
ctx.body = reqs
return await next()
})
return router
}

18
src/api/routes/request.js Normal file
View File

@ -0,0 +1,18 @@
'use strict'
import Router from 'koa-router'
import db from '../db'
const router = new Router()
export default function (checkJwt) {
router.post('/', checkJwt, async (ctx, next) => {
const newId = await db.request.addNew(ctx.state.user.sub, ctx.body.requestText)
ctx.body = { id: newId }
await next()
})
return router
}

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,16 @@
<template lang="pug">
article
page-title(:title="title")
p here you are!
p(v-if="isLoadingJournal") journal is loading...
p(v-if="!isLoadingJournal") journal has {{ journal.length }} entries
template(v-if="!isLoadingJournal")
new-request
p journal has {{ journal.length }} entries
</template>
<script>
import { mapState } from 'vuex'
import PageTitle from './PageTitle'
import NewRequest from './request/NewRequest'
import * as actions from '@/store/action-types'
@ -19,7 +21,8 @@ export default {
return {}
},
components: {
PageTitle
PageTitle,
NewRequest
},
computed: {
title () {

View File

@ -0,0 +1,26 @@
<template lang="pug">
div
el-button(@click='showNewVisible = true') Add a New Request
el-dialog(title='Add a New Prayer Request' :visible.sync='showNewVisible')
el-form(:model='form' :label-position='top')
el-form-item(label='Prayer Request')
el-input(type='textarea' v-model.trim='form.requestText' :rows='10')
span.dialog-footer(slot='footer')
el-button(@click='showNewVisible = false') Cancel
el-button(type='primary' @click='showNewVisible = false') Confirm
</template>
<script>
export default {
name: 'new-request',
data () {
return {
showNewVisible: false,
form: {
requestText: ''
},
formLabelWidth: '120px'
}
}
}
</script>