Skip to content

Commit

Permalink
Merge pull request #41 from coderbunker/issue40-internal-schema
Browse files Browse the repository at this point in the history
issue #40: create internal schema
  • Loading branch information
rngadam authored Jan 11, 2020
2 parents 2b95c0b + 0c21bbb commit f37e0fb
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 110 deletions.
1 change: 1 addition & 0 deletions .env-template
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ PGPASSWORD=
PGDATABASE=timesheet
PGPORT=5432
PORT=3000
TESTAPIKEY=secret
File renamed without changes.
61 changes: 61 additions & 0 deletions HEROKU.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Heroku Deployment

## Manage Domain

### CNAME Setup for Heroku app

1. Get CNAME from heroku: `heroku domains -a coderbunker-timesheet`

2. add CNAME to google domains

| NAME | TYPE | TTL | DATA |
|--------|:--------------:|------:|--------------------------------------:|
| data | CNAME | 1h | data.coderbunker.com.herokudns.com. |


### SSL Setup

Enable SSL automatically managed by heroku.

## troubleshooting

want to push an amended history with subtree push? sadly, does not support push.

create a local branch and force push that first:

```
git subtree split --prefix server -b backup-branch
git push -f heroku backup-branch:master
```

should now be back to normal...

## deploying to Heroku

Creating/updating schema on Heroku instance:

```bash
psql -v "ON_ERROR_STOP=1" -b -1 -e -f sql/PSQL.sql `heroku pg:credentials:url | tail -1`
```

Restarting the dyno (to load changes to the database for example)

```bash
heroku restart -a coderbunker-timesheet
```

## data transfer to/from heroku database

Pushing the local database:

```bash
heroku pg:push timesheet postgresql-rigid-65921 --app coderbunker-timesheet
```

Pulling the Heroku database locally and making a copy before changing the pulled version
(adjust date):

```bash
heroku pg:pull postgresql-rigid-65921 heroku-timesheet --app coderbunker-timesheet
psql -c 'CREATE DATABASE "heroku-timesheet-20180416" TEMPLATE "heroku-timesheet";' postgres
```
30 changes: 6 additions & 24 deletions INSTALL.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Integration with Google Spreadsheet
# Running the system locally

## setup NodeJS latest LTS

Expand All @@ -12,7 +12,7 @@ nvm install --lts

```bash
npm install
node app.js
npm start
```

install PostgreSQL extensions:
Expand All @@ -21,6 +21,7 @@ install PostgreSQL extensions:
pip install pgxnclient
pgxn install pgtap
```

create schema of DB

```bash
Expand All @@ -37,28 +38,9 @@ brew install fswatch
./watch-test.sh timesheet
```

## Apache hook

Apache config:

```text
<Location "/spreadsheet">
ProxyPass "http://localhost:3000/spreadsheet"
</Location>
```

restart:

```bash
systemctl restart apache2
```

## Setup

creates two routes:
## Exposing local as a web service

* /spreadsheet/snapshot/SPREADSHEET_ID
* /spreadsheet/change/SPREADSHEET_ID
Use http://ngrok.io

## Testing

Expand All @@ -71,7 +53,7 @@ Sample file:
"apptype": "Spreadsheet",
"category": "Timesheet"
},
"apikey": "f98dbe87-5749-47c6-8e39-47ae7ff401ac"
"apikey": "STRING_CONFIGURED_IN_ENV"
}
```

Expand Down
140 changes: 114 additions & 26 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,130 @@
require('dotenv').config()

const express = require('express')
const mountRoutes = require('./routes')
const Router = require('express-promise-router')
const bodyParser = require('body-parser')
const { postgraphile } = require("postgraphile");
const cookieParser = require('cookie-parser');
const { Pool } = require('pg')

const app = express()
function createQuery(dburl) {
console.log('connecting to DATABASE_URL=%s', dburl);

app.use(postgraphile(
process.env.DATABASE_URL || "postgres://localhost/heroku-timesheet",
"postgraphql",
{
const pool = new Pool({
connectionString: dburl
});

return function(text, params) {
pool.query(text, params);
}
}

function postgraphileDefaultConfig(config) {
config = config || {};
return Object.assign({
dynamicJson: true,
disableDefaultMutations: true,
disableDefaultMutations: false,
graphiql: true,
watchPg: true,
enableCors: true
enableCors: true,
extendedErrors: ['hint', 'detail', 'errcode']
}, config);
}

function validateApiKey(apikey) {
return function(req, res, next) {
function err(e) {
res.writeHead(400);
res.end(JSON.stringify({error: e}));
}
if(req.originalUrl.match(/internal/)) {
if(!((req.body && req.body.apikey === apikey) || req.cookies.apikey === apikey)) {
return err(`invalid api key for ${req.originalUrl}`);
}
}
next();
}
));
}

// create a new express-promise-router
// this has the same API as the normal express router except
// it allows you to use async functions as route handlers
function createRouter(router, query) {
router.post('/snapshot', async (req, res) => {
function err(e) {
res.writeHead(400);
res.end(JSON.stringify({error: e}));
}

if(!req.body.id) {
return err("id is not defined in body")
}
if(!req.body.doc) {
return err("doc is not defined in body")
}
const { rows, fields } = await query(
`SELECT api.snapshot_json($1, $2::json)`,
[
req.body.id,
JSON.stringify(req.body.doc)
])
var json = JSON.stringify(rows[0].snapshot_json);
res.writeHead(200, {'content-type':'application/json', 'content-length': Buffer.byteLength(json)});
res.end(json);
});

return router;
}

function registerEndpoint(app, schema, endpoints) {
if(!endpoints) {
endpoints = {
graphqlRoute: `/${schema}/graphql`,
graphiqlRoute: `/${schema}/graphiql`
}
}

app.use(postgraphile(
process.env.DATABASE_URL || "postgres://localhost/heroku-timesheet",
schema,
postgraphileDefaultConfig(endpoints)
));
}

function createServer(app, router, mountPoint, apikey, port) {
app.use(mountPoint, router);
app.use(cookieParser());
app.use(validateApiKey(apikey));

registerEndpoint(app, 'postgraphql', {});
registerEndpoint(app, 'internal');

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({
extended: false
}))

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({
extended: false
}))
// parse application/json
app.use(bodyParser.json({
limit: "5mb"
}))

// parse application/json
app.use(bodyParser.json({
limit: "5mb"
}))
app.use(express.static(__dirname + '/public'));

app.use(express.static(__dirname + '/public'));
var server = app.listen(port, function() {
console.log(JSON.stringify(server.address()))
const host = server.address().address;
const port = server.address().port;
console.log('timesheet app listening at http://%s:%s', host, port);
});

mountRoutes(app)
return server;
}

console.log('port: %s', process.env.PORT)
var server = app.listen(process.env.PORT, () => {
console.log(JSON.stringify(server.address()))
const host = server.address().address;
const port = server.address().port;
console.log('timesheet app listening at http://%s:%s', host, port);
});
if (require.main === module) {
const query = createQuery(process.env.DATABASE_URL);
const router = createRouter(new Router(), query);
const server = createServer(express(), router, '/gsuite', process.env.APIKEY, process.env.PORT);
} else {
module.exports = { createServer, createRouter, createQuery };
}
10 changes: 0 additions & 10 deletions db/index.js

This file was deleted.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "First version of the timesheet backend",
"main": "index.js",
"scripts": {
"start": "node app.js"
"start": "node app.js",
"test": "mocha"
},
"repository": {
"type": "git",
Expand All @@ -17,12 +18,19 @@
},
"homepage": "https://github.com/coderbunker/timesheet-backend#readme",
"dependencies": {
"base64url": "^3.0.0",
"body-parser": "^1.18.2",
"buffer-equal-constant-time": "^1.0.1",
"cookie-parser": "^1.4.3",
"dotenv": "^5.0.0",
"express": "^4.16.2",
"express-promise-router": "^3.0.1",
"find-free-port": "^2.0.0",
"googleapis": "^26.0.1",
"pg": "^7.4.1",
"postgraphile": "^4.0.0-beta.2"
"postgraphile": "^4.0.0-rc.3"
},
"devDependencies": {
"isomorphic-fetch": "^2.2.1"
}
}
5 changes: 0 additions & 5 deletions routes/index.js

This file was deleted.

37 changes: 0 additions & 37 deletions routes/snapshot.js

This file was deleted.

1 change: 1 addition & 0 deletions sql/010-psql-create-incoming.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
\ir incoming/profile.sql
\ir incoming/entry.sql
\ir incoming/people.sql
\ir incoming/group.sql
\ir incoming/transfer.sql
\ir incoming/waveapps.sql

Expand Down
4 changes: 4 additions & 0 deletions sql/070-psql-create-internal.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

-- internal interface
\ir internal/schema.sql
\ir internal/group.sql
1 change: 1 addition & 0 deletions sql/PSQL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
\ir 030-psql-incoming-to-model.sql
\ir 040-psql-create-reports.sql
\ir 050-psql-create-postgraphql.sql
\ir 070-psql-create-internal.sql
-- \ir 900-psql-testsuite.sql

\echo "Success. Please don't forget to run tests using watch-test.sh"
Loading

0 comments on commit f37e0fb

Please sign in to comment.