2 Commits

22 changed files with 1017 additions and 1190 deletions

View File

@@ -1,19 +0,0 @@
name: CD
on:
push:
branches: [ develop ]
# pull_request:
# branches: [ develop ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Trigger CD build
uses: peter-evans/repository-dispatch@v1
with:
token: ${{ secrets.CI_DISPATCH_TOKEN }}
repository: forensic-architecture/configs
event-type: remote-build
client-payload: '{"runtime_args": "datasheet", "branch": "${GITHUB_REF##*/}"}'

View File

@@ -1,20 +0,0 @@
name: CI
on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
with:
node-version: '12'
- run: npm install
- run: npm test
- run: npm run lint

1
.gitignore vendored
View File

@@ -14,4 +14,3 @@ tags
tags.lock tags.lock
tags.temp tags.temp
src/config.js src/config.js
src/local.config.js

14
.travis.yml Normal file
View File

@@ -0,0 +1,14 @@
language: node_js
node_js:
- stable
cache:
directories:
- node_modules
before_script:
- npm install -g yarn
install:
- yarn
script:
- yarn build
- yarn lint
- yarn test

View File

@@ -13,8 +13,8 @@ RUN yarn build
RUN mkdir -p data RUN mkdir -p data
# set your port # set your port
ENV PORT 4040 ENV PORT 8080
EXPOSE 4040 EXPOSE 8080
# start command as per package.json # start command as per package.json
CMD ["yarn", "start"] CMD ["yarn", "start"]

Binary file not shown.

1960
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,15 +26,16 @@
"express": "^4.13.3", "express": "^4.13.3",
"express-graphql": "^0.6.12", "express-graphql": "^0.6.12",
"express-handlebars": "^4.0.4", "express-handlebars": "^4.0.4",
"googleapis": "^39.1.0", "googleapis": "^32.0.0",
"graphql": "^0.13.2", "graphql": "^0.13.2",
"moment": "^2.27.0",
"morgan": "^1.8.0", "morgan": "^1.8.0",
"mz": "^2.7.0", "mz": "^2.7.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.3.0",
"node-xlsx": "^0.15.0",
"object-hash": "^1.3.0", "object-hash": "^1.3.0",
"ramda": "^0.25.0", "ramda": "^0.25.0",
"resource-router-middleware": "^0.6.0", "resource-router-middleware": "^0.6.0"
"xlsx": "^0.16.8"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.1.2", "@babel/cli": "^7.1.2",

11
scripts/check-branch.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
ENCRYPT_MESSAGE="\nThe .env.enc file has not changed its contents from the version on origin/develop.\nTo ensure that tests pass in Travis, you need to encrypt the contents of .env before pushing to the remote, so that the Travis server can use your service account credentials.\nEnsure that the Travis CLI is installed on your local, run\n\n\tnpm run travis-encrypt\n\nand then push to the remote again.\nIf you don't care whether your build passes on Travis, you can run:\n\n\tgit push --no-verify\n\nand bypass this check.\n\n"
# check whether .env.enc has changed
if [ -z "`git diff origin/develop -- .env.enc`" ]; then
echo $ENCRYPT_MESSAGE
exit 1
fi

38
scripts/encrypt.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
echo "Encrypting .env file for Travis..."
# confirm that the user has the core repo as origin, and is therefore a maintainer
# if [ -z `git config --get remote.origin.url | grep "forensic-architecture/datasheet-server"` ]; then
# echo "Travis encryption not required for satellite contributors, continuing.."
# exit 0
# fi
# confirm travis is installed
if [ ! hash travis 2>/dev/null ]; then
echo "============================================================================================"
echo "ERROR: Travis CLI is not installed on your local. Please install from:"
echo "\thttps://github.com/travis-ci/travis.rb"
echo "After installing, make sure that you login with:"
echo "\ttravis login --pro"
echo "============================================================================================"
exit 3
fi
# confirm there is a .env file to encrypt
if [ ! -f .env ]; then
echo "============================================================================================"
echo "ERROR: You must create a .env file and add your credentials. See .env.example for an example"
echo "============================================================================================"
exit 3
fi
# regex to match and delete 'before_install' and everything after it
# necessary to delete these lines to get Travis to build for multiple accounts
echo "creating new .travis.yml configuration"
sed -i.old '/^before_install.*/,$ d' .travis.yml
echo "old config file saved as .travis.yml.old"
travis encrypt-file .env --add --force --org
git add .env.enc
git add .travis.yml
echo ".env.enc created and added to commit"

View File

@@ -58,6 +58,17 @@ export default ({ config, controller }) => {
) )
}) })
// api.get('/:sheet/:tab/:resource/validate', (req, res) => {
// const { sheet, tab, resource } = req.params
// controller
// .retrieve(sheet, tab, resource)
// .then(data => res.json(data))
// .catch(err =>
// res.status(err.status || 404)
// .send({ error: err.message })
// )
// })
// ERROR routes. Note that it is important that these come AFTER routes // ERROR routes. Note that it is important that these come AFTER routes
// like /update, so that the regex does not greedily match these routes. // like /update, so that the regex does not greedily match these routes.

View File

@@ -1,4 +1,5 @@
import { fmtObj } from '../lib/util' import { fmtObj } from '../lib/util'
import { getColumnValidation } from '../lib/validation'
/** /**
* Each resource item is an object with values labelled according * Each resource item is an object with values labelled according
@@ -35,7 +36,6 @@ export default (data) => {
} }
} }
}) })
// generate the value for deep labels using the structure created // generate the value for deep labels using the structure created
data.forEach((row, idx) => { data.forEach((row, idx) => {
if (idx === 0) return if (idx === 0) return
@@ -61,6 +61,8 @@ export default (data) => {
// move values for flat labels over from base // move values for flat labels over from base
structure.__flat.forEach(label => { structure.__flat.forEach(label => {
const validatedLabel = getColumnValidation(label, baseRow[label])
console.log(validatedLabel)
deepRow[label] = baseRow[label] deepRow[label] = baseRow[label]
}) })
if (!Object.keys(deepRow).every(k => deepRow[k] === '')) { if (!Object.keys(deepRow).every(k => deepRow[k] === '')) {

View File

@@ -1,7 +1,13 @@
import { timemap } from './lib' import { timemap } from './lib'
export default { export default {
gsheets: [], gsheets: [
{
name: 'us2020',
id: '1I_pgyTQJlIorTIEHBxw1mM1STn-SrIi66FKYxut61iM',
tabs: timemap.default
}
],
xlsx: [ xlsx: [
{ {
name: 'timemap_data', name: 'timemap_data',

View File

@@ -1 +0,0 @@
CONFIG=../configs/timemap/grenfell/datasheet.config.js

View File

@@ -1,5 +1,4 @@
import http from 'http' import http from 'http'
import path from 'path'
import express from 'express' import express from 'express'
import initialize from './initialize' import initialize from './initialize'
import middleware from './middleware' import middleware from './middleware'
@@ -41,7 +40,7 @@ initialize(controller => {
}) })
) )
app.use(express.static(path.join(__dirname, 'public'))) app.use(express.static(__dirname + '/public'))
app.server.listen(process.env.PORT || 4040, () => { app.server.listen(process.env.PORT || 4040, () => {
console.log('===========================================') console.log('===========================================')

View File

@@ -1,6 +1,7 @@
import StoreJson from './models/StoreJson' import StoreJson from './models/StoreJson'
import fetchers from './lib/Fetcher' import fetchers from './lib/Fetcher'
import Controller from './lib/Controller' import Controller from './lib/Controller'
import config from './config'
import R from 'ramda' import R from 'ramda'
const isntNull = n => n !== null const isntNull = n => n !== null
@@ -8,13 +9,6 @@ const filterNull = ls => R.filter(isntNull, ls)
const flattenfilterNull = ls => filterNull(R.flatten(ls)) const flattenfilterNull = ls => filterNull(R.flatten(ls))
let themFetchers let themFetchers
let config
try {
config = require('./local.config.js').default
} catch (_) {
config = require('./config.js').default
}
export default callback => { export default callback => {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
return Object.keys(config).map(fType => { return Object.keys(config).map(fType => {

View File

@@ -5,7 +5,9 @@ function prefixedTabs (prefix, cfg) {
const prf = key => cfg[key] ? `${prefix}_` : '' const prf = key => cfg[key] ? `${prefix}_` : ''
return { return {
[`${prf('events')}export_events`]: BP.deeprows, [`${prf('events')}export_events`]: BP.deeprows,
[`${prf('associations')}export_associations`]: BP.deeprows, [`${prf('categories')}export_categories`]: [BP.groups, BP.rows],
[`${prf('filters')}export_filters`]: BP.tree,
[`${prf('narratives')}export_narratives`]: BP.rows,
[`${prf('sources')}export_sources`]: BP.deepids, [`${prf('sources')}export_sources`]: BP.deepids,
[`${prf('sites')}export_sites`]: BP.rows [`${prf('sites')}export_sites`]: BP.rows
} }

View File

@@ -1,16 +1,15 @@
import R from 'ramda' // FetcherTwo class interfaces with Google Sheet, and saves to a specified db
import { createHash } from 'crypto' import { google } from 'googleapis'
import { buildDesaturated } from './blueprinters' import { buildDesaturated } from './blueprinters'
import { import {
fmtName, fmtName,
fmtBlueprinterTitles, fmtBlueprinterTitles,
isFunction isFunction
} from './util' } from './util'
import { createHash } from 'crypto'
/* GsheetFetcher deps */ import R from 'ramda'
import { google } from 'googleapis' import xlsx from 'node-xlsx'
/* LocalFetcher deps */ import fs from 'fs'
import X from 'xlsx'
class Fetcher { class Fetcher {
constructor (db, name, bps) { constructor (db, name, bps) {
@@ -139,6 +138,7 @@ class Fetcher {
/** Run on startup. Should be overridden if explicit auth is required **/ /** Run on startup. Should be overridden if explicit auth is required **/
authenticate (env) { authenticate (env) {
console.log(`Connected to ${this.sheetName}. No explicit authentication required for ${this.type}s.`)
return Promise.resolve(this) return Promise.resolve(this)
} }
} }
@@ -221,22 +221,27 @@ class GsheetFetcher extends Fetcher {
} }
} }
class LocalFetcher extends Fetcher { class XlsxFetcher extends Fetcher {
constructor (db, name, bps, path) { constructor (db, name, bps, path) {
super(db, name, bps) super(db, name, bps)
this.type = 'XLSX File'
this.path = path this.path = path
this.update().then(res => this.isRemote = false
console.log(`${res ? 'Successful' : 'Couldn\'t'} update ${name}`)
) if (this.path.startsWith('https')) {
this.isRemote = true
}
} }
update () { update () {
const wb = X.readFile(this.path) const data = xlsx.parse(fs.readFileSync(this.path))
wb.SheetNames.forEach(name => { data.forEach(tab => {
const sh = wb.Sheets[name] const stringyData = tab.data.map(row =>
const csv = X.utils.sheet_to_csv(sh, { FS: '\t' }) row.map(d =>
const ll = csv.split('\n').map(line => line.split('\t')) typeof (d) === 'number' ? d.toString() : d
this.save(name, ll) )
)
this.save(tab.name, stringyData)
}) })
return Promise.resolve(true) return Promise.resolve(true)
} }
@@ -244,7 +249,5 @@ class LocalFetcher extends Fetcher {
export default { export default {
'gsheets': GsheetFetcher, 'gsheets': GsheetFetcher,
'xlsx': LocalFetcher, 'xlsx': XlsxFetcher
'ods': LocalFetcher,
'local': LocalFetcher
} }

View File

@@ -1,4 +1,4 @@
import R from 'ramda' import R from 'ramda';
/* eslint-disable */ /* eslint-disable */
String.prototype.replaceAll = function (search, replacement) { String.prototype.replaceAll = function (search, replacement) {

20
src/lib/validation.js Normal file
View File

@@ -0,0 +1,20 @@
import moment from 'moment';
const DATE_FORMAT = "MM/DD/YYYY";
const TIME_REGEX = new RegExp("^([01]\d|2[0-3]):?([0-5]\d)$")
export const getColumnValidation = (colName, value) => {
switch(colName) {
case 'longitude':
return isFinite(value) && Math.abs(value) <= 180;
case 'latitude':
return isFinite(value) && Math.abs(value) <= 90;
case 'date':
return moment(value, DATE_FORMAT, true).isValid();
case 'time':
return TIME_REGEX.test(value);
default:
return true
}
}

View File

@@ -54,7 +54,14 @@ test('should launch', t => {
}) })
const passUrls = [ const passUrls = [
'/api/' // /
'/api/',
// /blueprints
'/api/blueprints',
// /:sheet/:tab/:resource
'/api/example/export_events/rows',
// /:sheet/:tab/:resource/:frag
'/api/example/export_events/rows/1'
] ]
const failUrls = [ const failUrls = [
@@ -70,7 +77,6 @@ passUrls.forEach(function (url) {
.then(checkStatus) .then(checkStatus)
.then(res => res.json()) .then(res => res.json())
.then(json => { .then(json => {
console.info('JSON: ', json)
t.pass() t.pass()
}) })
}) })

View File

@@ -9,6 +9,7 @@
</div> </div>
{{#each urls}} {{#each urls}}
<div><a target="_blank" href="http://localhost:4040{{ this }}">{{ this }}</a></div> <div><a target="_blank" href="http://localhost:4040{{ this }}">{{ this }}</a></div>
<div class="bp-validate-button" target="_blank" href="http://localhost:4040{{ this }}">Validate</div>
{{/each}} {{/each}}
</div> </div>
{{ else }} {{ else }}
@@ -25,6 +26,7 @@
:root { :root {
--grey: #8a8a8a; --grey: #8a8a8a;
--btn-width: 200px; --btn-width: 200px;
--validate-btn-width: 150px;
} }
.main-container { .main-container {
display: flex; display: flex;
@@ -79,6 +81,21 @@
color: black; color: black;
} }
.bp-validate-button {
transition: all 0.3s ease;
text-transform: uppercase;
font-size: 12pt;
font-weight: bold;
width: var(--validate-btn-width);
display: flex;
justify-content: center;
align-items: center;
border: 3px solid red;
padding: .5em;
text-decoration: none;
color: red;
}
.bp-update-container:hover { .bp-update-container:hover {
background-color: black; background-color: black;
transition: all 0.3s ease; transition: all 0.3s ease;