Node API attempt #1
22
src/api/app.js
Normal file
22
src/api/app.js
Normal 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}}}`)
|
||||||
|
}))
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const { Pool } = require('pg')
|
import { Pool } from 'pg'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL to check the existence of a table in the mpj schema
|
* SQL to check the existence of a table in the mpj schema
|
||||||
|
@ -55,7 +55,7 @@ const ddl = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = query => {
|
export default function (query) {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Ensure that the database schema, tables, and indexes exist
|
* Ensure that the database schema, tables, and indexes exist
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
'use strict'
|
'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 */
|
/** Pooled PostgreSQL instance */
|
||||||
const pool = new Pool(require('../appsettings.json').pgPool)
|
const pool = new Pool(appConfig.pgPool)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a SQL query
|
* Run a SQL query
|
||||||
|
@ -12,8 +16,8 @@ const pool = new Pool(require('../appsettings.json').pgPool)
|
||||||
*/
|
*/
|
||||||
const query = (text, params) => pool.query(text, params)
|
const query = (text, params) => pool.query(text, params)
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
query: query,
|
query: query,
|
||||||
request: require('./request')(query),
|
request: request(query),
|
||||||
verify: require('./ddl')(query).ensureDatabase
|
verify: ddl(query).ensureDatabase
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const { Pool } = require('pg')
|
import { Pool } from 'pg'
|
||||||
|
import cuid from 'cuid'
|
||||||
|
|
||||||
module.exports = query => {
|
export default function (query) {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Get the current requests for a user (i.e., their complete current journal)
|
* 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
|
* @return The requests that make up the current journal
|
||||||
*/
|
*/
|
||||||
journal: async userId =>
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,33 @@
|
||||||
'use strict'
|
'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 */
|
import appConfig from './appsettings.json'
|
||||||
const appConfig = require('./appsettings.json')
|
import router from './routes'
|
||||||
|
|
||||||
/** Express app */
|
/** Koa app */
|
||||||
const app = express()
|
const app = new Koa()
|
||||||
|
|
||||||
// Serve the Vue files from /public
|
|
||||||
app.use(express.static('public'))
|
|
||||||
|
|
||||||
|
export default app
|
||||||
// Logging FTW!
|
// Logging FTW!
|
||||||
app.use(require('morgan')('dev'))
|
.use(morgan('dev'))
|
||||||
|
// Serve the Vue files from /public
|
||||||
// Tie in all the API routes
|
.use(serveFrom('public'))
|
||||||
require('./routes').mount(app)
|
// Tie in all the routes
|
||||||
|
.use(router.routes())
|
||||||
|
.use(router.allowedMethods())
|
||||||
// Send the index.html file for what would normally get a 404
|
// Send the index.html file for what would normally get a 404
|
||||||
app.use(async (req, res, next) => {
|
.use(async (ctx, next) => {
|
||||||
|
if (ctx.url.indexOf('/api') === -1) {
|
||||||
try {
|
try {
|
||||||
await res.sendFile('index.html', { root: __dirname + '/public/', dotfiles: 'deny' })
|
await send(ctx, 'index.html', { root: __dirname + '/public/' })
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return next(err)
|
return await next(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return await next()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ensure the database exists...
|
|
||||||
require('./db').verify().then(() =>
|
|
||||||
// ...and start it up!
|
|
||||||
app.listen(appConfig.port, () => {
|
|
||||||
console.log(`Listening on port ${appConfig.port}`)
|
|
||||||
}))
|
|
||||||
|
|
|
@ -7,16 +7,37 @@
|
||||||
"author": "Daniel J. Summers <daniel@djs-consulting.com>",
|
"author": "Daniel J. Summers <daniel@djs-consulting.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chalk": "^2.1.0",
|
||||||
"cuid": "^1.3.8",
|
"cuid": "^1.3.8",
|
||||||
"express": "^4.15.4",
|
"jwks-rsa-koa": "^1.1.3",
|
||||||
"express-jwt": "^5.3.0",
|
"koa": "^2.3.0",
|
||||||
"express-promise-router": "^2.0.0",
|
"koa-bodyparser": "^4.2.0",
|
||||||
"jwks-rsa": "^1.2.0",
|
"koa-jwt": "^3.2.2",
|
||||||
"morgan": "^1.8.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"
|
"pg": "^7.3.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node app.js",
|
||||||
"vue": "cd ../app && node build/build.js prod && cd ../api && node index.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,39 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const jwt = require('express-jwt')
|
import jwt from 'koa-jwt'
|
||||||
const jwksRsa = require('jwks-rsa')
|
import jwksRsa from 'jwks-rsa-koa'
|
||||||
const config = require('../appsettings.json')
|
import Router from 'koa-router'
|
||||||
|
|
||||||
// Authentication middleware. When used, the
|
import appConfig from '../appsettings.json'
|
||||||
// access token must exist and be verified against
|
import journal from './journal'
|
||||||
// the Auth0 JSON Web Key Set
|
import request from './request'
|
||||||
|
|
||||||
|
/** Authentication middleware to verify the access token against the Auth0 JSON Web Key Set */
|
||||||
const checkJwt = jwt({
|
const checkJwt = jwt({
|
||||||
// Dynamically provide a signing key
|
// Dynamically provide a signing key
|
||||||
// based on the kid in the header and
|
// based on the kid in the header and
|
||||||
// the singing keys provided by the JWKS endpoint.
|
// the singing keys provided by the JWKS endpoint.
|
||||||
secret: jwksRsa.expressJwtSecret({
|
secret: jwksRsa.koaJwt2Key({
|
||||||
cache: true,
|
cache: true,
|
||||||
rateLimit: true,
|
rateLimit: true,
|
||||||
jwksRequestsPerMinute: 5,
|
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.
|
// Validate the audience and the issuer.
|
||||||
audience: config.auth0.clientId,
|
audience: appConfig.auth0.clientId,
|
||||||
issuer: `https://${config.auth0.domain}/`,
|
issuer: `https://${appConfig.auth0.domain}/`,
|
||||||
algorithms: ['RS256']
|
algorithms: ['RS256']
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = {
|
/** /api/journal routes */
|
||||||
mount: app => {
|
const journalRoutes = journal(checkJwt)
|
||||||
app.use('/api/journal', require('./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
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const Router = require('express-promise-router')
|
import Router from 'koa-router'
|
||||||
const db = require('../db')
|
import db from '../db'
|
||||||
|
|
||||||
module.exports = checkJwt => {
|
const router = new Router()
|
||||||
let router = new Router()
|
|
||||||
|
|
||||||
router.get('/', checkJwt, async (req, res) => {
|
export default function (checkJwt) {
|
||||||
const reqs = await db.request.journal(req.user.sub)
|
|
||||||
res.json(reqs)
|
router.get('/', checkJwt, async (ctx, next) => {
|
||||||
|
const reqs = await db.request.journal(ctx.state.user.sub)
|
||||||
|
ctx.body = reqs
|
||||||
|
return await next()
|
||||||
})
|
})
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/api/routes/request.js
Normal file
18
src/api/routes/request.js
Normal 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
|
||||||
|
}
|
||||||
|
|
1204
src/api/yarn.lock
1204
src/api/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -1,14 +1,16 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
article
|
article
|
||||||
page-title(:title="title")
|
page-title(:title="title")
|
||||||
p here you are!
|
|
||||||
p(v-if="isLoadingJournal") journal is loading...
|
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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import PageTitle from './PageTitle'
|
import PageTitle from './PageTitle'
|
||||||
|
import NewRequest from './request/NewRequest'
|
||||||
|
|
||||||
import * as actions from '@/store/action-types'
|
import * as actions from '@/store/action-types'
|
||||||
|
|
||||||
|
@ -19,7 +21,8 @@ export default {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
PageTitle
|
PageTitle,
|
||||||
|
NewRequest
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
title () {
|
title () {
|
||||||
|
|
26
src/app/src/components/request/NewRequest.vue
Normal file
26
src/app/src/components/request/NewRequest.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user