Skip to content

Commit

Permalink
Create /enrichments endpoint with POST, GET, and GET :id (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefandesu committed Dec 3, 2024
1 parent 5bbe06e commit 1e9c7ca
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 8 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
BASE=/coli-rich/app/
LOGIN=http://localhost:3004
ENRICHMENTS_PATH=./enrichments
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ dist-ssr
.env

.docker/data

enrichments/
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ BASE=/
VITE_LOGIN_SERVER=http://localhost:3004
# Hardcoded list of allow user URIs that can perform enrichments in the backend
VITE_ALLOWED_USERS=uri1,uri2
# Local file path where submitted enrichments will be temporarily stored
ENRICHMENTS_PATH=./enrichments
```

## To-Dos
Expand Down
2 changes: 1 addition & 1 deletion src/client/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ async function submitEnrichments(ppn, suggestions) {
}
// TODO: Improve error handling further.
try {
const response = await fetch(baseUrl + "submit", options)
const response = await fetch(baseUrl + "enrichment", options)
const data = await response.json()
if (response.status === 201) {
alert("Success")
Expand Down
15 changes: 15 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,28 @@ if (login && !login.endsWith("/")) {
login += "/"
}

const enrichmentsPath = env.ENRICHMENTS_PATH || "./enrichments"

// Create folder for enrichments path if necessary
import fs from "node:fs"
try {
if (!fs.existsSync(enrichmentsPath)) {
fs.mkdirSync(enrichmentsPath)
}
} catch (err) {
console.error(`Error when trying to access/create enrichtments path ${enrichmentsPath}:`, err)
console.error(`Make sure ${enrichmentsPath} is writable and restart the application.`)
process.exit(1)
}

export default {
env: NODE_ENV,
isProduction: NODE_ENV === "production",
base: env.BASE || "/",
port: parseInt(env.PORT) || 3454,
login,
allowedUsers: (env.VITE_ALLOWED_USERS || "").split(",").filter(Boolean).map(uri => uri.trim()),
enrichmentsPath,
// methods
log,
warn: logger("warn"),
Expand Down
66 changes: 66 additions & 0 deletions src/server/enrichment-router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import express from "express"
import path from "node:path"
import fs from "node:fs"
import { createHash } from "node:crypto"

import * as auth from "./auth.js"
import * as errors from "./errors.js"
import config from "../config.js"

const router = express.Router()
export default router

function getBase(req) {
const url = new URL(`${req.protocol}://${req.get("host")}${req.originalUrl}`)
let base = `${url.origin}${url.pathname}`
if (!base.endsWith("/")) {
base += "/"
}
return base
}

router.post("/", auth.main, (req, res) => {
// Create hash of PICA patch content as id
const id = createHash("sha1").update(req.body).digest("hex")
const uri = `${getBase(req)}${id}`
res.set("Location", uri)
try {
fs.writeFileSync(path.join(config.enrichmentsPath, id), req.body)
res.status(201).json({
id,
uri,
ok: 1,
})
} catch (error) {
config.error(error)
// ? Should we differentiate between errors?
throw new errors.BackendError()
}
})

router.get("/", (req, res) => {
let base = getBase(req), enrichments = ""
for (const id of fs.readdirSync(config.enrichmentsPath)) {
const stats = fs.lstatSync(path.join(config.enrichmentsPath, id))
// Make sure it is a file
if (stats.isFile()) {
enrichments += `${base}${id} ${stats.birthtime.toISOString()}\n`
}
}
res.type("txt").send(enrichments)
})

router.get("/:id", (req, res) => {
const id = req.params.id
try {
const file = path.join(config.enrichmentsPath, id)
const created = fs.lstatSync(file).birthtime
const enrichment = fs.readFileSync(file, "utf-8")
res.set("Date", created)
res.type("txt")
res.send(enrichment)
} catch (error) {
// ? Should we differentiate between errors?
throw new errors.EntityNotFoundError(null, id)
}
})
26 changes: 26 additions & 0 deletions src/server/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,32 @@ export class ForbiddenAccessError extends Error {
}
}

export class EntityNotFoundError extends Error {
constructor(message, id) {
const prefLabel = message ? { en: message } : {
en: `The requested entity ${id} could not be found.`,
de: `Die abgefragte Entität ${id} konnte nicht gefunden werden.`,
}
message = message || prefLabel.en
super(message)
this.statusCode = 404
this.prefLabel = prefLabel
}
}

export class BackendError extends Error {
constructor(message) {
const prefLabel = message ? { en: message } : {
en: "There was an unknown error with the backend.",
de: "Es gab einen unbekannten Backend-Fehler.",
}
message = message || prefLabel.en
super(message)
this.statusCode = 500
this.prefLabel = prefLabel
}
}

export class NotImplementedError extends Error {
constructor(message) {
const prefLabel = message ? { en: message } : {
Expand Down
11 changes: 4 additions & 7 deletions src/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import express from "express"
import ViteExpress from "vite-express"
import path from "node:path"

import * as auth from "./auth.js"
import * as errors from "./errors.js"
import config from "../config.js"
import enrichmentRouter from "./enrichment-router.js"

import * as jskos from "jskos-tools"

Expand All @@ -16,12 +16,9 @@ import bodyParser from "body-parser"
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.text())

// TODO: Authenticated test endpoint, replace with actual enrichment endpoint
app.post(path.join(config.base, "/submit"), auth.main, (req) => {
// PICA data in req.body
config.log(req.body)
throw new errors.NotImplementedError()
})
const enrichmentServerPath = path.join(config.base, "/enrichment")
app.use(enrichmentServerPath, enrichmentRouter)


// Error handling
app.use((error, req, res, next) => {
Expand Down

0 comments on commit 1e9c7ca

Please sign in to comment.