Version 3 #67

Merged
danieljsummers merged 53 commits from version-3 into master 2021-10-26 23:39:59 +00:00
17 changed files with 264 additions and 196 deletions
Showing only changes of commit f8dad545bb - Show all commits

View File

@ -35,7 +35,7 @@ import Vue from 'vue'
import Navigation from '@/components/common/Navigation' import Navigation from '@/components/common/Navigation'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
import { version } from '../package.json' import { version } from '../package.json'
export default { export default {
@ -63,7 +63,7 @@ export default {
this.progress.events.$on('done', this.hideProgress) this.progress.events.$on('done', this.hideProgress)
this.snackbar.events.$on('info', this.showInfo) this.snackbar.events.$on('info', this.showInfo)
this.snackbar.events.$on('error', this.showError) this.snackbar.events.$on('error', this.showError)
await this.$store.dispatch(actions.CHECK_AUTHENTICATION) await this.$store.dispatch(Actions.CheckAuthentication)
}, },
computed: { computed: {
version () { version () {

View File

@ -13,9 +13,9 @@ export default {
/** /**
* Set the bearer token for all future requests * Set the bearer token for all future requests
* @param {string} token The token to use to identify the user to the server * @param token The token to use to identify the user to the server
*/ */
setBearer: token => { http.defaults.headers.common.Authorization = `Bearer ${token}` }, setBearer: (token: string) => { http.defaults.headers.common.Authorization = `Bearer ${token}` },
/** /**
* Remove the bearer token * Remove the bearer token
@ -24,18 +24,19 @@ export default {
/** /**
* Add a note for a prayer request * Add a note for a prayer request
* @param {string} requestId The Id of the request to which the note applies * @param requestId The Id of the request to which the note applies
* @param {string} notes The notes to be added * @param notes The notes to be added
*/ */
addNote: (requestId, notes) => http.post(`request/${requestId}/note`, { notes }), addNote: (requestId: string, notes: string) => http.post(`request/${requestId}/note`, { notes }),
/** /**
* Add a new prayer request * Add a new prayer request
* @param {string} requestText The text of the request to be added * @param requestText The text of the request to be added
* @param {string} recurType The type of recurrence for this request * @param recurType The type of recurrence for this request
* @param {number} recurCount The number of intervals of recurrence * @param recurCount The number of intervals of recurrence
*/ */
addRequest: (requestText, recurType, recurCount) => http.post('request', { requestText, recurType, recurCount }), addRequest: (requestText: string, recurType: string, recurCount: number) =>
http.post('request', { requestText, recurType, recurCount }),
/** /**
* Get all answered requests, along with the text they had when it was answered * Get all answered requests, along with the text they had when it was answered
@ -44,21 +45,21 @@ export default {
/** /**
* Get a prayer request (full; includes all history and notes) * Get a prayer request (full; includes all history and notes)
* @param {string} requestId The Id of the request to retrieve * @param requestId The Id of the request to retrieve
*/ */
getFullRequest: requestId => http.get(`request/${requestId}/full`), getFullRequest: (requestId: string) => http.get(`request/${requestId}/full`),
/** /**
* Get past notes for a prayer request * Get past notes for a prayer request
* @param {string} requestId The Id of the request for which notes should be retrieved * @param requestId The Id of the request for which notes should be retrieved
*/ */
getNotes: requestId => http.get(`request/${requestId}/notes`), getNotes: (requestId: string) => http.get(`request/${requestId}/notes`),
/** /**
* Get a prayer request (journal-style; only latest update) * Get a prayer request (journal-style; only latest update)
* @param {string} requestId The Id of the request to retrieve * @param requestId The Id of the request to retrieve
*/ */
getRequest: requestId => http.get(`request/${requestId}`), getRequest: (requestId: string) => http.get(`request/${requestId}`),
/** /**
* Get all prayer requests and their most recent updates * Get all prayer requests and their most recent updates
@ -67,32 +68,33 @@ export default {
/** /**
* Show a request after the given date (used for "show now") * Show a request after the given date (used for "show now")
* @param {string} requestId The ID of the request which should be shown * @param requestId The ID of the request which should be shown
* @param {number} showAfter The ticks after which the request should be shown * @param showAfter The ticks after which the request should be shown
*/ */
showRequest: (requestId, showAfter) => http.patch(`request/${requestId}/show`, { showAfter }), showRequest: (requestId: string, showAfter: number) => http.patch(`request/${requestId}/show`, { showAfter }),
/** /**
* Snooze a request until the given time * Snooze a request until the given time
* @param {string} requestId The ID of the prayer request to be snoozed * @param requestId The ID of the prayer request to be snoozed
* @param {number} until The ticks until which the request should be snoozed * @param until The ticks until which the request should be snoozed
*/ */
snoozeRequest: (requestId, until) => http.patch(`request/${requestId}/snooze`, { until }), snoozeRequest: (requestId: string, until: number) => http.patch(`request/${requestId}/snooze`, { until }),
/** /**
* Update recurrence for a prayer request * Update recurrence for a prayer request
* @param {string} requestId The ID of the prayer request for which recurrence is being updated * @param requestId The ID of the prayer request for which recurrence is being updated
* @param {string} recurType The type of recurrence to set * @param recurType The type of recurrence to set
* @param {number} recurCount The number of recurrence intervals to set * @param recurCount The number of recurrence intervals to set
*/ */
updateRecurrence: (requestId, recurType, recurCount) => updateRecurrence: (requestId: string, recurType: string, recurCount: number) =>
http.patch(`request/${requestId}/recurrence`, { recurType, recurCount }), http.patch(`request/${requestId}/recurrence`, { recurType, recurCount }),
/** /**
* Update a prayer request * Update a prayer request
* @param {string} requestId The ID of the request to be updated * @param requestId The ID of the request to be updated
* @param {string} status The status of the update * @param status The status of the update
* @param {string} updateText The text of the update (optional) * @param updateText The text of the update (optional)
*/ */
updateRequest: (requestId, status, updateText) => http.post(`request/${requestId}/history`, { status, updateText }) updateRequest: (requestId: string, status: string, updateText: string) =>
http.post(`request/${requestId}/history`, { status, updateText })
} }

View File

@ -1,18 +1,18 @@
'use strict' import { Store } from 'vuex'
/* eslint-disable */ import auth0, { Auth0DecodedHash } from 'auth0-js'
import auth0 from 'auth0-js'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import AUTH_CONFIG from './auth0-variables' import { Mutations, AppState } from '@/store/types'
import mutations from '@/store/mutation-types'
/* es-lint-enable*/ import Auth0Config from './auth0-variables'
import { Session } from './types'
// Auth0 web authentication instance to use for our calls // Auth0 web authentication instance to use for our calls
const webAuth = new auth0.WebAuth({ const webAuth = new auth0.WebAuth({
domain: AUTH_CONFIG.domain, domain: Auth0Config.domain,
clientID: AUTH_CONFIG.clientId, clientID: Auth0Config.clientId,
redirectUri: AUTH_CONFIG.appDomain + AUTH_CONFIG.callbackUrl, redirectUri: Auth0Config.appDomain + Auth0Config.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`, audience: `https://${Auth0Config.domain}/userinfo`,
responseType: 'token id_token', responseType: 'token id_token',
scope: 'openid profile email' scope: 'openid profile email'
}) })
@ -21,14 +21,13 @@ const webAuth = new auth0.WebAuth({
* A class to handle all authentication calls and determinations * A class to handle all authentication calls and determinations
*/ */
class AuthService extends EventEmitter { class AuthService extends EventEmitter {
// Local storage key for our session data // Local storage key for our session data
AUTH_SESSION = 'auth-session' AUTH_SESSION = 'auth-session'
// Received and calculated values for our ssesion (initially loaded from local storage if present) // Received and calculated values for our ssesion (initially loaded from local storage if present)
session: any = {} session = new Session()
constructor() { constructor () {
super() super()
this.refreshSession() this.refreshSession()
} }
@ -36,7 +35,7 @@ class AuthService extends EventEmitter {
/** /**
* Starts the user log in flow * Starts the user log in flow
*/ */
login (customState) { login (customState: any) {
webAuth.authorize({ webAuth.authorize({
appState: customState appState: customState
}) })
@ -45,10 +44,10 @@ class AuthService extends EventEmitter {
/** /**
* Promisified parseHash function * Promisified parseHash function
*/ */
parseHash () { parseHash (): Promise<Auth0DecodedHash> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
webAuth.parseHash((err, authResult) => { webAuth.parseHash((err, authResult) => {
if (err) { if (err || authResult === null) {
reject(err) reject(err)
} else { } else {
resolve(authResult) resolve(authResult)
@ -59,33 +58,31 @@ class AuthService extends EventEmitter {
/** /**
* Handle authentication replies from Auth0 * Handle authentication replies from Auth0
*
* @param store The Vuex store * @param store The Vuex store
*/ */
async handleAuthentication (store) { async handleAuthentication (store: Store<AppState>) {
try { try {
const authResult: any = await this.parseHash() const authResult = await this.parseHash()
if (authResult && authResult.accessToken && authResult.idToken) { if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult) this.setSession(authResult)
store.commit(mutations.USER_LOGGED_ON, this.session.profile) store.commit(Mutations.UserLoggedOn, this.session.profile)
} }
} catch(err) { } catch (err) {
console.error(err) console.error(err) // eslint-disable-line no-console
alert(`Error: ${err.error}. Check the console for further details.`) alert(`Error: ${err.error}. Check the console for further details.`)
} }
} }
/** /**
* Set up the session and commit it to local storage * Set up the session and commit it to local storage
*
* @param authResult The authorization result * @param authResult The authorization result
*/ */
setSession (authResult) { setSession (authResult: Auth0DecodedHash) {
this.session.profile = authResult.idTokenPayload this.session.profile = authResult.idTokenPayload
this.session.id.token = authResult.idToken this.session.id.token = authResult.idToken!
this.session.id.expiry = this.session.profile.exp * 1000 this.session.id.expiry = this.session.profile.exp * 1000
this.session.access.token = authResult.accessToken this.session.access.token = authResult.accessToken!
this.session.access.expiry = authResult.expiresIn * 1000 + Date.now() this.session.access.expiry = authResult.expiresIn! * 1000 + Date.now()
localStorage.setItem(this.AUTH_SESSION, JSON.stringify(this.session)) localStorage.setItem(this.AUTH_SESSION, JSON.stringify(this.session))
@ -100,10 +97,8 @@ class AuthService extends EventEmitter {
* Refresh this instance's session from the one in local storage * Refresh this instance's session from the one in local storage
*/ */
refreshSession () { refreshSession () {
this.session = const emptySession = {
localStorage.getItem(this.AUTH_SESSION) profile: {},
? JSON.parse(localStorage.getItem(this.AUTH_SESSION) || '{}')
: { profile: {},
id: { id: {
token: null, token: null,
expiry: null expiry: null
@ -113,12 +108,16 @@ class AuthService extends EventEmitter {
expiry: null expiry: null
} }
} }
this.session =
localStorage.getItem(this.AUTH_SESSION)
? JSON.parse(localStorage.getItem(this.AUTH_SESSION) || '{}')
: emptySession
} }
/** /**
* Renew authorzation tokens with Auth0 * Renew authorzation tokens with Auth0
*/ */
renewTokens () { renewTokens (): Promise<Auth0DecodedHash> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.refreshSession() this.refreshSession()
if (this.session.id.token !== null) { if (this.session.id.token !== null) {
@ -126,67 +125,58 @@ class AuthService extends EventEmitter {
if (err) { if (err) {
reject(err) reject(err)
} else { } else {
this.setSession(authResult) const result = authResult as Auth0DecodedHash
resolve(authResult) this.setSession(result)
resolve(result)
} }
}) })
} else { } else {
reject('Not logged in') reject(new Error('Not logged in'))
} }
}) })
} }
/** /**
* Log out of myPrayerJournal * Log out of myPrayerJournal
*
* @param store The Vuex store * @param store The Vuex store
*/ */
logout (store) { logout (store: Store<AppState>) {
// Clear access token and ID token from local storage // Clear access token and ID token from local storage
localStorage.removeItem(this.AUTH_SESSION) localStorage.removeItem(this.AUTH_SESSION)
this.refreshSession() this.refreshSession()
store.commit(mutations.USER_LOGGED_OFF) store.commit(Mutations.UserLoggedOff)
webAuth.logout({ webAuth.logout({
returnTo: `${AUTH_CONFIG.appDomain}/`, returnTo: `${Auth0Config.appDomain}/`,
clientID: AUTH_CONFIG.clientId clientID: Auth0Config.clientId
}) })
this.emit('loginEvent', { loggedIn: false }) this.emit('loginEvent', { loggedIn: false })
} }
/**
* Check expiration for a token (the way it's stored in the session)
*/
checkExpiry = (it) => it.token && it.expiry && Date.now() < it.expiry
/** /**
* Is there a user authenticated? * Is there a user authenticated?
*/ */
isAuthenticated () { isAuthenticated () {
return this.checkExpiry(this.session.id) return this.session.id.isValid()
} }
/** /**
* Is the current access token valid? * Is the current access token valid?
*/ */
isAccessTokenValid () { isAccessTokenValid () {
return this.checkExpiry(this.session.access) return this.session.access.isValid()
} }
/** /**
* Get the user's access token, renewing it if required * Get the user's access token, renewing it if required
*/ */
async getAccessToken () { async getAccessToken (): Promise<string> {
if (this.isAccessTokenValid()) { if (this.isAccessTokenValid()) {
return this.session.access.token return this.session.access.token
} else { } else {
try { const authResult = await this.renewTokens()
const authResult: any = await this.renewTokens() return authResult.accessToken!
return authResult.accessToken
} catch (reject) {
throw reject
}
} }
} }
} }

25
src/app/src/auth/types.ts Normal file
View File

@ -0,0 +1,25 @@
/** A token and expiration set */
export class Token {
/** The actual token */
token: string = ''
/** The expiration for the token */
expiry: number = 0
/** Whether this token is currently valid */
isValid (): boolean {
return this.token !== '' && this.expiry !== 0 && Date.now() < this.expiry
}
}
/** A user's current session */
export class Session {
/** The user's profile from Auth0 */
profile: any = {}
/** The user's ID token */
id = new Token()
/** The user's access token */
access = new Token()
}

View File

@ -31,7 +31,7 @@ import NotesEdit from './request/NotesEdit'
import RequestCard from './request/RequestCard' import RequestCard from './request/RequestCard'
import SnoozeRequest from './request/SnoozeRequest' import SnoozeRequest from './request/SnoozeRequest'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
export default { export default {
name: 'journal', name: 'journal',
@ -59,7 +59,7 @@ export default {
...mapState(['user', 'journal', 'isLoadingJournal']) ...mapState(['user', 'journal', 'isLoadingJournal'])
}, },
async created () { async created () {
await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) await this.$store.dispatch(Actions.LoadJournal, this.progress)
this.messages.$emit('info', `Loaded ${this.journal.length} prayer requests`) this.messages.$emit('info', `Loaded ${this.journal.length} prayer requests`)
}, },
provide () { provide () {

View File

@ -21,7 +21,7 @@ import { mapState } from 'vuex'
import RequestList from '@/components/request/RequestList' import RequestList from '@/components/request/RequestList'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
export default { export default {
name: 'active-requests', name: 'active-requests',
@ -46,7 +46,7 @@ export default {
async ensureJournal () { async ensureJournal () {
if (!Array.isArray(this.journal)) { if (!Array.isArray(this.journal)) {
this.loaded = false this.loaded = false
await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) await this.$store.dispatch(Actions.LoadJournal, this.progress)
} }
this.requests = this.journal this.requests = this.journal
.sort((a, b) => a.showAfter - b.showAfter) .sort((a, b) => a.showAfter - b.showAfter)

View File

@ -54,7 +54,7 @@ md-content(role='main').mpj-narrow
import { mapState } from 'vuex' import { mapState } from 'vuex'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
export default { export default {
name: 'edit-request', name: 'edit-request',
@ -114,7 +114,7 @@ export default {
this.title = 'Edit Prayer Request' this.title = 'Edit Prayer Request'
this.isNew = false this.isNew = false
if (this.journal.length === 0) { if (this.journal.length === 0) {
await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) await this.$store.dispatch(Actions.LoadJournal, this.progress)
} }
const req = this.journal.filter(r => r.requestId === this.id)[0] const req = this.journal.filter(r => r.requestId === this.id)[0]
this.form.requestId = this.id this.form.requestId = this.id
@ -140,12 +140,12 @@ export default {
}, },
async ensureJournal () { async ensureJournal () {
if (!Array.isArray(this.journal)) { if (!Array.isArray(this.journal)) {
await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) await this.$store.dispatch(Actions.LoadJournal, this.progress)
} }
}, },
async saveRequest () { async saveRequest () {
if (this.isNew) { if (this.isNew) {
await this.$store.dispatch(actions.ADD_REQUEST, { await this.$store.dispatch(Actions.AddRequest, {
progress: this.progress, progress: this.progress,
requestText: this.form.requestText, requestText: this.form.requestText,
recurType: this.form.recur.typ === 'Immediate' ? 'Immediate' : this.form.recur.other, recurType: this.form.recur.typ === 'Immediate' ? 'Immediate' : this.form.recur.other,
@ -153,7 +153,7 @@ export default {
}) })
this.messages.$emit('info', 'New prayer request added') this.messages.$emit('info', 'New prayer request added')
} else { } else {
await this.$store.dispatch(actions.UPDATE_REQUEST, { await this.$store.dispatch(Actions.UpdateRequest, {
progress: this.progress, progress: this.progress,
requestId: this.form.requestId, requestId: this.form.requestId,
updateText: this.form.requestText, updateText: this.form.requestText,

View File

@ -27,7 +27,7 @@ md-card(v-if='shouldDisplay'
<script> <script>
'use strict' 'use strict'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
export default { export default {
name: 'request-card', name: 'request-card',
@ -47,7 +47,7 @@ export default {
}, },
methods: { methods: {
async markPrayed () { async markPrayed () {
await this.$store.dispatch(actions.UPDATE_REQUEST, { await this.$store.dispatch(Actions.UpdateRequest, {
progress: this.progress, progress: this.progress,
requestId: this.request.requestId, requestId: this.request.requestId,
status: 'Prayed', status: 'Prayed',

View File

@ -31,7 +31,7 @@ md-table-row
<script> <script>
'use strict' 'use strict'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
export default { export default {
name: 'request-list-item', name: 'request-list-item',
@ -61,7 +61,7 @@ export default {
}, },
methods: { methods: {
async cancelSnooze () { async cancelSnooze () {
await this.$store.dispatch(actions.SNOOZE_REQUEST, { await this.$store.dispatch(Actions.SnoozeRequest, {
progress: this.progress, progress: this.progress,
requestId: this.request.requestId, requestId: this.request.requestId,
until: 0 until: 0
@ -73,7 +73,7 @@ export default {
this.$router.push({ name: 'EditRequest', params: { id: this.request.requestId } }) this.$router.push({ name: 'EditRequest', params: { id: this.request.requestId } })
}, },
async showNow () { async showNow () {
await this.$store.dispatch(actions.SHOW_REQUEST_NOW, { await this.$store.dispatch(Actions.ShowRequestNow, {
progress: this.progress, progress: this.progress,
requestId: this.request.requestId, requestId: this.request.requestId,
showAfter: 0 showAfter: 0

View File

@ -15,7 +15,7 @@ md-dialog(:md-active.sync='snoozeVisible').mpj-skinny
<script> <script>
'use strict' 'use strict'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
export default { export default {
name: 'snooze-request', name: 'snooze-request',
@ -56,7 +56,7 @@ export default {
this.snoozeVisible = true this.snoozeVisible = true
}, },
async snoozeRequest () { async snoozeRequest () {
await this.$store.dispatch(actions.SNOOZE_REQUEST, { await this.$store.dispatch(Actions.SnoozeRequest, {
progress: this.progress, progress: this.progress,
requestId: this.form.requestId, requestId: this.form.requestId,
until: Date.parse(this.form.snoozedUntil) until: Date.parse(this.form.snoozedUntil)

View File

@ -19,7 +19,7 @@ article.mpj-main-content(role='main')
import { mapState } from 'vuex' import { mapState } from 'vuex'
import actions from '@/store/action-types' import { Actions } from '@/store/types'
import RequestList from '@/components/request/RequestList' import RequestList from '@/components/request/RequestList'
@ -45,7 +45,7 @@ export default {
async ensureJournal () { async ensureJournal () {
if (!Array.isArray(this.journal)) { if (!Array.isArray(this.journal)) {
this.loaded = false this.loaded = false
await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) await this.$store.dispatch(Actions.LoadJournal, this.progress)
} }
this.requests = this.journal this.requests = this.journal
.filter(req => req.snoozedUntil > Date.now()) .filter(req => req.snoozedUntil > Date.now())

View File

@ -1,6 +1,4 @@
'use strict' import authService from '@/auth'
import authService from '../auth/AuthService'
export default { export default {
install (Vue) { install (Vue) {

View File

@ -1,12 +1,8 @@
'use strict'
/* eslint-disable */
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
import auth from './auth/AuthService' import auth from '@/auth'
import Home from '@/components/Home.vue' import Home from '@/components/Home.vue'
/* eslint-enable */
Vue.use(Router) Vue.use(Router)

View File

@ -1,16 +0,0 @@
'use strict'
export default {
/** Action to add a prayer request (pass request text) */
ADD_REQUEST: 'add-request',
/** Action to check if a user is authenticated, refreshing the session first if it exists */
CHECK_AUTHENTICATION: 'check-authentication',
/** Action to load the user's prayer journal */
LOAD_JOURNAL: 'load-journal',
/** Action to update a request */
UPDATE_REQUEST: 'update-request',
/** Action to skip the remaining recurrence period */
SHOW_REQUEST_NOW: 'show-request-now',
/** Action to snooze a request */
SNOOZE_REQUEST: 'snooze-request'
}

View File

@ -1,20 +1,15 @@
'use strict'
/* eslint-disable no-multi-spaces */
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex, { StoreOptions } from 'vuex'
import api from '@/api' import api from '@/api'
import auth from '@/auth/AuthService' import auth from '@/auth'
import mutations from './mutation-types' import { AppState, Actions, JournalRequest, Mutations } from './types'
import actions from './action-types'
/* eslint-enable no-multi-spaces */
Vue.use(Vuex) Vue.use(Vuex)
/* eslint-disable no-console */ /* eslint-disable no-console */
const logError = function (error) { const logError = function (error: any) { // TODO: can we do better on this type?
if (error.response) { if (error.response) {
// The request was made and the server responded with a status code // The request was made and the server responded with a status code
// that falls out of the range of 2xx // that falls out of the range of 2xx
@ -41,7 +36,7 @@ const setBearer = async function () {
await auth.getAccessToken() await auth.getAccessToken()
api.setBearer(auth.session.id.token) api.setBearer(auth.session.id.token)
} catch (err) { } catch (err) {
if (err === 'Not logged in') { if (err.message === 'Not logged in') {
console.warn('API request attempted when user was not logged in') console.warn('API request attempted when user was not logged in')
} else { } else {
console.error(err) console.error(err)
@ -54,88 +49,87 @@ const setBearer = async function () {
* Get the sort value for a prayer request * Get the sort value for a prayer request
* @param x The prayer request * @param x The prayer request
*/ */
const sortValue = x => x.showAfter === 0 ? x.asOf : x.showAfter const sortValue = (x: JournalRequest) => x.showAfter === 0 ? x.asOf : x.showAfter
/** /**
* Sort journal requests either by asOf or showAfter * Sort journal requests either by asOf or showAfter
*/ */
const journalSort = (a, b) => sortValue(a) - sortValue(b) const journalSort = (a: JournalRequest, b: JournalRequest) => sortValue(a) - sortValue(b)
const emptyJournal: any = [] /** The initial state of the store */
const store : StoreOptions<AppState> = {
export default new Vuex.Store({
state: { state: {
user: auth.session.profile, user: auth.session.profile,
isAuthenticated: auth.isAuthenticated(), isAuthenticated: auth.isAuthenticated(),
journal: emptyJournal, journal: [],
isLoadingJournal: false isLoadingJournal: false
}, },
mutations: { mutations: {
[mutations.LOADING_JOURNAL] (state, flag) { [Mutations.LoadingJournal] (state, flag: boolean) {
state.isLoadingJournal = flag state.isLoadingJournal = flag
}, },
[mutations.LOADED_JOURNAL] (state, journal) { [Mutations.LoadedJournal] (state, journal: JournalRequest[]) {
state.journal = journal.sort(journalSort) state.journal = journal.sort(journalSort)
}, },
[mutations.REQUEST_ADDED] (state, newRequest) { [Mutations.RequestAdded] (state, newRequest: JournalRequest) {
state.journal.push(newRequest) state.journal.push(newRequest)
}, },
[mutations.REQUEST_UPDATED] (state, request) { [Mutations.RequestUpdated] (state, request: JournalRequest) {
const jrnl = state.journal.filter(it => it.requestId !== request.requestId) const jrnl = state.journal.filter(it => it.requestId !== request.requestId)
if (request.lastStatus !== 'Answered') jrnl.push(request) if (request.lastStatus !== 'Answered') jrnl.push(request)
state.journal = jrnl state.journal = jrnl
}, },
[mutations.SET_AUTHENTICATION] (state, value) { [Mutations.SetAuthentication] (state, value: boolean) {
state.isAuthenticated = value state.isAuthenticated = value
}, },
[mutations.USER_LOGGED_OFF] (state) { [Mutations.UserLoggedOff] (state) {
state.user = {} state.user = {}
api.removeBearer() api.removeBearer()
state.isAuthenticated = false state.isAuthenticated = false
}, },
[mutations.USER_LOGGED_ON] (state, user) { [Mutations.UserLoggedOn] (state, user) {
state.user = user state.user = user
state.isAuthenticated = true state.isAuthenticated = true
} }
}, },
actions: { actions: {
async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) { async [Actions.AddRequest] ({ commit }, { progress, requestText, recurType, recurCount }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer() await setBearer()
const newRequest = await api.addRequest(requestText, recurType, recurCount) const newRequest = await api.addRequest(requestText, recurType, recurCount)
commit(mutations.REQUEST_ADDED, newRequest.data) commit(Mutations.RequestAdded, newRequest.data)
progress.$emit('done') progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.$emit('done') progress.$emit('done')
} }
}, },
async [actions.CHECK_AUTHENTICATION] ({ commit }) { async [Actions.CheckAuthentication] ({ commit }) {
try { try {
await auth.getAccessToken() await auth.getAccessToken()
commit(mutations.SET_AUTHENTICATION, auth.isAuthenticated()) commit(Mutations.SetAuthentication, auth.isAuthenticated())
} catch (_) { } catch (_) {
commit(mutations.SET_AUTHENTICATION, false) commit(Mutations.SetAuthentication, false)
} }
}, },
async [actions.LOAD_JOURNAL] ({ commit }, progress) { async [Actions.LoadJournal] ({ commit }, progress) {
commit(mutations.LOADED_JOURNAL, []) commit(Mutations.LoadedJournal, [])
progress.$emit('show', 'query') progress.$emit('show', 'query')
commit(mutations.LOADING_JOURNAL, true) commit(Mutations.LoadingJournal, true)
await setBearer() await setBearer()
try { try {
const jrnl = await api.journal() const jrnl = await api.journal()
commit(mutations.LOADED_JOURNAL, jrnl.data) commit(Mutations.LoadedJournal, jrnl.data)
progress.$emit('done') progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.$emit('done') progress.$emit('done')
} finally { } finally {
commit(mutations.LOADING_JOURNAL, false) commit(Mutations.LoadingJournal, false)
} }
}, },
async [actions.UPDATE_REQUEST] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) { async [Actions.UpdateRequest] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer() await setBearer()
@ -149,33 +143,33 @@ export default new Vuex.Store({
await api.updateRequest(requestId, status, oldReq.text !== updateText ? updateText : '') await api.updateRequest(requestId, status, oldReq.text !== updateText ? updateText : '')
} }
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data) commit(Mutations.RequestUpdated, request.data)
progress.$emit('done') progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.$emit('done') progress.$emit('done')
} }
}, },
async [actions.SHOW_REQUEST_NOW] ({ commit }, { progress, requestId, showAfter }) { async [Actions.ShowRequestNow] ({ commit }, { progress, requestId, showAfter }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer() await setBearer()
await api.showRequest(requestId, showAfter) await api.showRequest(requestId, showAfter)
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data) commit(Mutations.RequestUpdated, request.data)
progress.$emit('done') progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
progress.$emit('done') progress.$emit('done')
} }
}, },
async [actions.SNOOZE_REQUEST] ({ commit }, { progress, requestId, until }) { async [Actions.SnoozeRequest] ({ commit }, { progress, requestId, until }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer() await setBearer()
await api.snoozeRequest(requestId, until) await api.snoozeRequest(requestId, until)
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data) commit(Mutations.RequestUpdated, request.data)
progress.$emit('done') progress.$emit('done')
} catch (err) { } catch (err) {
logError(err) logError(err)
@ -185,4 +179,6 @@ export default new Vuex.Store({
}, },
getters: {}, getters: {},
modules: {} modules: {}
}) }
export default new Vuex.Store<AppState>(store)

View File

@ -1,18 +0,0 @@
'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 to replace a prayer request at the top of the current journal */
REQUEST_UPDATED: 'request-updated',
/** Mutation for setting the authentication state */
SET_AUTHENTICATION: 'set-authentication',
/** 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'
}

View File

@ -0,0 +1,95 @@
/** A prayer request that is part of the user's journal */
export interface JournalRequest {
/** The ID of the request (just the CUID part) */
requestId: string
/** The ID of the user to whom the request belongs */
userId: string
/** The current text of the request */
text: string
/** The last time action was taken on the request */
asOf: number
/** The last status for the request */
lastStatus: string // TODO string union?
/** The time that this request should reappear in the user's journal */
snoozedUntil: number
/** The time after which this request should reappear in the user's journal by configured recurrence */
showAfter: number
/** The type of recurrence for this request */
recurType: string // TODO Recurrence union?
/** How many of the recurrence intervals should occur between appearances in the journal */
recurCount: number
/** History entries for the request */
history: any[] // History list
/** Note entries for the request */
notes: any[] // Note list
}
/** The state of myPrayerJournal */
export interface AppState {
/** The user's profile */
user: any,
/** Whether there is a user signed in */
isAuthenticated: boolean,
/** The current set of prayer requests */
journal: JournalRequest[],
/** Whether the journal is currently being loaded */
isLoadingJournal: boolean
}
const actions = {
/** Action to add a prayer request (pass request text) */
AddRequest: 'add-request',
/** Action to check if a user is authenticated, refreshing the session first if it exists */
CheckAuthentication: 'check-authentication',
/** Action to load the user's prayer journal */
LoadJournal: 'load-journal',
/** Action to update a request */
UpdateRequest: 'update-request',
/** Action to skip the remaining recurrence period */
ShowRequestNow: 'show-request-now',
/** Action to snooze a request */
SnoozeRequest: 'snooze-request'
}
export { actions as Actions }
const mutations = {
/** Mutation for when the user's prayer journal is being loaded */
LoadingJournal: 'loading-journal',
/** Mutation for when the user's prayer journal has been loaded */
LoadedJournal: 'journal-loaded',
/** Mutation for adding a new prayer request (pass text) */
RequestAdded: 'request-added',
/** Mutation to replace a prayer request at the top of the current journal */
RequestUpdated: 'request-updated',
/** Mutation for setting the authentication state */
SetAuthentication: 'set-authentication',
/** Mutation for logging a user off */
UserLoggedOff: 'user-logged-off',
/** Mutation for logging a user on (pass user) */
UserLoggedOn: 'user-logged-on'
}
export { mutations as Mutations }