From 015645aa86e32e9a6883ffbead649f2361e47c95 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Thu, 21 Sep 2017 18:45:06 -0500 Subject: [PATCH] Adding request works (mostly) Everything works except adding the new request back to the list; this proof of concept is a go, and will be merged to master --- src/api/app.js | 33 ++++++++---- src/api/db/index.js | 2 +- src/api/db/request.js | 51 +++++++++++++++---- src/api/index.js | 3 ++ src/api/json.mjs | 12 +++++ src/api/routes/request.js | 3 +- src/app/src/api/index.js | 8 ++- src/app/src/auth/AuthService.js | 12 +++-- src/app/src/components/Dashboard.vue | 10 +++- src/app/src/components/request/NewRequest.vue | 12 ++++- .../components/request/RequestListItem.vue | 15 ++++++ src/app/src/store/action-types.js | 9 +++- src/app/src/store/index.js | 46 ++++++++++------- src/app/src/store/mutation-types.js | 18 +++++-- 14 files changed, 179 insertions(+), 55 deletions(-) create mode 100644 src/api/json.mjs create mode 100644 src/app/src/components/request/RequestListItem.vue diff --git a/src/api/app.js b/src/api/app.js index 9ba2d29..4e18b89 100644 --- a/src/api/app.js +++ b/src/api/app.js @@ -6,17 +6,30 @@ const env = process.env.NODE_ENV || 'dev' if ('dev' === env) require('babel-register') +const app = require('./index').default +const db = require('./db').default +const json = require('./json.mjs').default + const fullEnv = ('dev' === env) ? 'Development' : 'Production' -/** Configuration for the application */ -const appConfig = require('./appsettings.json') +const { port } = json('./appsettings.json') -/** Express app */ -const app = require('./index').default +/** + * Log a start-up message for the app + * @param {string} status The status to display + */ +const startupMsg = (status) => { + console.log(chalk`{reset myPrayerJournal ${status} | Port: {bold ${port}} | Mode: {bold ${fullEnv}}}`) +} -// 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}}}`) - })) +// Ensure the database exists before starting up +db.verify() +.then(() => app.listen(port, () => startupMsg('ready'))) +.catch(err => { + console.log(chalk`\n{reset {bgRed.white.bold || Error connecting to PostgreSQL }}`) + for (let key of Object.keys(err)) { + console.log(chalk`${key}: {reset {bold ${err[key]}}}`) + } + console.log('') + startupMsg('failed') +}) diff --git a/src/api/db/index.js b/src/api/db/index.js index 0b5b349..5e20290 100644 --- a/src/api/db/index.js +++ b/src/api/db/index.js @@ -18,6 +18,6 @@ const query = (text, params) => pool.query(text, params) export default { query: query, - request: request(query), + request: request(pool), verify: ddl(query).ensureDatabase } diff --git a/src/api/db/request.js b/src/api/db/request.js index c25f2f0..4a96a85 100644 --- a/src/api/db/request.js +++ b/src/api/db/request.js @@ -3,7 +3,7 @@ import { Pool } from 'pg' import cuid from 'cuid' -export default function (query) { +export default function (pool) { return { /** * Get the current requests for a user (i.e., their complete current journal) @@ -11,24 +11,55 @@ export default function (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 pool.query({ + name: 'journal', + text: ` + 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 + WHERE "userId" = $1 + GROUP BY request."requestId"` + }, [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 + * @return 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 + ;(async () => { + const client = await pool.connect() + try { + await client.query('BEGIN') + await client.query( + 'INSERT INTO mpj.request ("requestId", "enteredOn", "userId") VALUES ($1, $2, $3)', + [ id, enteredOn, userId ]) + await client.query( + `INSERT INTO mpj.history ("requestId", "asOf", "status", "text") VALUES ($1, $2, 'Created', $3)`, + [ id, enteredOn, requestText ]) + await client.query('COMMIT') + } catch (e) { + await client.query('ROLLBACK') + throw e + } finally { + client.release() + } + return { requestId: id, text: requestText, asOf: enteredOn } + })().catch(e => console.error(e.stack)) } } } diff --git a/src/api/index.js b/src/api/index.js index a467651..f516249 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,6 +1,7 @@ 'use strict' import Koa from 'koa' +import bodyParser from 'koa-bodyparser' import morgan from 'koa-morgan' import send from 'koa-send' import serveFrom from 'koa-static' @@ -16,6 +17,8 @@ export default app .use(morgan('dev')) // Serve the Vue files from /public .use(serveFrom('public')) + // Parse the body into ctx.request.body, if present + .use(bodyParser()) // Tie in all the routes .use(router.routes()) .use(router.allowedMethods()) diff --git a/src/api/json.mjs b/src/api/json.mjs new file mode 100644 index 0000000..c5eb575 --- /dev/null +++ b/src/api/json.mjs @@ -0,0 +1,12 @@ +'use strict' + +import fs from 'fs' + +/** + * Read and parse a JSON file + * @param {string} path The path to the file + * @param {string} encoding The encoding of the file (defaults to UTF-8) + * @return {*} The parsed contents of the file + */ +export default (path, encoding = 'utf-8') => + JSON.parse(fs.readFileSync(path, encoding)) diff --git a/src/api/routes/request.js b/src/api/routes/request.js index b08edf4..06f3191 100644 --- a/src/api/routes/request.js +++ b/src/api/routes/request.js @@ -8,8 +8,7 @@ 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 } + ctx.body = await db.request.addNew(ctx.state.user.sub, ctx.request.body.requestText) await next() }) diff --git a/src/app/src/api/index.js b/src/app/src/api/index.js index 91b7732..c0a1a83 100644 --- a/src/app/src/api/index.js +++ b/src/app/src/api/index.js @@ -23,6 +23,12 @@ export default { /** * Get all prayer requests and their most recent updates */ - journal: () => http.get('journal/') + journal: () => http.get('journal/'), + + /** + * Add a new prayer request + * @param {string} requestText The text of the request to be added + */ + addRequest: requestText => http.post('request/', { requestText }) } diff --git a/src/app/src/auth/AuthService.js b/src/app/src/auth/AuthService.js index c71717f..e840abf 100644 --- a/src/app/src/auth/AuthService.js +++ b/src/app/src/auth/AuthService.js @@ -1,7 +1,9 @@ -import auth0 from 'auth0-js' -import { AUTH_CONFIG } from './auth0-variables' +'use strict' -import * as types from '@/store/mutation-types' +import auth0 from 'auth0-js' + +import AUTH_CONFIG from './auth0-variables' +import mutations from '@/store/mutation-types' export default class AuthService { @@ -64,7 +66,7 @@ export default class AuthService { this.setSession(authResult) this.userInfo(authResult.accessToken) .then(user => { - store.commit(types.USER_LOGGED_ON, user) + store.commit(mutations.USER_LOGGED_ON, user) router.replace('/dashboard') }) } @@ -93,7 +95,7 @@ export default class AuthService { localStorage.removeItem('expires_at') localStorage.setItem('user_profile', JSON.stringify({})) // navigate to the home route - store.commit(types.USER_LOGGED_OFF) + store.commit(mutations.USER_LOGGED_OFF) router.replace('/') } diff --git a/src/app/src/components/Dashboard.vue b/src/app/src/components/Dashboard.vue index b86c3a7..71c273d 100644 --- a/src/app/src/components/Dashboard.vue +++ b/src/app/src/components/Dashboard.vue @@ -5,14 +5,19 @@ template(v-if="!isLoadingJournal") new-request p journal has {{ journal.length }} entries + request-list-item(v-for="request in journal" v-bind:request="request" v-bind:key="request.requestId") \ No newline at end of file diff --git a/src/app/src/components/request/RequestListItem.vue b/src/app/src/components/request/RequestListItem.vue new file mode 100644 index 0000000..d2b2445 --- /dev/null +++ b/src/app/src/components/request/RequestListItem.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/app/src/store/action-types.js b/src/app/src/store/action-types.js index dc4fcf0..6d24c31 100644 --- a/src/app/src/store/action-types.js +++ b/src/app/src/store/action-types.js @@ -1 +1,8 @@ -export const LOAD_JOURNAL = 'load-journal' +'use strict' + +export default { + /** Action to add a prayer request (pass request text) */ + ADD_REQUEST: 'add-request', + /** Action to load the user's prayer journal */ + LOAD_JOURNAL: 'load-journal' +} diff --git a/src/app/src/store/index.js b/src/app/src/store/index.js index a9cb43c..556f9ce 100644 --- a/src/app/src/store/index.js +++ b/src/app/src/store/index.js @@ -4,8 +4,8 @@ import Vuex from 'vuex' import api from '@/api' import AuthService from '@/auth/AuthService' -import * as types from './mutation-types' -import * as actions from './action-types' +import mutations from './mutation-types' +import actions from './action-types' Vue.use(Vuex) @@ -38,38 +38,48 @@ export default new Vuex.Store({ isLoadingJournal: false }, mutations: { - [types.USER_LOGGED_ON] (state, user) { + [mutations.USER_LOGGED_ON] (state, user) { localStorage.setItem('user_profile', JSON.stringify(user)) state.user = user api.setBearer(localStorage.getItem('id_token')) state.isAuthenticated = true }, - [types.USER_LOGGED_OFF] (state) { + [mutations.USER_LOGGED_OFF] (state) { state.user = {} api.removeBearer() state.isAuthenticated = false }, - [types.LOADING_JOURNAL] (state, flag) { + [mutations.LOADING_JOURNAL] (state, flag) { state.isLoadingJournal = flag }, - [types.LOADED_JOURNAL] (state, journal) { + [mutations.LOADED_JOURNAL] (state, journal) { state.journal = journal + }, + [mutations.REQUEST_ADDED] (state, newRequest) { + state.journal.unshift(newRequest) } }, actions: { - [actions.LOAD_JOURNAL] ({ commit }) { - commit(types.LOADED_JOURNAL, {}) - commit(types.LOADING_JOURNAL, true) + async [actions.LOAD_JOURNAL] ({ commit }) { + commit(mutations.LOADED_JOURNAL, {}) + commit(mutations.LOADING_JOURNAL, true) api.setBearer(localStorage.getItem('id_token')) - api.journal() - .then(jrnl => { - commit(types.LOADING_JOURNAL, false) - commit(types.LOADED_JOURNAL, jrnl.data) - }) - .catch(err => { - commit(types.LOADING_JOURNAL, false) - logError(err) - }) + try { + const jrnl = await api.journal() + commit(mutations.LOADED_JOURNAL, jrnl.data) + } catch (err) { + logError(err) + } finally { + commit(mutations.LOADING_JOURNAL, false) + } + }, + async [actions.ADD_REQUEST] ({ commit }, requestText) { + try { + const newRequest = await api.addRequest(requestText) + commit(mutations.REQUEST_ADDED, newRequest) + } catch (err) { + logError(err) + } } }, getters: {}, diff --git a/src/app/src/store/mutation-types.js b/src/app/src/store/mutation-types.js index d306356..e396b63 100644 --- a/src/app/src/store/mutation-types.js +++ b/src/app/src/store/mutation-types.js @@ -1,4 +1,14 @@ -export const USER_LOGGED_OFF = 'user-logged-out' -export const USER_LOGGED_ON = 'user-logged-on' -export const LOADING_JOURNAL = 'loading-journal' -export const LOADED_JOURNAL = 'journal-loaded' +'use strict' + +export default { + /** Mutation for when the user's prayer journal is being loaded */ + LOADING_JOURNAL: 'loading-journal', + /** Mutation for when the user's prayer journal has been loaded */ + LOADED_JOURNAL: 'journal-loaded', + /** Mutation for adding a new prayer request (pass text) */ + REQUEST_ADDED: 'request-added', + /** Mutation for logging a user off */ + USER_LOGGED_OFF: 'user-logged-off', + /** Mutation for logging a user on (pass user) */ + USER_LOGGED_ON: 'user-logged-on' +}