Node API attempt #1
| @ -6,17 +6,30 @@ const env = process.env.NODE_ENV || 'dev' | |||||||
| 
 | 
 | ||||||
| if ('dev' === env) require('babel-register') | 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' | const fullEnv = ('dev' === env) ? 'Development' : 'Production' | ||||||
| 
 | 
 | ||||||
| /** Configuration for the application */ | const { port } = json('./appsettings.json') | ||||||
| const appConfig = require('./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...
 | // Ensure the database exists before starting up
 | ||||||
| require('./db').default.verify().then(() =>  | db.verify() | ||||||
|   // ...and start it up!
 | .then(() => app.listen(port, () => startupMsg('ready'))) | ||||||
|   app.listen(appConfig.port, () => { | .catch(err => { | ||||||
|     console.log(chalk`{reset myPrayerJournal | Port: {bold ${appConfig.port}} | Mode: {bold ${fullEnv}}}`) |   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 { | export default { | ||||||
|   query: query, |   query: query, | ||||||
|   request: request(query), |   request: request(pool), | ||||||
|   verify: ddl(query).ensureDatabase |   verify: ddl(query).ensureDatabase | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
| import { Pool } from 'pg' | import { Pool } from 'pg' | ||||||
| import cuid from 'cuid' | import cuid from 'cuid' | ||||||
| 
 | 
 | ||||||
| export default function (query) { | export default function (pool) { | ||||||
|   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) | ||||||
| @ -11,24 +11,55 @@ export default function (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 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 |      * Add a new prayer request | ||||||
|      * @param {string} userId The Id of the user |      * @param {string} userId The Id of the user | ||||||
|      * @param {string} requestText The text of the request |      * @param {string} requestText The text of the request | ||||||
|      * @return {string} The Id of the created request |      * @return The created request | ||||||
|      */ |      */ | ||||||
|     addNew: async (userId, requestText) => { |     addNew: async (userId, requestText) => { | ||||||
|       const id = cuid() |       const id = cuid() | ||||||
|       const enteredOn = Date.now() |       const enteredOn = Date.now() | ||||||
|       await query(` |       ;(async () => { | ||||||
|         BEGIN; |         const client = await pool.connect() | ||||||
|         INSERT INTO request ("requestId", "enteredOn", "userId") VALUES ($1, $2, $3); |         try { | ||||||
|         INSERT INTO history ("requestId", "asOf", "status", "text") VALUES ($1, $2, 'Created', $4); |           await client.query('BEGIN') | ||||||
|         COMMIT;`,
 |           await client.query( | ||||||
|         [ id, enteredOn, userId, requestText ]) |             'INSERT INTO mpj.request ("requestId", "enteredOn", "userId") VALUES ($1, $2, $3)', | ||||||
|       return id |             [ 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' | 'use strict' | ||||||
| 
 | 
 | ||||||
| import Koa from 'koa' | import Koa from 'koa' | ||||||
|  | import bodyParser from 'koa-bodyparser' | ||||||
| import morgan from 'koa-morgan' | import morgan from 'koa-morgan' | ||||||
| import send from 'koa-send' | import send from 'koa-send' | ||||||
| import serveFrom from 'koa-static' | import serveFrom from 'koa-static' | ||||||
| @ -16,6 +17,8 @@ export default app | |||||||
|   .use(morgan('dev')) |   .use(morgan('dev')) | ||||||
|   // Serve the Vue files from /public
 |   // Serve the Vue files from /public
 | ||||||
|   .use(serveFrom('public')) |   .use(serveFrom('public')) | ||||||
|  |   // Parse the body into ctx.request.body, if present
 | ||||||
|  |   .use(bodyParser()) | ||||||
|   // Tie in all the routes
 |   // Tie in all the routes
 | ||||||
|   .use(router.routes()) |   .use(router.routes()) | ||||||
|   .use(router.allowedMethods()) |   .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) { | export default function (checkJwt) { | ||||||
| 
 | 
 | ||||||
|   router.post('/', checkJwt, async (ctx, next) => { |   router.post('/', checkJwt, async (ctx, next) => { | ||||||
|     const newId = await db.request.addNew(ctx.state.user.sub, ctx.body.requestText) |     ctx.body = await db.request.addNew(ctx.state.user.sub, ctx.request.body.requestText) | ||||||
|     ctx.body = { id: newId } |  | ||||||
|     await next() |     await next() | ||||||
|   }) |   }) | ||||||
|    |    | ||||||
|  | |||||||
| @ -23,6 +23,12 @@ export default { | |||||||
|   /** |   /** | ||||||
|    * Get all prayer requests and their most recent updates |    * 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' | 'use strict' | ||||||
| import { AUTH_CONFIG } from './auth0-variables' |  | ||||||
| 
 | 
 | ||||||
| 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 { | export default class AuthService { | ||||||
| 
 | 
 | ||||||
| @ -64,7 +66,7 @@ export default class AuthService { | |||||||
|           this.setSession(authResult) |           this.setSession(authResult) | ||||||
|           this.userInfo(authResult.accessToken) |           this.userInfo(authResult.accessToken) | ||||||
|             .then(user => { |             .then(user => { | ||||||
|               store.commit(types.USER_LOGGED_ON, user) |               store.commit(mutations.USER_LOGGED_ON, user) | ||||||
|               router.replace('/dashboard') |               router.replace('/dashboard') | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| @ -93,7 +95,7 @@ export default class AuthService { | |||||||
|     localStorage.removeItem('expires_at') |     localStorage.removeItem('expires_at') | ||||||
|     localStorage.setItem('user_profile', JSON.stringify({})) |     localStorage.setItem('user_profile', JSON.stringify({})) | ||||||
|     // navigate to the home route
 |     // navigate to the home route
 | ||||||
|     store.commit(types.USER_LOGGED_OFF) |     store.commit(mutations.USER_LOGGED_OFF) | ||||||
|     router.replace('/') |     router.replace('/') | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,14 +5,19 @@ | |||||||
|     template(v-if="!isLoadingJournal") |     template(v-if="!isLoadingJournal") | ||||||
|       new-request |       new-request | ||||||
|       p journal has {{ journal.length }} entries |       p journal has {{ journal.length }} entries | ||||||
|  |       request-list-item(v-for="request in journal" v-bind:request="request" v-bind:key="request.requestId") | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|  | 'use strict' | ||||||
|  | 
 | ||||||
| import { mapState } from 'vuex' | import { mapState } from 'vuex' | ||||||
|  | 
 | ||||||
| import PageTitle from './PageTitle' | import PageTitle from './PageTitle' | ||||||
| import NewRequest from './request/NewRequest' | 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 { | export default { | ||||||
|   name: 'dashboard', |   name: 'dashboard', | ||||||
| @ -22,7 +27,8 @@ export default { | |||||||
|   }, |   }, | ||||||
|   components: { |   components: { | ||||||
|     PageTitle, |     PageTitle, | ||||||
|     NewRequest |     NewRequest, | ||||||
|  |     RequestListItem | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     title () { |     title () { | ||||||
|  | |||||||
| @ -7,10 +7,14 @@ | |||||||
|           el-input(type='textarea' v-model.trim='form.requestText' :rows='10') |           el-input(type='textarea' v-model.trim='form.requestText' :rows='10') | ||||||
|       span.dialog-footer(slot='footer') |       span.dialog-footer(slot='footer') | ||||||
|         el-button(@click='showNewVisible = false') Cancel |         el-button(@click='showNewVisible = false') Cancel | ||||||
|         el-button(type='primary' @click='showNewVisible = false') Confirm |         el-button(type='primary' @click='saveRequest()') Save | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|  | 'use strict' | ||||||
|  | 
 | ||||||
|  | import actions from '@/store/action-types' | ||||||
|  | 
 | ||||||
| export default { | export default { | ||||||
|   name: 'new-request', |   name: 'new-request', | ||||||
|   data () { |   data () { | ||||||
| @ -21,6 +25,12 @@ export default { | |||||||
|       }, |       }, | ||||||
|       formLabelWidth: '120px' |       formLabelWidth: '120px' | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     saveRequest: async function () { | ||||||
|  |       await this.$store.dispatch(actions.ADD_REQUEST, this.form.requestText) | ||||||
|  |       this.showNewVisible = false | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| </script> | </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 api from '@/api' | ||||||
| import AuthService from '@/auth/AuthService' | import AuthService from '@/auth/AuthService' | ||||||
| 
 | 
 | ||||||
| import * as types from './mutation-types' | import mutations from './mutation-types' | ||||||
| import * as actions from './action-types' | import actions from './action-types' | ||||||
| 
 | 
 | ||||||
| Vue.use(Vuex) | Vue.use(Vuex) | ||||||
| 
 | 
 | ||||||
| @ -38,38 +38,48 @@ export default new Vuex.Store({ | |||||||
|     isLoadingJournal: false |     isLoadingJournal: false | ||||||
|   }, |   }, | ||||||
|   mutations: { |   mutations: { | ||||||
|     [types.USER_LOGGED_ON] (state, user) { |     [mutations.USER_LOGGED_ON] (state, user) { | ||||||
|       localStorage.setItem('user_profile', JSON.stringify(user)) |       localStorage.setItem('user_profile', JSON.stringify(user)) | ||||||
|       state.user = user |       state.user = user | ||||||
|       api.setBearer(localStorage.getItem('id_token')) |       api.setBearer(localStorage.getItem('id_token')) | ||||||
|       state.isAuthenticated = true |       state.isAuthenticated = true | ||||||
|     }, |     }, | ||||||
|     [types.USER_LOGGED_OFF] (state) { |     [mutations.USER_LOGGED_OFF] (state) { | ||||||
|       state.user = {} |       state.user = {} | ||||||
|       api.removeBearer() |       api.removeBearer() | ||||||
|       state.isAuthenticated = false |       state.isAuthenticated = false | ||||||
|     }, |     }, | ||||||
|     [types.LOADING_JOURNAL] (state, flag) { |     [mutations.LOADING_JOURNAL] (state, flag) { | ||||||
|       state.isLoadingJournal = flag |       state.isLoadingJournal = flag | ||||||
|     }, |     }, | ||||||
|     [types.LOADED_JOURNAL] (state, journal) { |     [mutations.LOADED_JOURNAL] (state, journal) { | ||||||
|       state.journal = journal |       state.journal = journal | ||||||
|  |     }, | ||||||
|  |     [mutations.REQUEST_ADDED] (state, newRequest) { | ||||||
|  |       state.journal.unshift(newRequest) | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   actions: { |   actions: { | ||||||
|     [actions.LOAD_JOURNAL] ({ commit }) { |     async [actions.LOAD_JOURNAL] ({ commit }) { | ||||||
|       commit(types.LOADED_JOURNAL, {}) |       commit(mutations.LOADED_JOURNAL, {}) | ||||||
|       commit(types.LOADING_JOURNAL, true) |       commit(mutations.LOADING_JOURNAL, true) | ||||||
|       api.setBearer(localStorage.getItem('id_token')) |       api.setBearer(localStorage.getItem('id_token')) | ||||||
|       api.journal() |       try { | ||||||
|         .then(jrnl => { |         const jrnl = await api.journal() | ||||||
|           commit(types.LOADING_JOURNAL, false) |         commit(mutations.LOADED_JOURNAL, jrnl.data) | ||||||
|           commit(types.LOADED_JOURNAL, jrnl.data) |       } catch (err) { | ||||||
|         }) |  | ||||||
|         .catch(err => { |  | ||||||
|           commit(types.LOADING_JOURNAL, false) |  | ||||||
|         logError(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: {}, |   getters: {}, | ||||||
|  | |||||||
| @ -1,4 +1,14 @@ | |||||||
| export const USER_LOGGED_OFF = 'user-logged-out' | 'use strict' | ||||||
| export const USER_LOGGED_ON = 'user-logged-on' | 
 | ||||||
| export const LOADING_JOURNAL = 'loading-journal' | export default { | ||||||
| export const LOADED_JOURNAL = 'journal-loaded' |   /** 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user