Skip to content

Commit

Permalink
Extend configuration via CONFIG directory
Browse files Browse the repository at this point in the history
  • Loading branch information
nichtich committed Jun 1, 2023
1 parent 2e50d07 commit 9682962
Show file tree
Hide file tree
Showing 13 changed files with 85 additions and 56 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ dist-ssr
*.local

.env
config
40 changes: 14 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ This web service can be put in front of [JSKOS] data sources to provide RDF seri
- [Configuration](#configuration)
- [Installation](#installation)
- [Usage](#usage)
- [Configure](#configure)
- [Run Server](#run-server)
- [API](#api)
- [Related works](#related-works)
- [Maintainers](#maintainers)
Expand All @@ -36,28 +34,21 @@ npm ci

### Configuration

Create a local file `.env` with the following keys:
Instances of jskos-proxy are configured with environment variables, in local file `.env`, and files in an optional configuration directory. The following keys are supported:

- `PORT` - which port to run the service on (default: `3555`)
- `HMR_PORT` - port for Vite hot module reloading in development (default: `3556`)
- `NAMESPACE` - URI namespace of all objects served via this proxy.
Must end with a slash (default: `http://example.org/`)
- `NAMESPACE` - URI namespace of all objects served via this proxy. Must end with a slash (default: `http://example.org/`)
- `BACKEND` - JSKOS API base URL or local NDJSON file
- `LISTING` - whether to show list of vocabularies from backend API on NAMESPACE URL (enabled by default, disable with `0` or `false`)
- `TITLE` - Title of the service (default `JSKOS Proxy`)

#### Examples
A **configuration directory** can set with environment variable `CONFIG` (default: `config`). It may contain:

DANTE Vocabularies
- file `config.env` with configuration keys documented above
- directory `views` with [EJS templates](https://ejs.co/) to override default templates in directory `views`

NAMESPACE=http://uri.gbv.de/terminology/
BACKEND=https://api.dante.gbv.de/

RVK

NAMESPACE=http://uri.gbv.de/terminology/rvk/
BACKEND=https://coli-conc.gbv.de/rvk/api/
LISTING=false
Directory `examples` contains configuration directories for some known terminology services. To try out one of these example, set nothing but `CONFIG`, e.g. `CONFIG=examples/rvk`.

### Installation

Expand All @@ -76,24 +67,21 @@ See file `ecosystem.example.json` for deployment with [PM2](https://pm2.keymetri

## Usage

### Configure
For production (less verbose logging, no reload), first build the Vue front-end and then start the server:

See [configuration](#configuration) above.
```bash
npm run build
npm run start
```

### Run Server
For development with Vite:

```bash
# Development server with hot reload:
npm run dev

# Production server (less verbose logging, no hot reload):
# 1. Build production front-end
npm run build
# 2. Start the server in production mode
npm run start
```

Note about hot reload: Changes to the server (i.e. in `server.js`, files in `lib/`, or configuration in `.env`) will reload the server, but not the front-end. In that case, the front-end needs to be manually reloaded. Changes in `src/` will only reload the front-end.
Changes to the server (i.e. in `server.js`, files in `lib/`, or configuration in `.env`) will reload the server, but not the front-end. In that case, the front-end needs to be manually reloaded. Changes in `src/` will only reload the front-end.


## API

Expand Down
4 changes: 4 additions & 0 deletions examples/rvk/config.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Example of a single-vocabulary configuration
NAMESPACE=http://uri.gbv.de/terminology/rvk/
BACKEND=https://coli-conc.gbv.de/rvk/api/
LISTING=false
3 changes: 3 additions & 0 deletions examples/uri.gbv.de/config.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TITLE=GBV Terminology
NAMESPACE=http://uri.gbv.de/terminology/
BACKEND=https://api.dante.gbv.de/
12 changes: 12 additions & 0 deletions examples/uri.gbv.de/views/about.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<p>
Unter <a href="<%= namespace.pathname %>"><%= namespace %></a>
können <b>kontrollierte Vokabulare</b> (Normdateien, Klassifikationen, Thesauri...)
in einer Webansicht und als Linked Open Data abgerufen werden.
</p>
<p>
Die Daten stammen von <code><%= backend %></code> und werden mittels
<a href="<%= homepage %>"><%= name %></a> umgesetzt.
</p>
<p>
Die Darstellung und Funktionalität werden derzeit (2023) überarbeitet.
</p>
1 change: 1 addition & 0 deletions examples/uri.gbv.de/views/imprint.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a href="https://www.gbv.de/impressum">Verbundzentrale des GBV (VZG)</a>
14 changes: 12 additions & 2 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import * as dotenv from "dotenv"
import path from "node:path"
import fs from "node:fs"

const log = msg => console.log(msg)

const NODE_ENV = process.env.NODE_ENV || "development"

Expand All @@ -7,6 +11,13 @@ if (NODE_ENV !== "test") {
dotenv.config()
}

const configDir = process.env.CONFIG || "config"
const configFile = path.resolve(configDir, "config.env")
if (fs.existsSync(configFile)) {
dotenv.populate(process.env, dotenv.parse(fs.readFileSync(configFile)))
log(`Read configuration from ${configFile}`)
}

// eslint does not like: import pkg from "../package.json" assert { type: "json" }
import { readFile } from "node:fs/promises"
const fileUrl = new URL("../package.json", import.meta.url)
Expand All @@ -15,10 +26,9 @@ const pkg = JSON.parse(await readFile(fileUrl, "utf8"))
const { name, version, homepage } = pkg
const { env } = process

const log = msg => console.log(msg)

const config = {
env: NODE_ENV,
configDir,
isProduction: NODE_ENV === "production",
namespace: new URL(env.NAMESPACE || "http://example.org/"),
port: env.PORT || 3555,
Expand Down
11 changes: 7 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"axios": "^1.4.0",
"cocoda-sdk": "^3.4.5",
"cookie-parser": "^1.4.6",
"dotenv": "^16.0.3",
"dotenv": "^16.1.0",
"ejs": "^3.1.9",
"express": "^4.18.2",
"jskos-tools": "^1.0.31",
Expand Down
38 changes: 26 additions & 12 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import express from "express"
import ejs from "ejs"
import portfinder from "portfinder"
import cookieParser from "cookie-parser"
import { protocolless, uriPath, link } from "./lib/utils.js"
Expand All @@ -13,14 +14,26 @@ import jskos from "jskos-tools"
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const resolve = (p) => path.resolve(__dirname, p)
const isProduction = config.isProduction
const { log, info, namespace } = config
const { log, info, namespace, configDir } = config

const app = express()
async function init() {

app.set("views", "./views")
// configure template engine to look up views in config directory first
const views = [ path.resolve(configDir, "views"), "./views" ]
const includer = (original) => {
for (let view of views) {
const filename = path.resolve(view, original + ".ejs")
if (fs.existsSync(filename)) {
return { filename }
}
}
}
app.set("views", views)
app.engine("ejs", (path, data, cb) => ejs.renderFile(path, data, {includer}, cb))
app.set("view engine", "ejs")


app.use(cookieParser())

// serve message on root if mounted at a specific root path
Expand Down Expand Up @@ -62,13 +75,14 @@ async function init() {
// serve HTML view or info information
async function serve(req, res, vars) {
vars.source = vars.item?._source
const options = { ...config, ...vars, link }
const data = { ...config, ...vars, link }

// Locale
// Note that this has to be skipped during testing because it caused timeout issues
let locale = req.cookies.locale
if (!locale && config.env !== "test") {
for (const lang of req.get("Accept-Language").split(",")) {
const accept = req.get("Accept-Language") || ""
for (const lang of accept.split(",")) {
if (lang.startsWith("de") || lang.startsWith("en")) {
locale = lang.slice(0, 2)
break
Expand All @@ -77,29 +91,29 @@ async function init() {
locale = locale || "en"
res.cookie("locale", locale, { sameSite: "lax", path: namespace.pathname })
}
options.locale = locale
data.locale = locale

const itemLabel = jskos.prefLabel(options.item, { fallbackToUri: false, language: locale })
const itemLabel = jskos.prefLabel(data.item, { fallbackToUri: false, language: locale })
if (itemLabel) {
options.title = `${options.title} - ${itemLabel}`
data.title = `${data.title} - ${itemLabel}`
}

// replace inScheme with scheme if possible
if (scheme && jskos.compare(scheme, options.item?.inScheme?.[0])) {
options.item.inScheme[0] = scheme
if (scheme && jskos.compare(scheme, data.item?.inScheme?.[0])) {
data.item.inScheme[0] = scheme
}

if (req.query.format === "debug") {
res.json(options)
res.json(data)
} else {
if (isProduction) {
app.render("index", options, async (error, template) => {
app.render("index", data, async (error, template) => {
// inject production header from production index.html file
template = template.replace("<!--production-header-->", productionHeader)
res.set({ "Content-Type": "text/html" }).end(template)
})
} else {
res.render("index", options)
res.render("index", data)
}
}
}
Expand Down
13 changes: 3 additions & 10 deletions views/about.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
<p>
Unter <a href="<%= namespace.pathname %>"><%= namespace %></a>
können <b>kontrollierte Vokabulare</b> (Normdateien, Klassifikationen, Thesauri...)
in einer Webansicht und als Linked Open Data abgerufen werden.
</p>
<p>
Die Daten stammen von <code><%= backend %></code> und werden mittels
<a href="<%= homepage %>"><%= name %></a> umgesetzt.
</p>
<p>
Die Darstellung und Funktionalität werden derzeit (2023) überarbeitet.
This service provides terminologies at
<a href="<%= namespace.pathname %>"><%= namespace %></a>
via <code><%= backend %></code>.
</p>
2 changes: 1 addition & 1 deletion views/footer.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<footer>
<div style="float:left">
<a href="https://www.gbv.de/impressum">Verbundzentrale des GBV (VZG)</a>
<%- include("imprint") %>
</div>
<div style="float:right;text-align:right">
<% if (locals.uri) { %>
Expand Down
Empty file added views/imprint.ejs
Empty file.

0 comments on commit 9682962

Please sign in to comment.