Create tables/indexes on startup
Ready to create a request
This commit is contained in:
parent
fc02112295
commit
270ac45e8c
|
@ -1,87 +0,0 @@
|
|||
namespace MyPrayerJournal.Migrations
|
||||
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
open Microsoft.EntityFrameworkCore
|
||||
open Microsoft.EntityFrameworkCore.Infrastructure
|
||||
open Microsoft.EntityFrameworkCore.Metadata
|
||||
open Microsoft.EntityFrameworkCore.Migrations
|
||||
open Microsoft.EntityFrameworkCore.Migrations.Operations
|
||||
open Microsoft.EntityFrameworkCore.Migrations.Operations.Builders
|
||||
open MyPrayerJournal
|
||||
|
||||
type RequestTable = {
|
||||
RequestId : OperationBuilder<AddColumnOperation>
|
||||
EnteredOn : OperationBuilder<AddColumnOperation>
|
||||
UserId : OperationBuilder<AddColumnOperation>
|
||||
}
|
||||
|
||||
type HistoryTable = {
|
||||
RequestId : OperationBuilder<AddColumnOperation>
|
||||
AsOf : OperationBuilder<AddColumnOperation>
|
||||
Status : OperationBuilder<AddColumnOperation>
|
||||
Text : OperationBuilder<AddColumnOperation>
|
||||
}
|
||||
|
||||
[<DbContext (typeof<DataContext>)>]
|
||||
[<Migration "20170104023341_InitialDb">]
|
||||
type InitialDb () =
|
||||
inherit Migration ()
|
||||
|
||||
override this.Up migrationBuilder =
|
||||
migrationBuilder.EnsureSchema(
|
||||
name = "mpj")
|
||||
|> ignore
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name = "Request",
|
||||
schema = "mpj",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ RequestId = table.Column<Guid>(nullable = false)
|
||||
EnteredOn = table.Column<int64>(nullable = false)
|
||||
UserId = table.Column<string>(nullable = false)
|
||||
}
|
||||
),
|
||||
constraints =
|
||||
fun table ->
|
||||
table.PrimaryKey("PK_Request", fun x -> x.RequestId :> obj) |> ignore
|
||||
)
|
||||
|> ignore
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name = "History",
|
||||
schema = "mpj",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ RequestId = table.Column<Guid>(nullable = false)
|
||||
AsOf = table.Column<int64>(nullable = false)
|
||||
Status = table.Column<string>(nullable = true)
|
||||
Text = table.Column<string>(nullable = true)
|
||||
}
|
||||
),
|
||||
constraints =
|
||||
fun table ->
|
||||
table.PrimaryKey("PK_History", fun x -> (x.RequestId, x.AsOf) :> obj)
|
||||
|> ignore
|
||||
table.ForeignKey(
|
||||
name = "FK_History_Request_RequestId",
|
||||
column = (fun x -> x.RequestId :> obj),
|
||||
principalSchema = "mpj",
|
||||
principalTable = "Request",
|
||||
principalColumn = "RequestId",
|
||||
onDelete = ReferentialAction.Cascade)
|
||||
|> ignore
|
||||
)
|
||||
|> ignore
|
||||
|
||||
override this.Down migrationBuilder =
|
||||
migrationBuilder.DropTable(
|
||||
name = "History",
|
||||
schema = "mpj")
|
||||
|> ignore
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name = "Request",
|
||||
schema = "mpj")
|
||||
|> ignore
|
70
src/api/db/ddl.js
Normal file
70
src/api/db/ddl.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
'use strict'
|
||||
|
||||
const { Pool } = require('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: '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'`
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = 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,9 +1,9 @@
|
|||
'use strict'
|
||||
|
||||
const { Pool } = require('pg')
|
||||
const config = require('../appsettings.json')
|
||||
|
||||
const pool = new Pool(config.pgPool)
|
||||
/** Pooled PostgreSQL instance */
|
||||
const pool = new Pool(require('../appsettings.json').pgPool)
|
||||
|
||||
/**
|
||||
* Run a SQL query
|
||||
|
@ -14,5 +14,6 @@ const query = (text, params) => pool.query(text, params)
|
|||
|
||||
module.exports = {
|
||||
query: query,
|
||||
request: require('./request')(query)
|
||||
request: require('./request')(query),
|
||||
verify: require('./ddl')(query).ensureDatabase
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@ module.exports = 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 query('SELECT "requestId" FROM request WHERE "userId" = $1', [userId])).rows
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,34 +3,11 @@
|
|||
const express = require('express')
|
||||
|
||||
/** Configuration for the application */
|
||||
const config = require('./appsettings.json')
|
||||
const appConfig = require('./appsettings.json')
|
||||
|
||||
/** Express app */
|
||||
const app = express()
|
||||
|
||||
const jwt = require('express-jwt')
|
||||
const jwksRsa = require('jwks-rsa')
|
||||
|
||||
// Authentication middleware. When used, the
|
||||
// access token must exist and be verified 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.expressJwtSecret({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 5,
|
||||
jwksUri: `https://${config.auth0.domain}/.well-known/jwks.json`
|
||||
}),
|
||||
|
||||
// Validate the audience and the issuer.
|
||||
audience: config.auth0.clientId,
|
||||
issuer: `https://${config.auth0.domain}/`,
|
||||
algorithms: ['RS256']
|
||||
})
|
||||
|
||||
// Serve the Vue files from /public
|
||||
app.use(express.static('public'))
|
||||
|
||||
|
@ -38,23 +15,21 @@ app.use(express.static('public'))
|
|||
app.use(require('morgan')('dev'))
|
||||
|
||||
// Tie in all the API routes
|
||||
require('./routes').mount(app, checkJwt)
|
||||
require('./routes').mount(app)
|
||||
|
||||
// Send the index.html file for what would normally get a 404
|
||||
app.use(async (req, res, next) => {
|
||||
const options = {
|
||||
root: __dirname + '/public/',
|
||||
dotfiles: 'deny'
|
||||
}
|
||||
try {
|
||||
await res.sendFile('index.html', options)
|
||||
await res.sendFile('index.html', { root: __dirname + '/public/', dotfiles: 'deny' })
|
||||
}
|
||||
catch (err) {
|
||||
return next(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Start it up!
|
||||
app.listen(config.port, () => {
|
||||
console.log(`Listening on port ${config.port}`)
|
||||
})
|
||||
// Ensure the database exists...
|
||||
require('./db').verify().then(() =>
|
||||
// ...and start it up!
|
||||
app.listen(appConfig.port, () => {
|
||||
console.log(`Listening on port ${appConfig.port}`)
|
||||
}))
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"author": "Daniel J. Summers <daniel@djs-consulting.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cuid": "^1.3.8",
|
||||
"express": "^4.15.4",
|
||||
"express-jwt": "^5.3.0",
|
||||
"express-promise-router": "^2.0.0",
|
||||
|
|
|
@ -1,7 +1,31 @@
|
|||
'use strict'
|
||||
|
||||
const jwt = require('express-jwt')
|
||||
const jwksRsa = require('jwks-rsa')
|
||||
const config = require('../appsettings.json')
|
||||
|
||||
// Authentication middleware. When used, the
|
||||
// access token must exist and be verified 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.expressJwtSecret({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 5,
|
||||
jwksUri: `https://${config.auth0.domain}/.well-known/jwks.json`
|
||||
}),
|
||||
|
||||
// Validate the audience and the issuer.
|
||||
audience: config.auth0.clientId,
|
||||
issuer: `https://${config.auth0.domain}/`,
|
||||
algorithms: ['RS256']
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
mount: (app, checkJwt) => {
|
||||
mount: app => {
|
||||
app.use('/api/journal', require('./journal')(checkJwt))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,10 @@ boom@2.x.x:
|
|||
dependencies:
|
||||
hoek "2.x.x"
|
||||
|
||||
browser-fingerprint@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/browser-fingerprint/-/browser-fingerprint-0.0.1.tgz#8df3cdca25bf7d5b3542d61545d730053fce604a"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
|
@ -147,6 +151,10 @@ cookie@0.3.1:
|
|||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
|
||||
core-js@^1.1.1:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
|
||||
core-util-is@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
@ -157,6 +165,14 @@ cryptiles@2.x.x:
|
|||
dependencies:
|
||||
boom "2.x.x"
|
||||
|
||||
cuid@^1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/cuid/-/cuid-1.3.8.tgz#4b875e0969bad764f7ec0706cf44f5fb0831f6b7"
|
||||
dependencies:
|
||||
browser-fingerprint "0.0.1"
|
||||
core-js "^1.1.1"
|
||||
node-fingerprint "0.0.2"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
|
@ -545,6 +561,10 @@ negotiator@0.6.1:
|
|||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
|
||||
node-fingerprint@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/node-fingerprint/-/node-fingerprint-0.0.2.tgz#31cbabeb71a67ae7dd5a7dc042e51c3c75868501"
|
||||
|
||||
oauth-sign@~0.8.1:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
|
Loading…
Reference in New Issue
Block a user