Node API attempt #1
|
@ -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')
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
12
src/api/json.mjs
Normal file
12
src/api/json.mjs
Normal file
|
@ -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))
|
|
@ -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()
|
||||
})
|
||||
|
||||
|
|
|
@ -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 })
|
||||
|
||||
}
|
||||
|
|
|
@ -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('/')
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
'use strict'
|
||||
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import PageTitle from './PageTitle'
|
||||
import NewRequest from './request/NewRequest'
|
||||
import RequestListItem from './request/RequestListItem'
|
||||
|
||||
import * as actions from '@/store/action-types'
|
||||
import actions from '@/store/action-types'
|
||||
|
||||
export default {
|
||||
name: 'dashboard',
|
||||
|
@ -22,7 +27,8 @@ export default {
|
|||
},
|
||||
components: {
|
||||
PageTitle,
|
||||
NewRequest
|
||||
NewRequest,
|
||||
RequestListItem
|
||||
},
|
||||
computed: {
|
||||
title () {
|
||||
|
|
|
@ -7,10 +7,14 @@
|
|||
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
|
||||
el-button(type='primary' @click='saveRequest()') Save
|
||||
</template>
|
||||
|
||||
<script>
|
||||
'use strict'
|
||||
|
||||
import actions from '@/store/action-types'
|
||||
|
||||
export default {
|
||||
name: 'new-request',
|
||||
data () {
|
||||
|
@ -21,6 +25,12 @@ export default {
|
|||
},
|
||||
formLabelWidth: '120px'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveRequest: async function () {
|
||||
await this.$store.dispatch(actions.ADD_REQUEST, this.form.requestText)
|
||||
this.showNewVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
15
src/app/src/components/request/RequestListItem.vue
Normal file
15
src/app/src/components/request/RequestListItem.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<template lang="pug">
|
||||
div
|
||||
p Id {{ request.requestId }} as of {{ request.asOf }}
|
||||
p {{ request.text }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'request-list-item',
|
||||
props: ['request'],
|
||||
data: function () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user