Wrote API calls for history updates

Also switched app pkg mgt to yarn
This commit is contained in:
Daniel J. Summers 2017-09-22 19:27:23 -05:00
parent a6b5149b34
commit c703d717d8
10 changed files with 6676 additions and 11316 deletions

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
import { Pool } from 'pg' import { Pool, types } from 'pg'
import appConfig from '../appsettings.json' import appConfig from '../appsettings.json'
import ddl from './ddl' import ddl from './ddl'
@ -9,6 +9,10 @@ import request from './request'
/** Pooled PostgreSQL instance */ /** Pooled PostgreSQL instance */
const pool = new Pool(appConfig.pgPool) const pool = new Pool(appConfig.pgPool)
// Return "bigint" (int8) instances as number instead of strings
// ref: https://github.com/brianc/node-pg-types
types.setTypeParser(20, val => parseInt(val))
/** /**
* Run a SQL query * Run a SQL query
* @param {string} text The SQL command * @param {string} text The SQL command

View File

@ -3,6 +3,32 @@
import { Pool } from 'pg' import { Pool } from 'pg'
import cuid from 'cuid' import cuid from 'cuid'
const currentRequestSql = `
SELECT
request."requestId",
(SELECT "text"
FROM mpj.history
WHERE history."requestId" = request."requestId"
AND "text" IS NOT NULL
ORDER BY "asOf" DESC
LIMIT 1) AS "text",
(SELECT "asOf"
FROM mpj.history
WHERE history."requestId" = request."requestId"
ORDER BY "asOf" DESC
LIMIT 1) AS "asOf"
FROM mpj.request`
const journalSql = `${currentRequestSql}
WHERE "userId" = $1
GROUP BY request."requestId"`
const requestNotFound = {
requestId: '',
text: 'Not Found',
asOf: 0
}
export default function (pool) { export default function (pool) {
return { return {
/** /**
@ -10,29 +36,57 @@ export default function (pool) {
* @param {string} userId The Id of the user * @param {string} userId The Id of the user
* @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 pool.query(`${journalSql} ORDER BY "asOf" DESC`, [ userId ])).rows,
(await pool.query({
name: 'journal', /**
text: ` * Get the least-recently-updated prayer request for the given user
SELECT * @param {string} userId The Id of the current user
request."requestId", * @return The least-recently-updated request for the given user
(SELECT "text" */
FROM mpj.history oldest: async userId => (await pool.query(`${journalSql} ORDER BY "asOf" LIMIT 1`, [ userId ])).rows[0],
WHERE history."requestId" = request."requestId"
AND "text" IS NOT NULL
ORDER BY "asOf" DESC
LIMIT 1) AS "text",
(SELECT "asOf"
FROM mpj.history
WHERE history."requestId" = request."requestId"
ORDER BY "asOf" DESC
LIMIT 1) AS "asOf"
FROM mpj.request
WHERE "userId" = $1
GROUP BY request."requestId"
ORDER BY "asOf" DESC`
}, [userId])).rows,
/**
* Get the "current" version of a request by its Id
* @param {string} requestId The Id of the request to retrieve
* @param {string} userId The Id of the user to which the request belongs
* @return The request, or a request-like object indicating that the request was not found
*/
byId: async (userId, requestId) => {
const reqs = await pool.query(`${currentRequestSql}
WHERE "requestId" = $1
AND "userId" = $2
GROUP BY request."requestId"`,
[ requestId, userId ])
return (0 < req.rowCount) ? reqs.rows[0] : requestNotFound
},
/**
* Get a prayer request, including its full history, by its Id
* @param {string} userId The Id of the user to which the request belongs
* @param {string} requestId The Id of the request to retrieve
* @return The request, or a request-like object indicating that the request was not found
*/
fullById: async (userId, requestId) => {
const reqResults = await pool.query(`
SELECT "requestId", "enteredOn"
FROM mpj.request
WHERE "requestId" = $1
AND "userId" = $2`,
[ requestId, userId ])
if (0 === reqResults.rowCount) {
return requestNotFound
}
const req = reqResults.rows[0]
const history = await pool.query(`
SELECT "asOf", "status", COALESCE("text", '') AS "text"
FROM mpj.history
WHERE "requestId" = $1
ORDER BY "asOf"`,
[ requestId ])
req.history = history.rows
return req
},
/** /**
* Add a new prayer request * Add a new prayer request
* @param {string} userId The Id of the user * @param {string} userId The Id of the user
@ -64,6 +118,20 @@ export default function (pool) {
console.error(e.stack) console.error(e.stack)
return { requestId: '', text: 'error', asOf: 0 } return { requestId: '', text: 'error', asOf: 0 }
}) })
},
/**
* Add a history entry for this request
* @param {string} requestId The Id of the request
* @param {string} status The status for this history entry
* @param {string} updateText The updated text for the request (pass blank if no update)
*/
addHistory: async (requestId, status, updateText) => {
const asOf = Date.now()
await pool.query(`
INSERT INTO mpj.history ("requestId", "asOf", "status", "text") VALUES ($1, $2, $3, NULLIF($4, ''))`,
[ requestId, asOf, status, updateText ])
} }
} }
} }

View File

@ -7,11 +7,45 @@ const router = new Router()
export default function (checkJwt) { export default function (checkJwt) {
router.post('/', checkJwt, async (ctx, next) => { router
ctx.body = await db.request.addNew(ctx.state.user.sub, ctx.request.body.requestText) // Add a new request
await next() .post('/', checkJwt, async (ctx, next) => {
}) ctx.body = await db.request.addNew(ctx.state.user.sub, ctx.request.body.requestText)
await next()
})
// Add a request history entry (prayed, updated, answered, etc.)
.post('/:id/history', checkJwt, async (ctx, next) => {
const body = ctx.request.body
const result = await db.request.addHistory(ctx.params.id, body.status, body.updateText)
ctx.status(('Not Found' === result.text) ? 404 : 204)
await next()
})
// Get a journal-style request by its Id
.get('/:id', checkJwt, async (ctx, next) => {
const req = await db.request.byId(ctx.state.user.sub, ctx.params.id)
if ('Not Found' === req.text) {
ctx.status(404)
} else {
ctx.body = req
}
await next()
})
// Get a request, along with its full history
.get('/:id/full', checkJwt, async (ctx, next) => {
const req = await db.request.fullById(ctx.state.user.sub, ctx.params.id)
if ('Not Found' === req.text) {
ctx.status(404)
} else {
ctx.body = req
}
await next()
})
// Get the least-recently-updated request (used for the "pray through the journal" feature)
.get('/:id/oldest', checkJwt, async (ctx, next) => {
ctx.body = await db.request.oldest(ctx.state.user.sub)
await next()
})
return router return router
} }

11278
src/app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@
"auth0-js": "^8.10.1", "auth0-js": "^8.10.1",
"axios": "^0.16.2", "axios": "^0.16.2",
"element-ui": "^1.4.4", "element-ui": "^1.4.4",
"moment": "^2.18.1",
"pug": "^2.0.0-rc.4", "pug": "^2.0.0-rc.4",
"vue": "^2.4.4", "vue": "^2.4.4",
"vue-progressbar": "^0.7.3", "vue-progressbar": "^0.7.3",

View File

@ -29,6 +29,12 @@ export default {
* Add a new prayer request * Add a new prayer request
* @param {string} requestText The text of the request to be added * @param {string} requestText The text of the request to be added
*/ */
addRequest: requestText => http.post('request/', { requestText }) addRequest: requestText => http.post('request/', { requestText }),
/**
* Mark a prayer request as having been prayed
* @param {string} requestId The Id of the request
*/
markPrayed: requestId => http.post('request/${requestId}/history', { status: 'Prayed', updateText: '' })
} }

View File

@ -5,6 +5,10 @@
template(v-if="!isLoadingJournal") template(v-if="!isLoadingJournal")
new-request new-request
p journal has {{ journal.length }} entries p journal has {{ journal.length }} entries
el-row
el-col(:span='4'): strong Actions
el-col(:span='16'): strong Request
el-col(:span='4'): strong As Of
request-list-item(v-for="request in journal" v-bind:request="request" v-bind:key="request.requestId") request-list-item(v-for="request in journal" v-bind:request="request" v-bind:key="request.requestId")
</template> </template>

View File

@ -1,22 +1,34 @@
<template lang="pug"> <template lang="pug">
el-row.journal-request(:id="request.requestId") el-row.journal-request
el-col(:span='4') Action buttons el-col(:span='4')
el-col(:span='20') p
p Id {{ request.requestId }} as of {{ request.asOf }} el-button(icon='check' @click='markPrayed()' title='Pray')
p {{ request.text }} el-button(icon='edit' @click='editRequest()' title='Edit')
el-button(icon='document' @click='viewHistory()' title='Show History')
el-col(:span='16'): p {{ request.text }}
el-col(:span='4'): p {{ asOf }}
</template> </template>
<script> <script>
import moment from 'moment'
export default { export default {
name: 'request-list-item', name: 'request-list-item',
props: ['request'], props: ['request'],
data: function () { data () {
return {} return {}
}, },
methods: { methods: {
markPrayed: function (requestId) { markPrayed () {
alert(`Marking ${this.request.requestId} prayed`)
// TODO: write function // TODO: write function
} }
},
computed: {
asOf () {
// FIXME: why isn't this already a number?
return moment(parseInt(this.request.asOf)).fromNow()
}
} }
} }
</script> </script>

View File

@ -14,9 +14,9 @@ Vue.config.productionTip = false
Vue.use(ElementUI) Vue.use(ElementUI)
Vue.use(VueProgressBar, { Vue.use(VueProgressBar, {
color: 'rgb(143, 255, 199)', color: 'rgb(32, 160, 255)',
failedColor: 'red', failedColor: 'red',
height: '2px' height: '3px'
}) })
/* eslint-disable no-new */ /* eslint-disable no-new */

6509
src/app/yarn.lock Normal file

File diff suppressed because it is too large Load Diff