Remove JavaScript API files
This commit is contained in:
parent
9de713fc6a
commit
d57e2e863a
@ -1,35 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const chalk = require('chalk')
|
||||
|
||||
const { env } = require('./appsettings.json') // process.env.NODE_ENV || 'dev'
|
||||
|
||||
if ('dev' === env) require('babel-register')
|
||||
const src = (env === 'dev') ? './src' : './build'
|
||||
|
||||
const app = require(`${src}/index`).default
|
||||
const db = require(`${src}/db`).default
|
||||
|
||||
const fullEnv = ('dev' === env) ? 'Development' : 'Production'
|
||||
|
||||
const { port } = require('./appsettings.json')
|
||||
|
||||
/**
|
||||
* 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 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')
|
||||
})
|
@ -1,12 +0,0 @@
|
||||
'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))
|
@ -1,45 +0,0 @@
|
||||
{
|
||||
"name": "my-prayer-journal-api",
|
||||
"private": true,
|
||||
"version": "0.9.3",
|
||||
"description": "Server API for myPrayerJournal",
|
||||
"main": "index.js",
|
||||
"author": "Daniel J. Summers <daniel@bitbadger.solutions>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^2.1.0",
|
||||
"cuid": "^1.3.8",
|
||||
"jwks-rsa-koa": "^1.1.3",
|
||||
"koa": "^2.3.0",
|
||||
"koa-bodyparser": "^4.2.0",
|
||||
"koa-jwt": "^3.2.2",
|
||||
"koa-router": "^7.2.1",
|
||||
"koa-send": "^4.1.0",
|
||||
"koa-static": "^4.0.1",
|
||||
"pg": "^7.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"build": "babel src -d build",
|
||||
"vue": "cd ../app && node build/build.js prod && cd ../api && node app.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^6.23.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-register": "^6.26.0",
|
||||
"koa-morgan": "^1.0.1"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import { Pool } from 'pg'
|
||||
|
||||
/**
|
||||
* SQL to check the existence of a table in the mpj schema
|
||||
* @param {string} table The name of the table whose existence should be checked
|
||||
*/
|
||||
const tableSql = table => `SELECT 1 FROM pg_tables WHERE schemaname='mpj' AND tablename='${table}'`
|
||||
|
||||
/**
|
||||
* SQL to determine if an index exists
|
||||
* @param {string} table The name of the table which the given index indexes
|
||||
* @param {string} index The name of the index
|
||||
*/
|
||||
const indexSql = (table, index) =>
|
||||
`SELECT 1 FROM pg_indexes WHERE schemaname='mpj' AND tablename='${table}' AND indexname='${index}'`
|
||||
|
||||
const ddl = [
|
||||
{
|
||||
name: 'myPrayerJournal Schema',
|
||||
check: `SELECT 1 FROM pg_namespace WHERE nspname='mpj'`,
|
||||
fix: `
|
||||
CREATE SCHEMA mpj;
|
||||
COMMENT ON SCHEMA mpj IS 'myPrayerJournal data'`
|
||||
},
|
||||
{
|
||||
name: 'request Table',
|
||||
check: tableSql('request'),
|
||||
fix: `
|
||||
CREATE TABLE mpj.request (
|
||||
"requestId" varchar(25) PRIMARY KEY,
|
||||
"enteredOn" bigint NOT NULL,
|
||||
"userId" varchar(100) NOT NULL);
|
||||
COMMENT ON TABLE mpj.request IS 'Requests'`
|
||||
},
|
||||
{
|
||||
name: 'history Table',
|
||||
check: tableSql('history'),
|
||||
fix: `
|
||||
CREATE TABLE mpj.history (
|
||||
"requestId" varchar(25) NOT NULL REFERENCES mpj.request,
|
||||
"asOf" bigint NOT NULL,
|
||||
"status" varchar(25),
|
||||
"text" text,
|
||||
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'),
|
||||
fix: `
|
||||
CREATE INDEX "idx_request_userId" ON mpj.request ("userId");
|
||||
COMMENT ON INDEX "idx_request_userId" IS 'Requests are retrieved by user'`
|
||||
},
|
||||
{
|
||||
name: 'journal View',
|
||||
check: `SELECT 1 FROM pg_views WHERE schemaname='mpj' AND viewname='journal'`,
|
||||
fix: `
|
||||
CREATE VIEW mpj.journal AS
|
||||
SELECT
|
||||
request."requestId",
|
||||
request."userId",
|
||||
(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",
|
||||
(SELECT "status"
|
||||
FROM mpj.history
|
||||
WHERE history."requestId" = request."requestId"
|
||||
ORDER BY "asOf" DESC
|
||||
LIMIT 1) AS "lastStatus"
|
||||
FROM mpj.request;
|
||||
COMMENT ON VIEW mpj.journal IS 'Requests with latest text'`
|
||||
}
|
||||
]
|
||||
|
||||
export default function (query) {
|
||||
return {
|
||||
/**
|
||||
* Ensure that the database schema, tables, and indexes exist
|
||||
*/
|
||||
ensureDatabase: async () => {
|
||||
for (let item of ddl) {
|
||||
const result = await query(item.check, [])
|
||||
if (1 > result.rowCount) await query(item.fix, [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import { Pool, types } from 'pg'
|
||||
|
||||
import appConfig from '../../appsettings.json'
|
||||
import ddl from './ddl'
|
||||
import request from './request'
|
||||
|
||||
/** Pooled PostgreSQL instance */
|
||||
const pool = new Pool(appConfig.pgPool)
|
||||
|
||||
// Return "bigint" (int8) instances as number instead of strings
|
||||
// ref: https://github.com/brianc/node-pg-types
|
||||
types.setTypeParser(20, val => parseInt(val))
|
||||
|
||||
/**
|
||||
* Run a SQL query
|
||||
* @param {string} text The SQL command
|
||||
* @param {*[]} params The parameters for the query
|
||||
*/
|
||||
const query = (text, params) => pool.query(text, params)
|
||||
|
||||
export default {
|
||||
query: query,
|
||||
request: request(pool),
|
||||
verify: ddl(query).ensureDatabase
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import { Pool } from 'pg'
|
||||
import cuid from 'cuid'
|
||||
|
||||
const currentRequestSql = `
|
||||
SELECT "requestId", "text", "asOf", "lastStatus"
|
||||
FROM mpj.journal`
|
||||
|
||||
const journalSql = `${currentRequestSql}
|
||||
WHERE "userId" = $1
|
||||
AND "lastStatus" <> 'Answered'`
|
||||
|
||||
const requestNotFound = {
|
||||
requestId: '',
|
||||
text: 'Not Found',
|
||||
asOf: 0
|
||||
}
|
||||
|
||||
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} 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 (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, Date.now(), status, updateText ])
|
||||
return 204
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new prayer request
|
||||
* @param {string} userId The Id of the user
|
||||
* @param {string} requestText The text of the request
|
||||
* @return The created request
|
||||
*/
|
||||
addNew: async (userId, requestText) => {
|
||||
const id = cuid()
|
||||
const enteredOn = Date.now()
|
||||
return (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, lastStatus: 'Created' }
|
||||
})().catch(e => {
|
||||
console.error(e.stack)
|
||||
return { requestId: '', text: 'error', asOf: 0, lastStatus: 'Errored' }
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return All requests
|
||||
*/
|
||||
answered: async (userId) =>
|
||||
(await pool.query(`${currentRequestSql}
|
||||
WHERE "userId" = $1
|
||||
AND "lastStatus" = 'Answered'
|
||||
ORDER BY "asOf" DESC`,
|
||||
[ userId ])).rows,
|
||||
|
||||
/**
|
||||
* Get the "current" version of a request by its Id
|
||||
* @param {string} requestId The Id of the request to retrieve
|
||||
* @param {string} userId The Id of the user to which the request belongs
|
||||
* @return The request, or a request-like object indicating that the request was not found
|
||||
*/
|
||||
byId: async (userId, requestId) => {
|
||||
const reqs = await pool.query(`${currentRequestSql}
|
||||
WHERE "requestId" = $1
|
||||
AND "userId" = $2`,
|
||||
[ requestId, userId ])
|
||||
return (0 < reqs.rowCount) ? reqs.rows[0] : requestNotFound
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a prayer request, including its full history, by its Id
|
||||
* @param {string} userId The Id of the user to which the request belongs
|
||||
* @param {string} requestId The Id of the request to retrieve
|
||||
* @return The request, or a request-like object indicating that the request was not found
|
||||
*/
|
||||
fullById: async (userId, requestId) => {
|
||||
const reqResults = await retrieveRequest(requestId, userId)
|
||||
if (0 === reqResults.rowCount) {
|
||||
return requestNotFound
|
||||
}
|
||||
const req = reqResults.rows[0]
|
||||
const history = await pool.query(`
|
||||
SELECT "asOf", "status", COALESCE("text", '') AS "text"
|
||||
FROM mpj.history
|
||||
WHERE "requestId" = $1
|
||||
ORDER BY "asOf"`,
|
||||
[ requestId ])
|
||||
req.history = history.rows
|
||||
return req
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current requests for a user (i.e., their complete current journal)
|
||||
* @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,
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
'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'
|
||||
|
||||
import appConfig from '../appsettings.json'
|
||||
import router from './routes'
|
||||
|
||||
/** Koa app */
|
||||
const app = new Koa()
|
||||
|
||||
if (appConfig.env === 'dev') app.use(morgan('dev'))
|
||||
|
||||
export default app
|
||||
// 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())
|
||||
// Send the index.html file for what would normally get a 404
|
||||
.use(async (ctx, next) => {
|
||||
if (ctx.url.indexOf('/api') === -1) {
|
||||
try {
|
||||
await send(ctx, 'index.html', { root: __dirname + '/../public/' })
|
||||
}
|
||||
catch (err) {
|
||||
return await next(err)
|
||||
}
|
||||
}
|
||||
return await next()
|
||||
})
|
@ -1,39 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import jwt from 'koa-jwt'
|
||||
import jwksRsa from 'jwks-rsa-koa'
|
||||
import Router from 'koa-router'
|
||||
|
||||
import appConfig from '../../appsettings.json'
|
||||
import journal from './journal'
|
||||
import request from './request'
|
||||
|
||||
/** Authentication middleware to verify the access token against the Auth0 JSON Web Key Set */
|
||||
const checkJwt = jwt({
|
||||
// Dynamically provide a signing key
|
||||
// based on the kid in the header and
|
||||
// the singing keys provided by the JWKS endpoint.
|
||||
secret: jwksRsa.koaJwt2Key({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 5,
|
||||
jwksUri: `https://${appConfig.auth0.domain}/.well-known/jwks.json`
|
||||
}),
|
||||
|
||||
// Validate the audience and the issuer.
|
||||
audience: appConfig.auth0.clientId,
|
||||
issuer: `https://${appConfig.auth0.domain}/`,
|
||||
algorithms: ['RS256']
|
||||
})
|
||||
|
||||
/** /api/journal routes */
|
||||
const journalRoutes = journal(checkJwt)
|
||||
/** /api/request routes */
|
||||
const requestRoutes = request(checkJwt)
|
||||
|
||||
/** Combined router */
|
||||
const router = new Router({ prefix: '/api' })
|
||||
router.use('/journal', journalRoutes.routes(), journalRoutes.allowedMethods())
|
||||
router.use('/request', requestRoutes.routes(), requestRoutes.allowedMethods())
|
||||
|
||||
export default router
|
@ -1,16 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import Router from 'koa-router'
|
||||
import db from '../db'
|
||||
|
||||
const router = new Router()
|
||||
|
||||
export default function (checkJwt) {
|
||||
|
||||
router.get('/', checkJwt, async (ctx, next) => {
|
||||
const reqs = await db.request.journal(ctx.state.user.sub)
|
||||
ctx.body = reqs
|
||||
return await next()
|
||||
})
|
||||
return router
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
import Router from 'koa-router'
|
||||
import db from '../db'
|
||||
|
||||
const router = new Router()
|
||||
|
||||
export default function (checkJwt) {
|
||||
|
||||
router
|
||||
// Add a new request
|
||||
.post('/', checkJwt, async (ctx, next) => {
|
||||
ctx.body = await db.request.addNew(ctx.state.user.sub, ctx.request.body.requestText)
|
||||
await next()
|
||||
})
|
||||
// Add a request history entry (prayed, updated, answered, etc.)
|
||||
.post('/:id/history', checkJwt, async (ctx, next) => {
|
||||
const body = ctx.request.body
|
||||
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
|
||||
.get('/:id', checkJwt, async (ctx, next) => {
|
||||
const req = await db.request.byId(ctx.state.user.sub, ctx.params.id)
|
||||
if ('Not Found' === req.text) {
|
||||
ctx.response.status = 404
|
||||
} else {
|
||||
ctx.body = req
|
||||
}
|
||||
await next()
|
||||
})
|
||||
// Get a request, along with its full history
|
||||
.get('/:id/full', checkJwt, async (ctx, next) => {
|
||||
const req = await db.request.fullById(ctx.state.user.sub, ctx.params.id)
|
||||
if ('Not Found' === req.text) {
|
||||
ctx.response.status = 404
|
||||
} else {
|
||||
ctx.body = req
|
||||
}
|
||||
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 a complete request; equivalent to full + notes
|
||||
.get('/:id/complete', checkJwt, async (ctx, next) => {
|
||||
const req = await db.request.fullById(ctx.state.user.sub, ctx.params.id)
|
||||
if ('Not Found' === req.text) {
|
||||
ctx.response.status = 404
|
||||
} else {
|
||||
ctx.response.status = 200
|
||||
req.notes = await db.request.notesById(ctx.state.user.sub, ctx.params.id)
|
||||
ctx.body = req
|
||||
}
|
||||
await next()
|
||||
})
|
||||
// Get all answered requests
|
||||
.get('/answered', checkJwt, async (ctx, next) => {
|
||||
ctx.body = await db.request.answered(ctx.state.user.sub)
|
||||
ctx.response.status = 200
|
||||
await next()
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
2312
src/api/yarn.lock
2312
src/api/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user