Added Notes feature (#8)

Also:
- Moved buttons to top of request
- Tweaked layout of full request view
- Added code to ensure that users may only add history and notes to
their own requests; security FTW!
This commit is contained in:
Daniel J. Summers
2017-10-09 21:39:40 -05:00
parent b6d72d691b
commit 69811cbf54
10 changed files with 275 additions and 48 deletions

View File

@@ -46,6 +46,17 @@ const ddl = [
PRIMARY KEY ("requestId", "asOf"));
COMMENT ON TABLE mpj.history IS 'Request update history'`
},
{
name: 'note Table',
check: tableSql('note'),
fix: `
CREATE TABLE mpj.note (
"requestId" varchar(25) NOT NULL REFERENCES mpj.request,
"asOf" bigint NOT NULL,
"notes" text NOT NULL,
PRIMARY KEY ("requestId", "asOf"));
COMMENT ON TABLE mpj.note IS 'Notes regarding a request'`
},
{
name: 'request.userId Index',
check: indexSql('request', 'idx_request_userId'),

View File

@@ -18,21 +18,41 @@ const requestNotFound = {
}
export default function (pool) {
/**
* Retrieve basic information about a single request
* @param {string} requestId The Id of the request to retrieve
* @param {string} userId The Id of the user to whom the request belongs
*/
let retrieveRequest = (requestId, userId) =>
pool.query(`
SELECT "requestId", "enteredOn"
FROM mpj.request
WHERE "requestId" = $1
AND "userId" = $2`,
[ requestId, userId ])
return {
/**
* Add a history entry for this request
* @param {string} requestId The Id of the request
* @param {string} userId The Id of the user to whom this request belongs
* @param {string} requestId The Id of the request to which the update applies
* @param {string} status The status for this history entry
* @param {string} updateText The updated text for the request (pass blank if no update)
* @return {number} 404 if the request is not found or does not belong to the given user, 204 if successful
*/
addHistory: async (requestId, status, updateText) => {
const asOf = Date.now()
addHistory: async (userId, requestId, status, updateText) => {
const req = retrieveRequest(requestId, userId)
if (req.rowCount === 0) {
return 404
}
await pool.query(`
INSERT INTO mpj.history
("requestId", "asOf", "status", "text")
VALUES
($1, $2, $3, NULLIF($4, ''))`,
[ requestId, asOf, status, updateText ])
[ requestId, Date.now(), status, updateText ])
return 204
},
/**
@@ -68,6 +88,27 @@ export default function (pool) {
})
},
/**
* Add a note about a prayer request
* @param {string} userId The Id of the user to whom the request belongs
* @param {string} requestId The Id of the request to which the note applies
* @param {string} note The notes to add
* @return {number} 404 if the request is not found or does not belong to the given user, 204 if successful
*/
addNote: async (userId, requestId, note) => {
const req = retrieveRequest(requestId, userId)
if (req.rowCount === 0) {
return 404
}
await pool.query(`
INSERT INTO mpj.note
("requestId", "asOf", "notes")
VALUES
($1, $2, $3)`,
[ requestId, Date.now(), note ])
return 204
},
/**
* Get all answered requests with their text as of the "Answered" status
* @param {string} userId The Id of the user for whom requests should be retrieved
@@ -101,12 +142,7 @@ export default function (pool) {
* @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 ])
const reqResults = await retrieveRequest(requestId, userId)
if (0 === reqResults.rowCount) {
return requestNotFound
}
@@ -126,7 +162,27 @@ export default function (pool) {
* @param {string} userId The Id of the user
* @return The requests that make up the current journal
*/
journal: async userId => (await pool.query(`${journalSql} ORDER BY "asOf"`, [ userId ])).rows
journal: async userId => (await pool.query(`${journalSql} ORDER BY "asOf"`, [ userId ])).rows,
/**
* Get the notes for a request, most recent first
* @param {string} userId The Id of the user to whom the request belongs
* @param {string} requestId The Id of the request whose notes should be retrieved
* @return The notes for the request
*/
notesById: async (userId, requestId) => {
const reqResults = await retrieveRequest(requestId, userId)
if (0 === reqResults.rowCount) {
return requestNotFound
}
const notes = await pool.query(`
SELECT "asOf", "notes"
FROM mpj.note
WHERE "requestId" = $1
ORDER BY "asOf" DESC`,
[ requestId ])
return notes.rows
}
}
}

View File

@@ -16,8 +16,13 @@ export default function (checkJwt) {
// Add a request history entry (prayed, updated, answered, etc.)
.post('/:id/history', checkJwt, async (ctx, next) => {
const body = ctx.request.body
await db.request.addHistory(ctx.params.id, body.status, body.updateText)
ctx.response.status = 204
ctx.response.status = await db.request.addHistory(ctx.state.user.sub, ctx.params.id, body.status, body.updateText)
await next()
})
// Add a note to a request
.post('/:id/note', checkJwt, async (ctx, next) => {
const body = ctx.request.body
ctx.response.status = await db.request.addNote(ctx.state.user.sub, ctx.params.id, body.notes)
await next()
})
// Get a journal-style request by its Id
@@ -40,6 +45,17 @@ export default function (checkJwt) {
}
await next()
})
// Get the notes for a request
.get('/:id/notes', checkJwt, async (ctx, next) => {
const notes = await db.request.notesById(ctx.state.user.sub, ctx.params.id)
if (notes.text && 'Not Found' === notes.text) {
ctx.response.status = 404
} else {
ctx.body = notes
ctx.response.status = 200
}
await next()
})
.get('/answered', checkJwt, async (ctx, next) => {
ctx.body = await db.request.answered(ctx.state.user.sub)
ctx.response.status = 200