diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..39c4d3a
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,20 @@
+FROM denoland/deno:1.26.2
+
+EXPOSE 8000
+
+RUN mkdir -p /home/deno
+RUN chown -R deno /home/deno
+RUN mkdir -p /usr/src/app/src
+WORKDIR /usr/src/app
+
+RUN apt update \
+ && apt -y install pdfsandwich tesseract-ocr-deu tesseract-ocr-fra curl git zip unzip
+RUN rm /etc/ImageMagick-6/policy.xml
+
+USER deno
+COPY src/deps.ts src/deps.ts
+RUN deno cache src/deps.ts
+
+COPY . .
+
+CMD [ "/bin/bash", "/usr/src/app/.devcontainer/docker-cmd.sh" ]
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..03ce50f
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,47 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
+// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/docker-existing-docker-compose
+// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
+{
+ "name": "Existing Docker Compose (Extend)",
+
+ // Update the 'dockerComposeFile' list if you have more compose files or use different names.
+ // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
+ "dockerComposeFile": [
+ "../dev-docker-compose.yml",
+ "docker-compose.yml"
+ ],
+
+ "containerEnv": {
+ "TRIDOC_PWD": "pw123",
+ },
+
+ // The 'service' property is the name of the service for the container that VS Code should
+ // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
+ "service": "tridoc",
+
+ // The optional 'workspaceFolder' property is the path VS Code should open by default when
+ // connected. This is typically a file mount in .devcontainer/docker-compose.yml
+ "workspaceFolder": "/usr/src/app",
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Uncomment the next line if you want start specific services in your Docker Compose config.
+ "runServices": [ "fuseki" ],
+
+ // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
+ // "shutdownAction": "none",
+
+ // Uncomment the next line to run commands after the container is created - for example installing curl.
+ // "postCreateCommand": "apt-get update && apt-get install -y curl",
+
+ // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
+ "remoteUser": "deno",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "denoland.vscode-deno"
+ ]
+ }
+ }
+}
diff --git a/.devcontainer/docker-cmd.sh b/.devcontainer/docker-cmd.sh
new file mode 100644
index 0000000..6791b5b
--- /dev/null
+++ b/.devcontainer/docker-cmd.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+echo 'Attempting to create Dataset "3DOC"'
+curl 'http://fuseki:3030/$/datasets' -H "Authorization: Basic $(echo -n admin:pw123 | base64)" \
+ -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' --data 'dbName=3DOC&dbType=tdb'
+set -m
+deno run --watch --allow-net --allow-read=blobs,rdf.ttl --allow-write=blobs,rdf.ttl --allow-run --allow-env=TRIDOC_PWD,OCR_LANG src/main.ts &
+sleep 5
+echo 'Attempting to create Dataset "3DOC"'
+curl 'http://fuseki:3030/$/datasets' -H "Authorization: Basic $(echo -n admin:pw123 | base64)" \
+ -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' --data 'dbName=3DOC&dbType=tdb'
+fg 1
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
new file mode 100644
index 0000000..3b042ba
--- /dev/null
+++ b/.devcontainer/docker-compose.yml
@@ -0,0 +1,37 @@
+version: '3'
+services:
+ # Update this to the name of the service you want to work with in your docker-compose.yml file
+ tridoc:
+ # If you want add a non-root user to your Dockerfile, you can use the "remoteUser"
+ # property in devcontainer.json to cause VS Code its sub-processes (terminals, tasks,
+ # debugging) to execute as the user. Uncomment the next line if you want the entire
+ # container to run as this user instead. Note that, on Linux, you may need to
+ # ensure the UID and GID of the container user you create matches your local user.
+ # See https://aka.ms/vscode-remote/containers/non-root for details.
+ #
+ user: deno
+
+ # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
+ # folder. Note that the path of the Dockerfile and context is relative to the *primary*
+ # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
+ # array). The sample below assumes your primary file is in the root of your project.
+ #
+ build:
+ context: .
+ dockerfile: .devcontainer/Dockerfile
+
+ volumes:
+ # Update this to wherever you want VS Code to mount the folder of your project
+ - .:/usr/src/app:cached
+
+ # Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker-compose for details.
+ # - /var/run/docker.sock:/var/run/docker.sock
+
+ # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
+ # cap_add:
+ # - SYS_PTRACE
+ # security_opt:
+ # - seccomp:unconfined
+
+ # Overrides default command so things don't shut down after the process ends.
+ # command: "/bin/bash -c \"TRIDOC_PWD=\\\"pw123\\\" deno run --allow-net --allow-read=blobs --allow-write=blobs --allow-run=convert,pdfsandwich --allow-env=TRIDOC_PWD,OCR_LANG src/main.ts &\\\n sleep 5\\\n echo 'Attempting to create Dataset \\\"3DOC\\\"'\\\n curl 'http://fuseki:3030/$/datasets' -H \\\"Authorization: Basic $(echo -n admin:pw123 | base64)\\\" -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' --data 'dbName=3DOC&dbType=tdb'\\\n fg 1\\\n /bin/sh -c \\\"while sleep 1000; do :; done\\\"\""
diff --git a/.dockerignore b/.dockerignore
index f145ba1..e911418 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,3 +1,4 @@
+old
blobs
fuseki-base
node_modules
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index c797f99..ccf4377 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,65 +1,9 @@
-# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# TypeScript v1 declaration files
-typings/
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variables file
-.env
-
-# next.js build output
-.next
-
+node_modules
blobs
-
fuseki-base
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 01e694c..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "type": "node",
- "request": "launch",
- "name": "Launch Program",
- "program": "${workspaceFolder}/lib/server",
- "env": {"TRIDOC_PWD": "tridoc"}
- }
- ]
-}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3d41712..1535e13 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,5 @@
{
- "npm.packageManager": "yarn"
+ "deno.enable": true,
+ "deno.lint": true,
+ "deno.unstable": true
}
\ No newline at end of file
diff --git a/3doc.config.js b/3doc.config.js
deleted file mode 100644
index e69de29..0000000
diff --git a/DEV-README.md b/DEV-README.md
index 8ddc74e..cc6a6a2 100644
--- a/DEV-README.md
+++ b/DEV-README.md
@@ -1,65 +1,20 @@
# tridoc
-## Table Of Contents
- * [Easy Setup with Docker-Compose](#easy-setup-with-docker-compose)
- * [Dev Build](#dev-build)
- * [Production Build](#production-build)
- * [Setup with Persistent Fuseki](#setup-with-persistent-fuseki)
- * [Docker](#docker)
- * [Manual](#manual)
+## Run "live"
-## Developer Guide
+Use the vscode-devcontainer: this will start tridoc and fuseki.
-This assumes a Unix/Linux/wsl system with bash
+It will use TRIDOC_PWD = "pw123".
+Access tridoc from http://localhost:8000 and fuseki from http://localhost:8001
-### Easy Setup with Docker-Compose
+You might need to `chown deno:deno` blobs/ and fuseki-base (attach bash to docker as root from outside)
-This will setup tridoc on port 8000 and fuseki avaliable on port 8001.
+Watch the logs from outside of vscode with
-Replace `YOUR PASSWORD HERE` in the first command with your choice of password.
-
-#### Dev Build:
-
-```
-export TRIDOC_PWD="YOUR PASSWORD HERE"
-docker-compose -f dev-docker-compose.yml build
-docker-compose -f dev-docker-compose.yml up
-```
-
-#### Production Build:
-
-```
-export TRIDOC_PWD="YOUR PASSWORD HERE"
-docker-compose build
-docker-compose up
-```
-
-### Setup with Persistent Fuseki
-
-The following method expect an instance of Fuseki running on http://fuseki:3030/ with user `admin` and password `pw123`. This fuseki instance must have lucene indexing enabled and configured as in [config-tdb.ttl](config-tdb.ttl).
-
-#### Docker:
-
-```
-docker build -t tridoc .
-docker run -p 8000:8000 -e TRIDOC_PWD="YOUR PASSWORD HERE" tridoc
-```
-
-#### Manual:
-
-Install the following dependencies:
-
-```
-node:12.18 yarn pdfsandwich tesseract-ocr-deu tesseract-ocr-fra
+```sh
+docker logs -f tridoc-backend_tridoc_1
```
-And run the following commands
-
-```
-rm /etc/ImageMagick-6/policy.xml
-yarn install
-bash docker-cmd.sh
-```
## Tips & Tricks
diff --git a/Dockerfile b/Dockerfile
index 3fbabeb..680ee6a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,18 @@
-FROM node:lts-buster
+FROM denoland/deno:1.26.2
+
EXPOSE 8000
+
+RUN mkdir -p /usr/src/app/src
+WORKDIR /usr/src/app
+
RUN apt update \
- && apt -y install pdfsandwich tesseract-ocr-deu tesseract-ocr-fra
+ && apt -y install pdfsandwich tesseract-ocr-deu tesseract-ocr-fra curl zip unzip
RUN rm /etc/ImageMagick-6/policy.xml
-RUN mkdir -p /usr/src/app
-WORKDIR /usr/src/app
-COPY . /usr/src/app
-RUN yarn install
-RUN chmod +x /usr/src/app/docker-cmd.sh
-CMD [ "/usr/src/app/docker-cmd.sh" ]
\ No newline at end of file
+
+USER deno
+COPY src/deps.ts src/deps.ts
+RUN deno cache src/deps.ts
+
+COPY . .
+
+CMD [ "/bin/bash", "/usr/src/app/docker-cmd.sh" ]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index e2b8549..2ec5ef9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2018 Reto Gmür
+Copyright (c) 2022 Noam Bachmann & Reto Gmür
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 0965e93..58948b9 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ When getting a comment, a JSON array with objects of the following structure is
## API
| Address | Method | Description | Request / Payload | Response | Implemented in Version |
-| - | - | - | - | - | - |
+| - | - | - | - | - | - | - |
| `/count` | GET | Count (matching) documents | [1](#f1) [3](#f3) | Number | 1.1.0 |
| `/doc` | POST | Add / Store Document | PDF[5](#f5) | - | 1.1.0 |
| `/doc` | GET | Get List of all (matching) documents | [1](#f1) [2](#f2) [3](#f3) | Array of objects with document identifiers and titles (where available) | 1.1.0 |
@@ -123,6 +123,7 @@ When getting a comment, a JSON array with objects of the following structure is
| `/doc/{id}/title` | DELETE | Reset document title | - | - | 1.1.0 |
| `/doc/{id}/meta` | GET | Get various metadata | - | `{"title": "the_Title", "tags":[...], "comments": [...] ... }` | 1.1.0 \| .comments & .created in 1.2.1 |
| `/raw/rdf` | GET | Get all metadata as RDF. Useful for Backups | [4](#f4) | RDF, Content-Type defined over request Headers or ?accept. Fallback to text/turtle. | 1.1.0 |
+| `/raw/rdf` | DELETE | "Cancel" failed zip upload—use only if certain it’s done & failed | | | (deno only) |
| `/raw/zip` or `/raw/tgz` | GET | Get all data. Useful for backups | - | ZIP / TGZ containing blobs/ directory with all pdfs as stored within tridoc and a rdf.ttl file with all metadata. | 1.3.0 |
| `/raw/zip` | PUT | Replace all data with backup zip | ZIP | Replaces the metadata and adds the blobs from the zip | 1.3.0 |
| `/tag` | POST | Create new tag | See above | - | 1.1.0 |
diff --git a/deno.jsonc b/deno.jsonc
new file mode 100644
index 0000000..03da2df
--- /dev/null
+++ b/deno.jsonc
@@ -0,0 +1,12 @@
+{
+ "fmt": {
+ "files": {
+ "include": ["src/"]
+ }
+ },
+ "tasks": {
+ // --allow-run=convert,pdfsandwich,pdftotext,tar,zip,unzip,bash
+ "run": "deno run --allow-net --allow-read=blobs,rdf.ttl --allow-write=blobs,rdf.ttls --allow-run --allow-env=TRIDOC_PWD,OCR_LANG src/main.ts",
+ "run-watch": "deno run --watch --allow-net --allow-read=blobs,rdf.ttl --allow-write=blobs,rdf.ttl --allow-run --allow-env=TRIDOC_PWD,OCR_LANG src/main.ts"
+ }
+}
diff --git a/docker-cmd.sh b/docker-cmd.sh
index 56e8c9d..c707f9c 100644
--- a/docker-cmd.sh
+++ b/docker-cmd.sh
@@ -1,10 +1,9 @@
#!/bin/bash
-sleep 5
echo 'Attempting to create Dataset "3DOC"'
curl 'http://fuseki:3030/$/datasets' -H "Authorization: Basic $(echo -n admin:pw123 | base64)" \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' --data 'dbName=3DOC&dbType=tdb'
set -m
-yarn start &
+deno run --allow-net --allow-read=blobs,rdf.ttl --allow-write=blobs,rdf.ttl --allow-run --allow-env=TRIDOC_PWD,OCR_LANG src/main.ts &
sleep 5
echo 'Attempting to create Dataset "3DOC"'
curl 'http://fuseki:3030/$/datasets' -H "Authorization: Basic $(echo -n admin:pw123 | base64)" \
diff --git a/find-draft.txt b/find-draft.txt
deleted file mode 100644
index df2b38d..0000000
--- a/find-draft.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Encode where necessary: GET /doc?tag=pay&tag=prority(>3)&tag=not(personal)&search=helsinki
- /doc?tag=pay&tag=prority(>3)¬tag=personal&text=helsinki
diff --git a/lib/datastore.js b/old/lib/datastore.js
similarity index 100%
rename from lib/datastore.js
rename to old/lib/datastore.js
diff --git a/lib/metadeleter.js b/old/lib/metadeleter.js
similarity index 100%
rename from lib/metadeleter.js
rename to old/lib/metadeleter.js
diff --git a/lib/metafinder.js b/old/lib/metafinder.js
similarity index 100%
rename from lib/metafinder.js
rename to old/lib/metafinder.js
diff --git a/lib/metastorer.js b/old/lib/metastorer.js
similarity index 100%
rename from lib/metastorer.js
rename to old/lib/metastorer.js
diff --git a/lib/pdfprocessor.js b/old/lib/pdfprocessor.js
similarity index 100%
rename from lib/pdfprocessor.js
rename to old/lib/pdfprocessor.js
diff --git a/lib/server.js b/old/lib/server.js
similarity index 100%
rename from lib/server.js
rename to old/lib/server.js
diff --git a/tdt.fish b/old/tdt.fish
similarity index 100%
rename from tdt.fish
rename to old/tdt.fish
diff --git a/package.json b/package.json
deleted file mode 100644
index 5a603ea..0000000
--- a/package.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "tridoc-backend",
- "version": "1.5.2",
- "description": "Simple RDF-Based Document Management System",
- "main": "lib/server",
- "repository": "git@github.com:tridoc/tridoc-backend.git",
- "author": "Noam Bachmann ",
- "license": "MIT",
- "dependencies": {
- "adm-zip": "^0.4.16",
- "archiver": "^3.1.1",
- "hapi": "^17.5.2",
- "hapi-auth-basic": "^5.0.0",
- "nanoid": "^1.1.0",
- "node-fetch": "^2.2.0",
- "pdfjs-dist": "^2.0.489"
- },
- "scripts": {
- "start": "node lib/server.js",
- "start-with-pwd": "TRIDOC_PWD='tridoc' node lib/server.js"
- }
-}
diff --git a/src/deps.ts b/src/deps.ts
new file mode 100644
index 0000000..85736e4
--- /dev/null
+++ b/src/deps.ts
@@ -0,0 +1,8 @@
+export const VERSION = "1.6.0-alpha.deno.1";
+
+export { encode } from "https://deno.land/std@0.160.0/encoding/base64.ts";
+export { emptyDir, ensureDir } from "https://deno.land/std@0.160.0/fs/mod.ts";
+export { serve } from "https://deno.land/std@0.160.0/http/mod.ts";
+export { writableStreamFromWriter } from "https://deno.land/std@0.160.0/streams/mod.ts";
+
+export { nanoid } from "https://deno.land/x/nanoid@v3.0.0/mod.ts";
diff --git a/src/handlers/cors.ts b/src/handlers/cors.ts
new file mode 100644
index 0000000..d98c74a
--- /dev/null
+++ b/src/handlers/cors.ts
@@ -0,0 +1,12 @@
+import { respond } from "../helpers/cors.ts";
+
+export function options(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ return new Promise((resolve) =>
+ resolve(
+ respond(undefined, { status: 204 }),
+ )
+ );
+}
diff --git a/src/handlers/count.ts b/src/handlers/count.ts
new file mode 100644
index 0000000..06d0f26
--- /dev/null
+++ b/src/handlers/count.ts
@@ -0,0 +1,12 @@
+import { respond } from "../helpers/cors.ts";
+import { processParams } from "../helpers/processParams.ts";
+import { getDocumentNumber } from "../meta/finder.ts";
+
+export async function count(
+ request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const params = await processParams(request);
+ const count = await getDocumentNumber(params);
+ return respond("" + count);
+}
diff --git a/src/handlers/doc.ts b/src/handlers/doc.ts
new file mode 100644
index 0000000..db7b454
--- /dev/null
+++ b/src/handlers/doc.ts
@@ -0,0 +1,301 @@
+import { ensureDir } from "https://deno.land/std@0.160.0/fs/ensure_dir.ts";
+import { nanoid, writableStreamFromWriter } from "../deps.ts";
+import { respond } from "../helpers/cors.ts";
+import { getText } from "../helpers/pdfprocessor.ts";
+import { processParams } from "../helpers/processParams.ts";
+import * as metadelete from "../meta/delete.ts";
+import * as metafinder from "../meta/finder.ts";
+import * as metastore from "../meta/store.ts";
+
+type TagAdd = {
+ label: string;
+ parameter?: {
+ type:
+ | "http://www.w3.org/2001/XMLSchema#decimal"
+ | "http://www.w3.org/2001/XMLSchema#date";
+ value: string; // must be valid xsd:decimal or xsd:date, as specified in property type.
+ }; // only for parameterizable tags
+};
+
+function getDir(id: string) {
+ return "./blobs/" + id.slice(0, 2) + "/" + id.slice(2, 6) + "/" +
+ id.slice(6, 14);
+}
+
+function getPath(id: string) {
+ return "./blobs/" + id.slice(0, 2) + "/" + id.slice(2, 6) + "/" +
+ id.slice(6, 14) + "/" + id;
+}
+
+function datecheck(request: Request) {
+ const url = new URL(request.url);
+ const regex =
+ /^(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-6]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-6]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-6]\d([+-][0-2]\d:[0-5]\d|Z))$/;
+ const date = url.searchParams.get("date");
+ return date ? (regex.test(date) ? date : undefined) : undefined;
+}
+
+export async function deleteDoc(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ await metadelete.deleteFile(id);
+ return respond(undefined, { status: 204 });
+}
+
+export async function deleteTag(
+ _request: Request,
+ match: URLPatternResult,
+) {
+ await metadelete.deleteTag(
+ decodeURIComponent(match.pathname.groups.tagLabel),
+ match.pathname.groups.id,
+ );
+ return respond(undefined, { status: 204 });
+}
+export async function deleteTitle(
+ request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ await metadelete.deleteTitle(id);
+ return respond(undefined, { status: 201 });
+}
+
+export async function getComments(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ const response = await metafinder.getComments(id);
+ return respond(JSON.stringify(response), {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ });
+}
+
+export async function getPDF(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ const path = getPath(id);
+ try {
+ const fileName = await metafinder.getBasicMeta(id).then((
+ { title, created },
+ ) => title || created || "document");
+ const file = await Deno.open(path, { read: true });
+ // Build a readable stream so the file doesn't have to be fully loaded into memory while we send it
+ const readableStream = file.readable;
+ return respond(readableStream, {
+ headers: {
+ "content-disposition": `inline; filename="${encodeURI(fileName)}.pdf"`,
+ "content-type": "application/pdf",
+ },
+ });
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ return respond("404 Not Found", { status: 404 });
+ }
+ throw error;
+ }
+}
+
+export async function getMeta(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ return respond(
+ JSON.stringify({
+ ...(await metafinder.getBasicMeta(id)),
+ comments: await metafinder.getComments(id),
+ tags: await metafinder.getTags(id),
+ }),
+ {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ },
+ );
+}
+
+export async function getTags(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ return respond(JSON.stringify(await metafinder.getTags(id)), {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ });
+}
+
+export async function getThumb(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ const path = getPath(id);
+ const fileName = await metafinder.getBasicMeta(id).then((
+ { title, created },
+ ) => title || created || "thumbnail");
+ let thumb: Deno.FsFile;
+ try {
+ thumb = await Deno.open(path + ".png", { read: true });
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ try {
+ await Deno.stat(path); // Check if PDF exists → 404 otherwise
+ const p = Deno.run({
+ cmd: [
+ "convert",
+ "-thumbnail",
+ "300x",
+ "-alpha",
+ "remove",
+ `${path}[0]`,
+ `${path}.png`,
+ ],
+ });
+ const { success, code } = await p.status();
+ if (!success) throw new Error("convert failed with code " + code);
+ thumb = await Deno.open(path + ".png", { read: true });
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ return respond("404 Not Found", { status: 404 });
+ }
+ throw error;
+ }
+ } else {
+ throw error;
+ }
+ }
+ // Build a readable stream so the file doesn't have to be fully loaded into memory while we send it
+ const readableStream = thumb.readable;
+ return respond(readableStream, {
+ headers: {
+ "content-disposition": `inline; filename="${encodeURI(fileName)}.png"`,
+ "content-type": "image/png",
+ },
+ });
+}
+
+export async function getTitle(
+ _request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ return respond(
+ JSON.stringify({ title: (await metafinder.getBasicMeta(id)).title }),
+ {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ },
+ );
+}
+
+export async function list(
+ request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const params = await processParams(request);
+ const response = await metafinder.getDocumentList(params);
+ return respond(JSON.stringify(response), {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ });
+}
+
+export async function postComment(
+ request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ await metastore.addComment(id, (await request.json()).text);
+ return respond(undefined, { status: 201 });
+}
+
+export async function postPDF(
+ request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const id = nanoid();
+ const path = getPath(id);
+ await ensureDir(getDir(id));
+ const pdf = await Deno.open(path, { write: true, create: true });
+ const writableStream = writableStreamFromWriter(pdf);
+ await request.body?.pipeTo(writableStream);
+ console.log((new Date()).toISOString(), "Document created with id", id);
+ let text = await getText(path);
+ if (text.length < 4) {
+ // run OCR
+ const lang = Deno.env.get("OCR_LANG") || "fra+deu+eng";
+ const p = Deno.run({ cmd: ["pdfsandwich", "-rgb", "-lang", lang, path] });
+ const { success, code } = await p.status();
+ if (!success) throw new Error("pdfsandwich failed with code " + code);
+ // pdfsandwich generates a file with the same name + _ocr
+ await Deno.rename(path + "_ocr", path);
+ text = await getText(path);
+ console.log((new Date()).toISOString(), id, ": OCR finished");
+ }
+ // no await as we don’t care for the result - if it fails, the thumbnail will be created upon request.
+ Deno.run({
+ cmd: [
+ "convert",
+ "-thumbnail",
+ "300x",
+ "-alpha",
+ "remove",
+ `${path}[0]`,
+ `${path}.png`,
+ ],
+ });
+ const date = datecheck(request);
+ await metastore.storeDocument({ id, text, date });
+ return respond(undefined, {
+ headers: {
+ "Location": "/doc/" + id,
+ "Access-Control-Expose-Headers": "Location",
+ },
+ });
+}
+
+export async function postTag(
+ request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ const tagObject: TagAdd = await request.json();
+ const [label, type] =
+ (await metafinder.getTagTypes([tagObject.label]))?.[0] ??
+ [undefined, undefined];
+ if (!label) {
+ return respond("Tag must exist before adding to a document", {
+ status: 400,
+ });
+ }
+ if (tagObject.parameter?.type !== type) {
+ return respond("Type provided does not match", { status: 400 });
+ }
+ if (tagObject.parameter?.type && !tagObject.parameter?.value) {
+ return respond("No value provided", { status: 400 });
+ }
+ await metastore.addTag(id, tagObject.label, tagObject.parameter?.value, type);
+ return respond(undefined, { status: 201 });
+}
+
+export async function putTitle(
+ request: Request,
+ match: URLPatternResult,
+): Promise {
+ const id = match.pathname.groups.id;
+ const title: string = (await request.json())?.title;
+ await metastore.addTitle(id, title);
+ return respond(undefined, { status: 201 });
+}
diff --git a/src/handlers/notImplemented.ts b/src/handlers/notImplemented.ts
new file mode 100644
index 0000000..10da909
--- /dev/null
+++ b/src/handlers/notImplemented.ts
@@ -0,0 +1,6 @@
+export function notImplemented(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ throw new Error("not implemented");
+}
diff --git a/src/handlers/raw.ts b/src/handlers/raw.ts
new file mode 100644
index 0000000..a680a83
--- /dev/null
+++ b/src/handlers/raw.ts
@@ -0,0 +1,142 @@
+import { ensureDir } from "https://deno.land/std@0.160.0/fs/ensure_dir.ts";
+import { emptyDir, writableStreamFromWriter } from "../deps.ts";
+import { respond } from "../helpers/cors.ts";
+import { dump } from "../meta/fusekiFetch.ts";
+import { restore } from "../meta/store.ts";
+
+const decoder = new TextDecoder("utf-8");
+
+export async function deleteRDFFile(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ await Deno.remove("rdf.ttl");
+ return respond(undefined, { status: 204 });
+}
+
+export async function getRDF(
+ request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const url = new URL(request.url);
+ const accept = url.searchParams.has("accept")
+ ? decodeURIComponent(url.searchParams.get("accept")!)
+ : request.headers.get("Accept") || "text/turtle";
+ return await dump(accept);
+}
+
+export async function getTGZ(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const timestamp = "" + Date.now();
+ const tarPath = "blobs/tgz-" + timestamp;
+ const rdfName = "rdf-" + timestamp;
+ const rdfPath = "blobs/rdf/" + rdfName;
+ await ensureDir("blobs/rdf");
+ const rdf = await Deno.open(rdfPath, {
+ create: true,
+ write: true,
+ truncate: true,
+ });
+ const writableStream = writableStreamFromWriter(rdf);
+ await (await dump()).body?.pipeTo(writableStream);
+ const p = Deno.run({
+ cmd: [
+ "bash",
+ "-c",
+ `tar --transform="s|${rdfPath}|rdf.ttl|" --exclude-tag="${rdfName}" -czvf ${tarPath} blobs/*/`,
+ ],
+ });
+ const { success, code } = await p.status();
+ if (!success) throw new Error("tar -czf failed with code " + code);
+ await Deno.remove(rdfPath);
+ const tar = await Deno.open(tarPath);
+ // Build a readable stream so the file doesn't have to be fully loaded into memory while we send it
+ const readableStream = tar.readable;
+ return respond(readableStream, {
+ headers: {
+ "content-disposition":
+ `inline; filename="tridoc_backup_${timestamp}.tar.gz"`,
+ "content-type": "application/gzip",
+ },
+ });
+ // TODO: Figure out how to delete these files
+}
+
+export async function getZIP(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const timestamp = "" + Date.now();
+ const zipPath = `blobs/zip-${timestamp}.zip`;
+ const rdfPath = "blobs/rdf-" + timestamp;
+ const rdf = await Deno.open(rdfPath, {
+ create: true,
+ write: true,
+ truncate: true,
+ });
+ const writableStream = writableStreamFromWriter(rdf);
+ await (await dump()).body?.pipeTo(writableStream);
+ // Create zip
+ const p_1 = Deno.run({
+ cmd: [
+ "bash",
+ "-c",
+ `zip -r ${zipPath} blobs/*/ ${rdfPath} -x "blobs/rdf/*"`,
+ ],
+ });
+ const r_1 = await p_1.status();
+ if (!r_1.success) throw new Error("zip failed with code " + r_1.code);
+ // move rdf-??? to rdf.zip
+ const p_2 = Deno.run({
+ cmd: [
+ "bash",
+ "-c",
+ `printf "@ ${rdfPath}\\n@=rdf.ttl\\n" | zipnote -w ${zipPath}`,
+ ],
+ });
+ const r_2 = await p_2.status();
+ if (!r_2.success) throw new Error("zipnote failed with code " + r_2.code);
+ await Deno.remove(rdfPath);
+ const zip = await Deno.open(zipPath);
+ // Build a readable stream so the file doesn't have to be fully loaded into memory while we send it
+ const readableStream = zip.readable;
+ return respond(readableStream, {
+ headers: {
+ "content-disposition":
+ `inline; filename="tridoc_backup_${timestamp}.zip"`,
+ "content-type": "application/zip",
+ },
+ });
+ // TODO: Figure out how to delete these files
+}
+
+export async function putZIP(
+ request: Request,
+ _match: URLPatternResult,
+): Promise {
+ try {
+ await Deno.stat("rdf.ttl");
+ throw new Error(
+ "Can't unzip concurrently: rdf.ttl already exists. If you know what you are doing, clear this message with HTTP DELETE /raw/rdf",
+ );
+ } catch (error) {
+ if (!(error instanceof Deno.errors.NotFound)) {
+ throw error;
+ }
+ }
+ await emptyDir("blobs");
+ const zipPath = "blobs/zip-" + Date.now();
+ const zip = await Deno.open(zipPath, { write: true, create: true });
+ const writableStream = writableStreamFromWriter(zip);
+ await request.body?.pipeTo(writableStream);
+ const p = Deno.run({ cmd: ["unzip", zipPath] });
+ const { success, code } = await p.status();
+ if (!success) throw new Error("unzip failed with code " + code);
+ await Deno.remove(zipPath);
+ const turtleData = decoder.decode(await Deno.readFile("rdf.ttl"));
+ await Deno.remove("rdf.ttl");
+ await restore(turtleData);
+ return respond(undefined, { status: 204 });
+}
diff --git a/src/handlers/tag.ts b/src/handlers/tag.ts
new file mode 100644
index 0000000..27cf821
--- /dev/null
+++ b/src/handlers/tag.ts
@@ -0,0 +1,75 @@
+import { respond } from "../helpers/cors.ts";
+import { processParams } from "../helpers/processParams.ts";
+import * as metadelete from "../meta/delete.ts";
+import * as metafinder from "../meta/finder.ts";
+import * as metastore from "../meta/store.ts";
+
+type TagCreate = {
+ label: string;
+ parameter?: {
+ type:
+ | "http://www.w3.org/2001/XMLSchema#decimal"
+ | "http://www.w3.org/2001/XMLSchema#date";
+ }; // only for parameterizable tags
+};
+
+export async function createTag(
+ request: Request,
+ _match: URLPatternResult,
+): Promise {
+ const tagObject: TagCreate = await request.json();
+ if (!tagObject?.label) return respond("No label provided", { status: 400 });
+ if (
+ tagObject?.parameter &&
+ tagObject.parameter.type !== "http://www.w3.org/2001/XMLSchema#decimal" &&
+ tagObject.parameter.type !== "http://www.w3.org/2001/XMLSchema#date"
+ ) {
+ return respond("Invalid type", { status: 400 });
+ }
+ const tagList = await metafinder.getTagList();
+ if (tagList.some((e) => e.label === tagObject.label)) {
+ return respond("Tag already exists", { status: 400 });
+ }
+ const regex = /\s|^[.]{1,2}$|\/|\\|#|"|'|,|;|:|\?/;
+ if (regex.test(tagObject.label)) {
+ return respond("Label contains forbidden characters", { status: 400 });
+ }
+ await metastore.createTag(tagObject.label, tagObject.parameter?.type);
+ return respond(undefined, { status: 201 });
+}
+
+export async function deleteTag(
+ _request: Request,
+ match: URLPatternResult,
+) {
+ await metadelete.deleteTag(
+ decodeURIComponent(match.pathname.groups.tagLabel),
+ );
+ return respond(undefined, { status: 204 });
+}
+
+export async function getDocs(
+ request: Request,
+ match: URLPatternResult,
+): Promise {
+ const params = await processParams(request, {
+ tags: [[match.pathname.groups.tagLabel]],
+ });
+ const response = await metafinder.getDocumentList(params);
+ return respond(JSON.stringify(response), {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ });
+}
+
+export async function getTagList(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ return respond(JSON.stringify(await metafinder.getTagList()), {
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ },
+ });
+}
diff --git a/src/handlers/version.ts b/src/handlers/version.ts
new file mode 100644
index 0000000..2864385
--- /dev/null
+++ b/src/handlers/version.ts
@@ -0,0 +1,9 @@
+import { VERSION } from "../deps.ts";
+import { respond } from "../helpers/cors.ts";
+
+export function version(
+ _request: Request,
+ _match: URLPatternResult,
+): Promise {
+ return new Promise((resolve) => resolve(respond(VERSION)));
+}
diff --git a/src/helpers/cors.ts b/src/helpers/cors.ts
new file mode 100644
index 0000000..907f2ad
--- /dev/null
+++ b/src/helpers/cors.ts
@@ -0,0 +1,11 @@
+export function respond(body?: BodyInit, init?: ResponseInit) {
+ return new Response(body, {
+ ...init,
+ headers: {
+ ...init?.headers,
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "POST, PUT, DELETE, GET, OPTIONS",
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
+ },
+ });
+}
diff --git a/src/helpers/pdfprocessor.ts b/src/helpers/pdfprocessor.ts
new file mode 100644
index 0000000..1e267d2
--- /dev/null
+++ b/src/helpers/pdfprocessor.ts
@@ -0,0 +1,9 @@
+const decoder = new TextDecoder("utf-8");
+
+export async function getText(path: string) {
+ const p = Deno.run({ cmd: ["pdftotext", path, "-"], stdout: "piped" });
+ const output = decoder.decode(await p.output());
+ const { success, code } = await p.status();
+ if (!success) throw new Error("pdftotext failed with code " + code);
+ return output;
+}
diff --git a/src/helpers/processParams.ts b/src/helpers/processParams.ts
new file mode 100644
index 0000000..d03525a
--- /dev/null
+++ b/src/helpers/processParams.ts
@@ -0,0 +1,98 @@
+import { getTagTypes } from "../meta/finder.ts";
+
+function extractQuery(request: Request) {
+ const url = new URL(request.url);
+ const query: Record = {};
+ for (const param of url.searchParams) {
+ if (query[param[0]]) {
+ query[param[0]].push(param[1]);
+ } else query[param[0]] = new Array(param[1]);
+ }
+ return query;
+}
+
+type ParamTag = {
+ label: string; // [0]
+ min?: string; // [1]
+ max?: string; // [2]
+ type?: string; // [3]
+ maxIsExclusive?: boolean; //[5]
+};
+
+export type queryOverrides = {
+ tags?: string[][];
+ nottags?: string[][];
+};
+
+export type Params = {
+ tags?: ParamTag[];
+ nottags?: ParamTag[];
+ text?: string;
+ limit?: number;
+ offset?: number;
+};
+
+export async function processParams(
+ request: Request,
+ queryOverrides?: queryOverrides,
+): Promise {
+ const query = extractQuery(request);
+ const result: Params = {};
+ const tags = query.tag?.map((t) => t.split(";")) ?? [];
+ if (queryOverrides?.tags) tags.push(...queryOverrides.tags);
+ const nottags = query.nottag?.map((t) => t.split(";")) ?? [];
+ if (queryOverrides?.nottags) tags.push(...queryOverrides.nottags);
+ result.text = query.text?.[0];
+ result.limit = parseInt(query.limit?.[0], 10) > 0
+ ? parseInt(query.limit[0])
+ : undefined;
+ result.offset = parseInt(query.offset?.[0], 10) >= 0
+ ? parseInt(query.offset[0])
+ : undefined;
+ return await getTagTypes(
+ tags.map((e) => e[0]).concat(nottags.map((e) => e[0])),
+ ).then((types) => {
+ function tagMap(t: string[]): ParamTag {
+ const label = t[0];
+ const type = types.find((e) => e[0] === t[0])?.[1];
+ let min = t[1];
+ let max = t[2];
+ let maxIsExclusive;
+ if (type === "http://www.w3.org/2001/XMLSchema#date") {
+ if (min) {
+ switch (min.length) {
+ case 4:
+ min += "-01-01";
+ break;
+ case 7:
+ min += "-01";
+ break;
+ }
+ }
+ if (max) {
+ switch (max.length) {
+ case 4:
+ max += "-12-31";
+ break;
+ case 7: {
+ const month = parseInt(max.substring(5), 10) + 1;
+ if (month < 13) {
+ max = max.substring(0, 5) + "-" +
+ month.toString().padStart(2, "0") + "-01";
+ maxIsExclusive = true;
+ } else {
+ max += "-31";
+ }
+ break;
+ }
+ }
+ }
+ }
+ return { label, min, max, type, maxIsExclusive };
+ }
+ result.tags = tags.map(tagMap);
+ result.nottags = nottags.map(tagMap);
+ console.log("eh??", result);
+ return result;
+ });
+}
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..e75b254
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,12 @@
+import { serve } from "./server/server.ts";
+
+console.log("Starting tridoc backend server");
+
+// TODO Check external dependencies
+
+if (!Deno.env.get("TRIDOC_PWD")) {
+ throw new Error("No password set");
+}
+
+serve();
+console.log("Tridoc backend server is listening on port 8000");
diff --git a/src/meta/delete.ts b/src/meta/delete.ts
new file mode 100644
index 0000000..be9e4bc
--- /dev/null
+++ b/src/meta/delete.ts
@@ -0,0 +1,58 @@
+import { fusekiUpdate } from "./fusekiFetch.ts";
+
+export function deleteFile(id: string) {
+ return fusekiUpdate(`
+WITH
+DELETE { ?p ?o }
+WHERE { ?p ?o }`);
+}
+
+export async function deleteTag(label: string, id?: string) {
+ await Promise.allSettled([
+ fusekiUpdate(`
+PREFIX rdf:
+PREFIX s:
+PREFIX tridoc:
+WITH
+DELETE {
+ ${
+ id ? ` tridoc:tag ?ptag + ` : `?ptag ?p ?o .
+ ?s ?p1 ?ptag`
+ }
+}
+WHERE {
+ ?ptag tridoc:parameterizableTag ?tag.
+ ?tag tridoc:label "${label}" .
+ OPTIONAL { ?ptag ?p ?o }
+ OPTIONAL {
+ ${id ? ` tridoc:tag ?ptag` : "?s ?p1 ?ptag"}
+ }
+}`),
+ fusekiUpdate(`
+PREFIX rdf:
+PREFIX s:
+PREFIX tridoc:
+WITH
+DELETE {
+ ${
+ id ? ` tridoc:tag ?tag` : `?tag ?p ?o .
+ ?s ?p1 ?tag`
+ }
+}
+WHERE {
+ ?tag tridoc:label "${label}" .
+ OPTIONAL { ?tag ?p ?o }
+ OPTIONAL {
+ ${id ? ` ?p1 ?tag` : "?s ?p1 ?tag"}
+ }
+}`),
+ ]);
+}
+
+export function deleteTitle(id: string) {
+ return fusekiUpdate(`
+PREFIX s:
+WITH
+DELETE { s:name ?o }
+WHERE { s:name ?o }`);
+}
diff --git a/src/meta/finder.ts b/src/meta/finder.ts
new file mode 100644
index 0000000..ad3ed14
--- /dev/null
+++ b/src/meta/finder.ts
@@ -0,0 +1,257 @@
+import { Params } from "../helpers/processParams.ts";
+import { fusekiFetch } from "./fusekiFetch.ts";
+
+export async function getComments(id: string) {
+ const query = `PREFIX rdf:
+PREFIX xsd:
+PREFIX tridoc:
+PREFIX s:
+SELECT DISTINCT ?d ?t WHERE {
+ GRAPH {
+ s:comment [
+ a s:Comment ;
+ s:dateCreated ?d ;
+ s:text ?t
+ ] .
+ }
+}`;
+ return await fusekiFetch(query).then((json) =>
+ json.results.bindings.map((binding) => {
+ return { text: binding.t.value, created: binding.d.value };
+ })
+ );
+}
+
+export async function getDocumentList(
+ { tags = [], nottags = [], text, limit, offset }: Params,
+) {
+ let tagQuery = "";
+ for (let i = 0; i < tags.length; i++) {
+ if (tags[i].type) {
+ tagQuery += `{ ?s tridoc:tag ?ptag${i} .
+ ?ptag${i} tridoc:parameterizableTag ?atag${i} .
+ ?ptag${i} tridoc:value ?v${i} .
+ ?atag${i} tridoc:label "${tags[i].label}" .
+ ${
+ tags[i].min
+ ? `FILTER (?v${i} >= "${tags[i].min}"^^<${tags[i].type}> )`
+ : ""
+ }
+ ${
+ tags[i].max
+ ? `FILTER (?v${i} ${tags[i].maxIsExclusive ? "<" : "<="} "${
+ tags[i].max
+ }"^^<${tags[i].type}> )`
+ : ""
+ } }`;
+ } else {
+ tagQuery += `{ ?s tridoc:tag ?tag${i} .
+ ?tag${i} tridoc:label "${tags[i].label}" . }`;
+ }
+ }
+ for (let i = 0; i < nottags.length; i++) {
+ if (nottags[i].type) {
+ tagQuery += `FILTER NOT EXISTS { ?s tridoc:tag ?ptag${i} .
+ ?ptag${i} tridoc:parameterizableTag ?atag${i} .
+ ?ptag${i} tridoc:value ?v${i} .
+ ?atag${i} tridoc:label "${nottags[i].label}" .
+ ${
+ nottags[i].min
+ ? `FILTER (?v${i} >= "${nottags[i].min}"^^<${nottags[i].type}> )`
+ : ""
+ }
+ ${
+ nottags[i].max
+ ? `FILTER (?v${i} ${nottags[i].maxIsExclusive ? "<" : "<="} "${
+ nottags[i].max
+ }"^^<${nottags[i].type}> )`
+ : ""
+ } }`;
+ } else {
+ tagQuery += `FILTER NOT EXISTS { ?s tridoc:tag ?tag${i} .
+ ?tag${i} tridoc:label "${nottags[i].label}" . }`;
+ }
+ }
+ const body = "PREFIX rdf: \n" +
+ "PREFIX s: \n" +
+ "PREFIX tridoc: \n" +
+ "PREFIX text: \n" +
+ "SELECT DISTINCT ?s ?identifier ?title ?date\n" +
+ "WHERE {\n" +
+ " ?s s:identifier ?identifier .\n" +
+ " ?s s:dateCreated ?date .\n" +
+ tagQuery +
+ " OPTIONAL { ?s s:name ?title . }\n" +
+ (text
+ ? '{ { ?s text:query (s:name "' + text +
+ '") } UNION { ?s text:query (s:text "' + text + '")} } .\n'
+ : "") +
+ "}\n" +
+ "ORDER BY desc(?date)\n" +
+ (limit ? "LIMIT " + limit + "\n" : "") +
+ (offset ? "OFFSET " + offset : "");
+ return await fusekiFetch(body).then((json) =>
+ json.results.bindings.map((binding) => {
+ const result: Record = {};
+ result.identifier = binding.identifier.value;
+ if (binding.title) {
+ result.title = binding.title.value;
+ }
+ if (binding.date) {
+ result.created = binding.date.value;
+ }
+ return result;
+ })
+ );
+}
+
+export async function getDocumentNumber(
+ { tags = [], nottags = [], text }: Params,
+) {
+ let tagQuery = "";
+ for (let i = 0; i < tags.length; i++) {
+ if (tags[i].type) {
+ tagQuery += `{ ?s tridoc:tag ?ptag${i} .
+ ?ptag${i} tridoc:parameterizableTag ?atag${i} .
+ ?ptag${i} tridoc:value ?v${i} .
+ ?atag${i} tridoc:label "${tags[i].label}" .
+ ${
+ tags[i].min
+ ? `FILTER (?v${i} >= "${tags[i].min}"^^<${tags[i].type}> )`
+ : ""
+ }
+ ${
+ tags[i].max
+ ? `FILTER (?v${i} ${tags[i].maxIsExclusive ? "<" : "<="} "${
+ tags[i].max
+ }"^^<${tags[i].type}> )`
+ : ""
+ } }`;
+ } else {
+ tagQuery += `{ ?s tridoc:tag ?tag${i} .
+ ?tag${i} tridoc:label "${tags[i].label}" . }`;
+ }
+ }
+ for (let i = 0; i < nottags.length; i++) {
+ if (nottags[i].type) {
+ tagQuery += `FILTER NOT EXISTS { ?s tridoc:tag ?ptag${i} .
+ ?ptag${i} tridoc:parameterizableTag ?atag${i} .
+ ?ptag${i} tridoc:value ?v${i} .
+ ?atag${i} tridoc:label "${nottags[i].label}" .
+ ${
+ nottags[i].min
+ ? `FILTER (?v${i} >= "${nottags[i].min}"^^<${nottags[i].type}> )`
+ : ""
+ }
+ ${
+ nottags[i].max
+ ? `FILTER (?v${i} ${nottags[i].maxIsExclusive ? "<" : "<="} "${
+ nottags[i].max
+ }"^^<${nottags[i].type}> )`
+ : ""
+ } }`;
+ } else {
+ tagQuery += `FILTER NOT EXISTS { ?s tridoc:tag ?tag${i} .
+ ?tag${i} tridoc:label "${nottags[i].label}" . }`;
+ }
+ }
+ return await fusekiFetch(`
+PREFIX rdf:
+PREFIX s:
+PREFIX tridoc:
+PREFIX text:
+SELECT (COUNT(DISTINCT ?s) as ?count)
+WHERE {
+ ?s s:identifier ?identifier .
+ ${tagQuery}
+ ${
+ text
+ ? `{ { ?s text:query (s:name "${text}") } UNION { ?s text:query (s:text "${text}")} } .\n`
+ : ""
+ }}`).then((json) => parseInt(json.results.bindings[0].count.value, 10));
+}
+
+export async function getBasicMeta(id: string) {
+ return await fusekiFetch(`
+PREFIX rdf:
+PREFIX s:
+SELECT ?title ?date
+WHERE {
+ ?s s:identifier "${id}" .
+ ?s s:dateCreated ?date .
+ OPTIONAL { ?s s:name ?title . }
+}`).then((json) => {
+ return {
+ title: json.results.bindings[0]?.title?.value,
+ created: json.results.bindings[0]?.date?.value,
+ };
+ });
+}
+
+export async function getTagList() {
+ const query = `
+PREFIX tridoc:
+SELECT DISTINCT ?s ?label ?type
+WHERE {
+ ?s tridoc:label ?label .
+ OPTIONAL { ?s tridoc:valueType ?type . }
+}`;
+ return await fusekiFetch(query).then((json) =>
+ json.results.bindings.map((binding) => {
+ return {
+ label: binding.label.value,
+ parameter: binding.type ? { type: binding.type.value } : undefined,
+ };
+ })
+ );
+}
+
+export async function getTags(id: string) {
+ const query = `
+PREFIX tridoc:
+SELECT DISTINCT ?label ?type ?v
+ WHERE {
+ GRAPH {
+ tridoc:tag ?tag .
+ {
+ ?tag tridoc:label ?label .
+ }
+ UNION
+ {
+ ?tag tridoc:value ?v ;
+ tridoc:parameterizableTag ?ptag .
+ ?ptag tridoc:label ?label ;
+ tridoc:valueType ?type .
+ }
+ }
+}`;
+ return await fusekiFetch(query).then((json) =>
+ json.results.bindings.map((binding) => {
+ return {
+ label: binding.label.value,
+ parameter: binding.type
+ ? { type: binding.type.value, value: binding.v.value }
+ : undefined,
+ };
+ })
+ );
+}
+
+// => [label, type?][]
+export async function getTagTypes(labels: string[]) {
+ const json = await fusekiFetch(`
+PREFIX tridoc:
+SELECT DISTINCT ?l ?t WHERE { VALUES ?l { "${
+ labels.join('" "')
+ }" } ?s tridoc:label ?l . OPTIONAL { ?s tridoc:valueType ?t . } }`);
+ return json.results.bindings.map(
+ (binding) => {
+ const result_1 = [];
+ result_1[0] = binding.l.value;
+ if (binding.t) {
+ result_1[1] = binding.t.value;
+ }
+ return result_1;
+ },
+ );
+}
diff --git a/src/meta/fusekiFetch.ts b/src/meta/fusekiFetch.ts
new file mode 100644
index 0000000..afbaf5a
--- /dev/null
+++ b/src/meta/fusekiFetch.ts
@@ -0,0 +1,56 @@
+type SparqlJson = {
+ head: {
+ vars: string[];
+ };
+ results: {
+ bindings: { [key: string]: { type: string; value: string } }[];
+ };
+};
+
+export function dump(accept = "text/turtle") {
+ const query = "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }";
+ console.log((new Date()).toISOString(), "→ FUSEKI QUERY", query, "\n");
+ return fetch("http://fuseki:3030/3DOC/query", {
+ method: "POST",
+ headers: {
+ "Authorization": "Basic " + btoa("admin:pw123"),
+ "Content-Type": "application/sparql-query",
+ "Accept": accept,
+ },
+ body: query,
+ });
+}
+
+export async function fusekiFetch(query: string): Promise {
+ console.log((new Date()).toISOString(), "→ FUSEKI QUERY", query, "\n");
+ return await fetch("http://fuseki:3030/3DOC/query", {
+ method: "POST",
+ headers: {
+ "Authorization": "Basic " + btoa("admin:pw123"),
+ "Content-Type": "application/sparql-query",
+ },
+ body: query,
+ }).then(async (response) => {
+ if (response.ok) {
+ return response.json();
+ } else {
+ throw new Error("Fuseki Error: " + await response.text());
+ }
+ });
+}
+
+export async function fusekiUpdate(query: string): Promise {
+ console.log((new Date()).toISOString(), "→ FUSEKI UPDATE", query, "\n");
+ return await fetch("http://fuseki:3030/3DOC/update", {
+ method: "POST",
+ headers: {
+ "Authorization": "Basic " + btoa("admin:pw123"),
+ "Content-Type": "application/sparql-update",
+ },
+ body: query,
+ }).then(async (response) => {
+ if (!response.ok) {
+ throw new Error("Fuseki Error: " + await response.text());
+ }
+ });
+}
diff --git a/src/meta/store.ts b/src/meta/store.ts
new file mode 100644
index 0000000..ed5826c
--- /dev/null
+++ b/src/meta/store.ts
@@ -0,0 +1,116 @@
+import { fusekiUpdate } from "./fusekiFetch.ts";
+
+function escapeLiteral(string: string) {
+ return string.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(
+ /\r/g,
+ "\\r",
+ ).replace(/'/g, "\\'").replace(/"/g, '\\"');
+}
+
+export async function addComment(id: string, text: string) {
+ const now = new Date();
+ const query = `
+PREFIX rdf:
+PREFIX xsd:
+PREFIX tridoc:
+PREFIX s:
+INSERT DATA {
+ GRAPH {
+ s:comment [
+ a s:Comment ;
+ s:dateCreated "${now.toISOString()}"^^xsd:dateTime ;
+ s:text "${escapeLiteral(text)}"
+ ] .
+ }
+}`;
+ return await fusekiUpdate(query);
+}
+
+export async function addTag(
+ id: string,
+ label: string,
+ value: string,
+ type: string,
+) {
+ const tag = value
+ ? encodeURIComponent(label) + "/" + value
+ : encodeURIComponent(label);
+ const query = `
+PREFIX rdf:
+PREFIX xsd:
+PREFIX tridoc:
+PREFIX s:
+INSERT DATA {
+ GRAPH {
+ tridoc:tag .${
+ value
+ ? `
+ a tridoc:ParameterizedTag ;
+ tridoc:parameterizableTag ;
+ tridoc:value "${value}"^^<${type}> .`
+ : ""
+ }
+ }
+}`;
+ return await fusekiUpdate(query);
+}
+
+export async function addTitle(id: string, title: string) {
+ const query = `
+PREFIX rdf:
+PREFIX s:
+WITH
+DELETE { s:name ?o }
+INSERT { s:name "${escapeLiteral(title)}" }
+WHERE { OPTIONAL { s:name ?o } }`;
+ return await fusekiUpdate(query);
+}
+
+export async function createTag(
+ label: string,
+ type?:
+ | "http://www.w3.org/2001/XMLSchema#decimal"
+ | "http://www.w3.org/2001/XMLSchema#date",
+) {
+ const tagType = type ? "ParameterizableTag" : "Tag";
+ const valueType = type ? "tridoc:valueType <" + type + ">;\n" : "";
+ const query = `
+PREFIX rdf:
+PREFIX xsd:
+PREFIX tridoc:
+PREFIX s:
+INSERT DATA {
+ GRAPH {
+ rdf:type tridoc:${tagType} ;
+ ${valueType} tridoc:label "${escapeLiteral(label)}" .
+ }
+}`;
+ return await fusekiUpdate(query);
+}
+
+export function restore(turtleData: string) {
+ return fusekiUpdate(`
+CLEAR GRAPH ;
+INSERT DATA {
+ GRAPH { ${turtleData} }
+}`);
+}
+
+export async function storeDocument(
+ { id, text, date }: { id: string; text: string; date?: string },
+) {
+ const created = (date ? new Date(date) : new Date()).toISOString();
+ const query = `
+PREFIX rdf:
+PREFIX xsd:
+PREFIX s:
+INSERT DATA {
+ GRAPH {
+ rdf:type s:DigitalDocument ;
+ s:dateCreated "${created}"^^xsd:dateTime ;
+ s:identifier "${id}" ;
+ s:text "${escapeLiteral(text)}" .
+ }
+}`;
+ return await fusekiUpdate(query);
+}
diff --git a/src/server/routes.ts b/src/server/routes.ts
new file mode 100644
index 0000000..231fd6c
--- /dev/null
+++ b/src/server/routes.ts
@@ -0,0 +1,97 @@
+import { options } from "../handlers/cors.ts";
+import { count } from "../handlers/count.ts";
+import * as doc from "../handlers/doc.ts";
+import * as raw from "../handlers/raw.ts";
+import * as tag from "../handlers/tag.ts";
+import { version } from "../handlers/version.ts";
+
+export const routes: {
+ [method: string]: {
+ pattern: URLPattern;
+ handler: (request: Request, match: URLPatternResult) => Promise;
+ }[];
+} = {
+ "OPTIONS": [{
+ pattern: new URLPattern({ pathname: "*" }),
+ handler: options,
+ }],
+ "GET": [{
+ pattern: new URLPattern({ pathname: "/count" }),
+ handler: count,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc" }),
+ handler: doc.list,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id" }),
+ handler: doc.getPDF,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/comment" }),
+ handler: doc.getComments,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/tag" }),
+ handler: doc.getTags,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/thumb" }),
+ handler: doc.getThumb,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/title" }),
+ handler: doc.getTitle,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/meta" }),
+ handler: doc.getMeta,
+ }, {
+ pattern: new URLPattern({ pathname: "/raw/rdf" }),
+ handler: raw.getRDF,
+ }, {
+ pattern: new URLPattern({ pathname: "/raw/zip" }),
+ handler: raw.getZIP,
+ }, {
+ pattern: new URLPattern({ pathname: "/raw/tgz" }),
+ handler: raw.getTGZ,
+ }, {
+ pattern: new URLPattern({ pathname: "/tag" }),
+ handler: tag.getTagList,
+ }, {
+ pattern: new URLPattern({ pathname: "/tag/:tagLabel" }),
+ handler: tag.getDocs,
+ }, {
+ pattern: new URLPattern({ pathname: "/version" }),
+ handler: version,
+ }],
+ "POST": [{
+ pattern: new URLPattern({ pathname: "/doc" }),
+ handler: doc.postPDF,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/comment" }),
+ handler: doc.postComment,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/tag" }),
+ handler: doc.postTag,
+ }, {
+ pattern: new URLPattern({ pathname: "/tag" }),
+ handler: tag.createTag,
+ }],
+ "PUT": [{
+ pattern: new URLPattern({ pathname: "/doc/:id/title" }),
+ handler: doc.putTitle,
+ }, {
+ pattern: new URLPattern({ pathname: "/raw/zip" }),
+ handler: raw.putZIP,
+ }],
+ "DELETE": [{
+ pattern: new URLPattern({ pathname: "/doc/:id" }),
+ handler: doc.deleteDoc,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/tag/:tagLabel" }),
+ handler: doc.deleteTag,
+ }, {
+ pattern: new URLPattern({ pathname: "/doc/:id/title" }),
+ handler: doc.deleteTitle,
+ }, {
+ pattern: new URLPattern({ pathname: "/tag/:tagLabel" }),
+ handler: tag.deleteTag,
+ }, {
+ pattern: new URLPattern({ pathname: "/raw/rdf" }),
+ handler: raw.deleteRDFFile,
+ }],
+};
diff --git a/src/server/server.ts b/src/server/server.ts
new file mode 100644
index 0000000..f810f0a
--- /dev/null
+++ b/src/server/server.ts
@@ -0,0 +1,58 @@
+import { encode, serve as stdServe } from "../deps.ts";
+import { respond } from "../helpers/cors.ts";
+import { routes } from "./routes.ts";
+
+const isAuthenticated = (request: Request) => {
+ return (request.method === "OPTIONS") ||
+ request.headers.get("Authorization") ===
+ "Basic " + encode("tridoc:" + Deno.env.get("TRIDOC_PWD"));
+};
+
+const handler = async (request: Request): Promise => {
+ const path = request.url.slice(request.url.indexOf("/", "https://".length));
+ console.log((new Date()).toISOString(), request.method, path);
+ try {
+ if (!isAuthenticated(request)) {
+ console.log(
+ (new Date()).toISOString(),
+ request.method,
+ path,
+ "→ 401: Not Authenticated",
+ );
+ return respond("401 Not Authenticated", {
+ status: 401,
+ headers: { "WWW-Authenticate": "Basic" },
+ });
+ }
+
+ const route = routes[request.method]?.find(({ pattern }) =>
+ pattern.test(request.url)
+ );
+ if (route) {
+ return await route.handler(request, route.pattern.exec(request.url)!);
+ }
+
+ console.log(
+ (new Date()).toISOString(),
+ request.method,
+ path,
+ "→ 404: Path not found",
+ );
+ return respond("404 Path not found", { status: 404 });
+ } catch (error) {
+ let message;
+ if (error instanceof Deno.errors.PermissionDenied) {
+ message = "Got “Permission Denied” trying to access the file on disk.\n\n Please run ```docker exec -u 0 [name of backend-container] chmod -R a+r ./blobs/ rdf.ttl``` on the host server to fix this and similar issues for the future."
+ }
+ console.log(
+ (new Date()).toISOString(),
+ request.method,
+ path,
+ "→ 500:",
+ error,
+ );
+ return respond("500 " + (message || error), { status: 500 });
+ }
+};
+
+export const serve = () => stdServe(handler, { onListen: undefined });
diff --git a/yarn.lock b/yarn.lock
deleted file mode 100644
index 1ad8e52..0000000
--- a/yarn.lock
+++ /dev/null
@@ -1,624 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-accept@3.x.x:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/accept/-/accept-3.0.2.tgz#83e41cec7e1149f3fd474880423873db6c6cc9ac"
- dependencies:
- boom "7.x.x"
- hoek "5.x.x"
-
-adm-zip@^0.4.16:
- version "0.4.16"
- resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365"
- integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==
-
-ajv-keywords@^3.1.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
-
-ajv@^6.1.0:
- version "6.5.2"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360"
- dependencies:
- fast-deep-equal "^2.0.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.1"
-
-ammo@3.x.x:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/ammo/-/ammo-3.0.1.tgz#c79ceeac36fb4e55085ea3fe0c2f42bfa5f7c914"
- dependencies:
- hoek "5.x.x"
-
-archiver-utils@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2"
- integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==
- dependencies:
- glob "^7.1.4"
- graceful-fs "^4.2.0"
- lazystream "^1.0.0"
- lodash.defaults "^4.2.0"
- lodash.difference "^4.5.0"
- lodash.flatten "^4.4.0"
- lodash.isplainobject "^4.0.6"
- lodash.union "^4.6.0"
- normalize-path "^3.0.0"
- readable-stream "^2.0.0"
-
-archiver@^3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/archiver/-/archiver-3.1.1.tgz#9db7819d4daf60aec10fe86b16cb9258ced66ea0"
- integrity sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==
- dependencies:
- archiver-utils "^2.1.0"
- async "^2.6.3"
- buffer-crc32 "^0.2.1"
- glob "^7.1.4"
- readable-stream "^3.4.0"
- tar-stream "^2.1.0"
- zip-stream "^2.1.2"
-
-async@^2.6.3:
- version "2.6.3"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
- integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
- dependencies:
- lodash "^4.17.14"
-
-b64@4.x.x:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/b64/-/b64-4.0.0.tgz#c37f587f0a383c7019e821120e8c3f58f0d22772"
-
-balanced-match@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
- integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
-
-base64-js@^1.0.2:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
- integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
-
-big-time@2.x.x:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/big-time/-/big-time-2.0.1.tgz#68c7df8dc30f97e953f25a67a76ac9713c16c9de"
-
-big.js@^3.1.3:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
-
-bl@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88"
- integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A==
- dependencies:
- readable-stream "^3.0.1"
-
-boom@7.x.x:
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/boom/-/boom-7.2.0.tgz#2bff24a55565767fde869ec808317eb10c48e966"
- dependencies:
- hoek "5.x.x"
-
-bounce@1.x.x:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/bounce/-/bounce-1.2.0.tgz#e3bac68c73fd256e38096551efc09f504873c8c8"
- dependencies:
- boom "7.x.x"
- hoek "5.x.x"
-
-brace-expansion@^1.1.7:
- version "1.1.11"
- resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
- integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
- dependencies:
- balanced-match "^1.0.0"
- concat-map "0.0.1"
-
-buffer-crc32@^0.2.1, buffer-crc32@^0.2.13:
- version "0.2.13"
- resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
- integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
-
-buffer@^5.1.0:
- version "5.4.3"
- resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
- integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
- dependencies:
- base64-js "^1.0.2"
- ieee754 "^1.1.4"
-
-call@5.x.x:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/call/-/call-5.0.1.tgz#ac1b5c106d9edc2a17af2a4a4f74dd4f0c06e910"
- dependencies:
- boom "7.x.x"
- hoek "5.x.x"
-
-catbox-memory@3.x.x:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/catbox-memory/-/catbox-memory-3.1.2.tgz#4aeec1bc994419c0f7e60087f172aaedd9b4911c"
- dependencies:
- big-time "2.x.x"
- boom "7.x.x"
- hoek "5.x.x"
-
-catbox@10.x.x:
- version "10.0.2"
- resolved "https://registry.yarnpkg.com/catbox/-/catbox-10.0.2.tgz#e6ac1f35102d1a9bd07915b82e508d12b50a8bfa"
- dependencies:
- boom "7.x.x"
- bounce "1.x.x"
- hoek "5.x.x"
- joi "13.x.x"
-
-compress-commons@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-2.1.1.tgz#9410d9a534cf8435e3fbbb7c6ce48de2dc2f0610"
- integrity sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==
- dependencies:
- buffer-crc32 "^0.2.13"
- crc32-stream "^3.0.1"
- normalize-path "^3.0.0"
- readable-stream "^2.3.6"
-
-concat-map@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
- integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-content@4.x.x:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/content/-/content-4.0.5.tgz#bc547deabc889ab69bce17faf3585c29f4c41bf2"
- dependencies:
- boom "7.x.x"
-
-core-util-is@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
- integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-
-crc32-stream@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-3.0.1.tgz#cae6eeed003b0e44d739d279de5ae63b171b4e85"
- integrity sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==
- dependencies:
- crc "^3.4.4"
- readable-stream "^3.4.0"
-
-crc@^3.4.4:
- version "3.8.0"
- resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
- integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
- dependencies:
- buffer "^5.1.0"
-
-cryptiles@4.x.x:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.2.tgz#363c9ab5c859da9d2d6fb901b64d980966181184"
- dependencies:
- boom "7.x.x"
-
-emojis-list@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
-
-end-of-stream@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
- integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
- dependencies:
- once "^1.4.0"
-
-fast-deep-equal@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
-
-fast-json-stable-stringify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-
-fs-constants@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
- integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
-
-fs.realpath@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-glob@^7.1.4:
- version "7.1.4"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
- integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.0.4"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
-graceful-fs@^4.2.0:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"
- integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==
-
-hapi-auth-basic@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/hapi-auth-basic/-/hapi-auth-basic-5.0.0.tgz#0438b00225e4f7baccd7f29e04b4fc5037c012b0"
- integrity sha1-BDiwAiXk97rM1/KeBLT8UDfAErA=
- dependencies:
- boom "7.x.x"
- hoek "5.x.x"
-
-hapi@^17.5.2:
- version "17.5.2"
- resolved "https://registry.yarnpkg.com/hapi/-/hapi-17.5.2.tgz#9c5823cdcdd17e5621ebc8928aefb144d033caac"
- dependencies:
- accept "3.x.x"
- ammo "3.x.x"
- boom "7.x.x"
- bounce "1.x.x"
- call "5.x.x"
- catbox "10.x.x"
- catbox-memory "3.x.x"
- heavy "6.x.x"
- hoek "5.x.x"
- joi "13.x.x"
- mimos "4.x.x"
- podium "3.x.x"
- shot "4.x.x"
- statehood "6.x.x"
- subtext "6.x.x"
- teamwork "3.x.x"
- topo "3.x.x"
-
-heavy@6.x.x:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/heavy/-/heavy-6.1.0.tgz#1bbfa43dc61dd4b543ede3ff87db8306b7967274"
- dependencies:
- boom "7.x.x"
- hoek "5.x.x"
- joi "13.x.x"
-
-hoek@5.x.x:
- version "5.0.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac"
-
-ieee754@^1.1.4:
- version "1.1.13"
- resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
- integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
-
-inflight@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
- dependencies:
- once "^1.3.0"
- wrappy "1"
-
-inherits@2, inherits@^2.0.3, inherits@~2.0.3:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
- integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-iron@5.x.x:
- version "5.0.4"
- resolved "https://registry.yarnpkg.com/iron/-/iron-5.0.4.tgz#003ed822f656f07c2b62762815f5de3947326867"
- dependencies:
- boom "7.x.x"
- cryptiles "4.x.x"
- hoek "5.x.x"
-
-isarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
- integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
-isemail@3.x.x:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.3.tgz#64f37fc113579ea12523165c3ebe3a71a56ce571"
- dependencies:
- punycode "2.x.x"
-
-joi@13.x.x:
- version "13.4.0"
- resolved "https://registry.yarnpkg.com/joi/-/joi-13.4.0.tgz#afc359ee3d8bc5f9b9ba6cdc31b46d44af14cecc"
- dependencies:
- hoek "5.x.x"
- isemail "3.x.x"
- topo "3.x.x"
-
-json-schema-traverse@^0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
-
-json5@^0.5.0:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
-
-lazystream@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
- integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=
- dependencies:
- readable-stream "^2.0.5"
-
-loader-utils@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
- dependencies:
- big.js "^3.1.3"
- emojis-list "^2.0.0"
- json5 "^0.5.0"
-
-lodash.defaults@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
- integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
-
-lodash.difference@^4.5.0:
- version "4.5.0"
- resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
- integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=
-
-lodash.flatten@^4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
- integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
-
-lodash.isplainobject@^4.0.6:
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
- integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
-
-lodash.union@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
- integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
-
-lodash@^4.17.14:
- version "4.17.15"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
- integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
-
-mime-db@1.x.x:
- version "1.35.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47"
-
-mimos@4.x.x:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/mimos/-/mimos-4.0.0.tgz#76e3d27128431cb6482fd15b20475719ad626a5a"
- dependencies:
- hoek "5.x.x"
- mime-db "1.x.x"
-
-minimatch@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
- integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
- dependencies:
- brace-expansion "^1.1.7"
-
-nanoid@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-1.1.0.tgz#b18e806e1cdbfdbe030374d5cf08a48cbc80b474"
-
-nigel@3.x.x:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/nigel/-/nigel-3.0.1.tgz#48a08859d65177312f1c25af7252c1e07bb07c2a"
- dependencies:
- hoek "5.x.x"
- vise "3.x.x"
-
-node-ensure@^0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
-
-node-fetch@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5"
-
-normalize-path@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
- integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
-once@^1.3.0, once@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
- dependencies:
- wrappy "1"
-
-path-is-absolute@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-pdfjs-dist@^2.0.489:
- version "2.0.489"
- resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.0.489.tgz#63e54b292a86790a454697eb44d4347b8fbfad27"
- dependencies:
- node-ensure "^0.0.0"
- worker-loader "^1.1.1"
-
-pez@4.x.x:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/pez/-/pez-4.0.2.tgz#0a7c81b64968e90b0e9562b398f390939e9c4b53"
- dependencies:
- b64 "4.x.x"
- boom "7.x.x"
- content "4.x.x"
- hoek "5.x.x"
- nigel "3.x.x"
-
-podium@3.x.x:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/podium/-/podium-3.1.2.tgz#b701429739cf6bdde6b3015ae6b48d400817ce9e"
- dependencies:
- hoek "5.x.x"
- joi "13.x.x"
-
-process-nextick-args@~2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
- integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
-punycode@2.x.x, punycode@^2.1.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
-
-readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.3.6:
- version "2.3.6"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
- integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
- dependencies:
- core-util-is "~1.0.0"
- inherits "~2.0.3"
- isarray "~1.0.0"
- process-nextick-args "~2.0.0"
- safe-buffer "~5.1.1"
- string_decoder "~1.1.1"
- util-deprecate "~1.0.1"
-
-readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
- integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
- dependencies:
- inherits "^2.0.3"
- string_decoder "^1.1.1"
- util-deprecate "^1.0.1"
-
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-safe-buffer@~5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
- integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
-
-schema-utils@^0.4.0:
- version "0.4.5"
- resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e"
- dependencies:
- ajv "^6.1.0"
- ajv-keywords "^3.1.0"
-
-shot@4.x.x:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/shot/-/shot-4.0.5.tgz#c7e7455d11d60f6b6cd3c43e15a3b431c17e5566"
- dependencies:
- hoek "5.x.x"
- joi "13.x.x"
-
-statehood@6.x.x:
- version "6.0.6"
- resolved "https://registry.yarnpkg.com/statehood/-/statehood-6.0.6.tgz#0dbd7c50774d3f61a24e42b0673093bbc81fa5f0"
- dependencies:
- boom "7.x.x"
- bounce "1.x.x"
- cryptiles "4.x.x"
- hoek "5.x.x"
- iron "5.x.x"
- joi "13.x.x"
-
-string_decoder@^1.1.1:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
- integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
- dependencies:
- safe-buffer "~5.2.0"
-
-string_decoder@~1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
- integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
- dependencies:
- safe-buffer "~5.1.0"
-
-subtext@6.x.x:
- version "6.0.7"
- resolved "https://registry.yarnpkg.com/subtext/-/subtext-6.0.7.tgz#8e40a67901a734d598142665c90e398369b885f9"
- dependencies:
- boom "7.x.x"
- content "4.x.x"
- hoek "5.x.x"
- pez "4.x.x"
- wreck "14.x.x"
-
-tar-stream@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3"
- integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw==
- dependencies:
- bl "^3.0.0"
- end-of-stream "^1.4.1"
- fs-constants "^1.0.0"
- inherits "^2.0.3"
- readable-stream "^3.1.1"
-
-teamwork@3.x.x:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/teamwork/-/teamwork-3.0.1.tgz#ff38c7161f41f8070b7813716eb6154036ece196"
-
-topo@3.x.x:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.0.tgz#37e48c330efeac784538e0acd3e62ca5e231fe7a"
- dependencies:
- hoek "5.x.x"
-
-uri-js@^4.2.1:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
- dependencies:
- punycode "^2.1.0"
-
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
- integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-
-vise@3.x.x:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/vise/-/vise-3.0.0.tgz#76ad14ab31669c50fbb0817bc0e72fedcbb3bf4c"
- dependencies:
- hoek "5.x.x"
-
-worker-loader@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-1.1.1.tgz#920d74ddac6816fc635392653ed8b4af1929fd92"
- dependencies:
- loader-utils "^1.0.0"
- schema-utils "^0.4.0"
-
-wrappy@1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
- integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-wreck@14.x.x:
- version "14.0.2"
- resolved "https://registry.yarnpkg.com/wreck/-/wreck-14.0.2.tgz#89c17a9061c745ed1c3aebcb66ea181dbaab454c"
- dependencies:
- boom "7.x.x"
- hoek "5.x.x"
-
-zip-stream@^2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-2.1.2.tgz#841efd23214b602ff49c497cba1a85d8b5fbc39c"
- integrity sha512-ykebHGa2+uzth/R4HZLkZh3XFJzivhVsjJt8bN3GvBzLaqqrUdRacu+c4QtnUgjkkQfsOuNE1JgLKMCPNmkKgg==
- dependencies:
- archiver-utils "^2.1.0"
- compress-commons "^2.1.1"
- readable-stream "^3.4.0"