From 8edbc95e2152de8b9c06798d490ea602421396c0 Mon Sep 17 00:00:00 2001 From: Mike Arpaia Date: Sun, 14 Jun 2026 10:19:38 -0600 Subject: [PATCH 1/2] Update docs and environment variable defaults for local development --- README.md | 167 +++++++++++++----- backend/README.md | 38 +++- .../synbiohub/sbh3/mapper/InstanceMapper.java | 12 +- .../synbiohub/sbh3/services/UserService.java | 4 +- backend/src/main/resources/config.json | 4 +- frontend/.env.development | 4 +- frontend/README.md | 22 ++- package.json | 10 ++ 8 files changed, 198 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index b65ec87a..4e6f9202 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,147 @@ # Getting Started -## Frontend -Download or clone the repository on github. Then, make sure to install -all dependencies by running the following command in your terminal in the /frontend directory: + +SynBioHub3 runs as two separate processes: + +- **`frontend/`** — a Next.js app served on port **3333** +- **`backend/`** — a Java (Spring Boot) app served on port **6789** + +The backend also needs a **Virtuoso** triplestore (a SPARQL endpoint) to be useful. + +For full, component-specific instructions see [`frontend/README.md`](./frontend/README.md) +and [`backend/README.md`](./backend/README.md). + +## Quick start (one command) + +From the repository root you can start Virtuoso, the backend, and the frontend +together: + ```bash -yarn install -# or -npm install +npm install # one time — installs the root dev tooling (concurrently) +npm run dev # or: yarn dev ``` -In order to connect the frontend to your backend, go to the next.config.js file. -Then, change the env backendUrl variable to your backend's url. +Requirements: **Docker** (for Virtuoso), **Java 17**, **Node 17+**, and **Yarn**. + +`npm run dev` will: -Finally, run the development server: +1. Start a Virtuoso triplestore in Docker — creating the `virtuoso` container the + first time and reusing it afterwards (see `db:up` / `db:down` below). +2. Run the backend (`./mvnw spring-boot:run`) on **http://localhost:6789**. +3. Run the frontend (`yarn devNextGen`, the OpenSSL-legacy mode required by Node + 17+) on **http://localhost:3333**. + +Output from both services is shown together, prefixed with `backend`/`frontend`. +Press `Ctrl+C` to stop the backend and frontend. The Virtuoso container is left +running so the next start is fast; stop it explicitly with: ```bash -npm run dev -# or -yarn dev +npm run db:down # docker stop virtuoso ``` -NOTE, we use the legacy openssl with security vulnerabilities. This makes the dev command only work on mac and linux and introduces security vulnerablilites, to read more see [here](https://stackoverflow.com/questions/69692842/error-message-error0308010cdigital-envelope-routinesunsupported). +> The root `dev` script runs the frontend via `yarn devNextGen`, which requires +> Node 17+. On Node 14–16 (matching the Docker image) that flag is rejected — run +> the services separately as described below and use `yarn dev` in `frontend/`. -Open [http://localhost:3333](http://localhost:3333) with your browser to see the result. +To run each piece by hand (or on Node 14–16), follow the component sections below. +Start the backend first. -### Developer Notes +## Backend (port 6789) -Each component/page in SynBioHub should have a header which dicatates its purpose. +Requirements: **Java 17** (the project targets 17 — newer JDKs are not guaranteed to +work with Spring Boot 3.0.1) and **Docker** (for Virtuoso). -This app utilizes Redux to handle global application state and simplify the passing of -deeply nested props. To view how this application uses Redux, see the /redux directory. +1. Start a Virtuoso triplestore on port 8890: -This app uses eslint that is set up for React/Next.js code. To run the linter, navigate to -the frontend directory in your terminal (the directory this README is in) and run the command: -``` -npm run lint -``` -If you'd like to format all code and fix minor styling errors (recommened before pushing anything -to the directory), run the command: -``` -npm run lint.fix -``` + ```bash + docker run --name virtuoso -d \ + -p 8890:8890 -p 1111:1111 \ + -e DBA_PASSWORD=dba -e SPARQL_UPDATE=true \ + tenforce/virtuoso:virtuoso7.2.5 + ``` -### Next.js Resources +2. No extra configuration is needed — the bundled + `backend/src/main/resources/config.json` already points at `localhost:8890`, + matching the Virtuoso container above. If your Virtuoso runs elsewhere, override + the endpoints by creating `backend/data/config.local.json` (gitignored, takes + precedence per-key): -To learn more about Next.js, take a look at the following resources: + ```json + { + "sparqlEndpoint": "http://your-host:8890/sparql", + "graphStoreEndpoint": "http://your-host:8890/sparql-graph-crud-auth/" + } + ``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +3. Run the backend from the `backend/` directory (config is read relative to the + working directory). This uses the bundled Maven wrapper and the embedded H2 + database — no external database required: + + ```bash + cd backend + ./mvnw spring-boot:run + ``` + + The backend is now serving on http://localhost:6789. + +## Frontend (port 3333) + +Requirements: **Node 14–16** and **Yarn**. (The Docker image uses Node 14; newer +Node versions need the OpenSSL workaround in step 3.) +1. Install dependencies: -## Backend -### Enviroment Setup -[IntelliJ IDEA](https://www.jetbrains.com/idea/download/) is highly recommended for backend development. The community edition is free to download. -Make sure that a JDK is available on your machine. Java version 11 or greater is recommended. If you do not have a JDK, go [here](https://www.azul.com/downloads/?package=jdk#download-openjdk) and download one for your system. Java 17 LTS is recommended. + ```bash + cd frontend + yarn install + ``` -## Project Setup -Once you have finished installing the developer tools, clone the repository and open it as a project in IntelliJ. To setup your JDK, go to File->Project Structure->Project->ProjectSDK and click on your downloaded Java JDK. +2. Tell the frontend where the backend is. It reads the backend URL from the + `backend` environment variable, loaded in dev from `frontend/.env.development`, + which ships pointing at this repo's backend (`http://localhost:6789`). Edit that + file if your backend runs elsewhere. (`next.config.js` exposes it via + `publicRuntimeConfig` — there is no `backendUrl` variable to edit.) -## Running -You can run the application by right clicking the `Synbiohub3Application` class and clicking run. On later runs, simply clikc the run or debut icons at the top right of your screen. +3. Start the dev server: -## Troubleshooting -If IntelliJ is warning about packages not being found, right click on the `pom.xml` file and click Maven->Reload Project. This should download all required dependencies to run the application. + ```bash + yarn dev + ``` + + On Node 17+ this fails with an OpenSSL error (`ERR_OSSL_EVP_UNSUPPORTED`) because + Next.js 10 / webpack 4 use a legacy algorithm. Use the legacy-OpenSSL script + instead: + + ```bash + yarn devNextGen + ``` + + Open [http://localhost:3333](http://localhost:3333) in your browser. + +## Run everything with Docker (prebuilt images) + +`tests/docker-compose.yml` starts Virtuoso, the backend, and the frontend from +published images (not your local code) — handy for a quick instance, not for +development: + +```bash +cd tests && docker compose up +``` + +## Developer Notes + +Each component/page in SynBioHub should have a header which dictates its purpose. + +The frontend uses Redux for global application state and to simplify passing deeply +nested props; see the `frontend/redux` directory. + +The frontend uses ESLint configured for React/Next.js. From the `frontend` directory: + +```bash +npm run lint # check for problems +npm run lint.fix # auto-fix styling errors (recommended before pushing) +``` + +### Next.js Resources + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. diff --git a/backend/README.md b/backend/README.md index 57ff3a96..fea713d4 100644 --- a/backend/README.md +++ b/backend/README.md @@ -41,14 +41,27 @@ Open ``backend`` as a project in IntelliJ. To setup your JDK, go to ``File``->``Project Structure``->``Project``->``ProjectSDK`` and click on your downloaded Java JDK. -If you're not using IntelliJ: -To build a JAR file using maven, navigate to the ``backend`` directory and run: +If you're not using IntelliJ, use the bundled Maven wrapper (`./mvnw`) — a +separate Maven install is not required. From the ``backend`` directory: -``mvn package`` +Run directly (recommended for development): -and run the JAR file with +``./mvnw spring-boot:run`` -``java -jar target/{name of JAR}`` +Or build a JAR and run it: + +``./mvnw package`` + +``java -jar target/sbh3-0.0.1-SNAPSHOT-exec.jar`` + +The backend serves on **http://localhost:6789** and uses an embedded H2 database +by default, so no external database is needed. (The `local` Spring profile in +`application-local.yml` switches to PostgreSQL on `:5432`; activate it with +`./mvnw spring-boot:run -Dspring-boot.run.profiles=local` only if you want that.) + +**Important:** the backend reads `src/main/resources/config.json` relative to its +working directory, so it must be run from the ``backend`` directory (IntelliJ users +already set this in the working-directory step above). #### _Running_ @@ -65,6 +78,21 @@ The main class is the ``Synbiohub3Application`` class. Note: If packages or dependencies aren't found, click on ``Maven`` in the top right corner and click ``Reload all Maven projects``. +#### _Connecting to Virtuoso (local development)_ + +The backend needs a Virtuoso triplestore for SPARQL queries (set up below). The +bundled `src/main/resources/config.json` points at `localhost:8890`, so a Virtuoso +running on the host needs no extra configuration. If your Virtuoso runs elsewhere, +override the endpoints by creating `backend/data/config.local.json` (this path is +gitignored and takes precedence over `config.json` on a per-key basis): + +```json +{ + "sparqlEndpoint": "http://your-host:8890/sparql", + "graphStoreEndpoint": "http://your-host:8890/sparql-graph-crud-auth/" +} +``` + ## **Installing the Backend from Docker** Docker is recommended for normal use and testing. diff --git a/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java b/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java index 84ba102b..606e38e0 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java +++ b/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java @@ -20,9 +20,9 @@ public interface InstanceMapper { @Mapping(target = "configuration.theme.themeName", defaultValue = "default") @Mapping(source = "instanceURL", target = "instanceUri") @Mapping(source = "frontPageText", target = "description") - @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://virtuoso:8890/sparql") - @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://virtuoso:8890/sparql") - @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://virtuoso:8890/sparql-graph-crud-auth/") + @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://localhost:8890/sparql") + @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://localhost:8890/sparql") + @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://localhost:8890/sparql-graph-crud-auth/") @Mapping(target = "configuration.triplestore.graphPrefix", defaultValue = "https://synbiohub.org/") @Mapping(target = "configuration.triplestore.isql", defaultValue = "isql-vt") @Mapping(source = "virtuosoINI", target = "configuration.triplestore.virtuosoINI") @@ -36,9 +36,9 @@ public interface InstanceMapper { @Mapping(target = "configuration.theme.themeName", defaultValue = "default") @Mapping(source = "instanceURL", target = "instanceUri") @Mapping(source = "frontPageText", target = "description") - @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://virtuoso:8890/sparql") - @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://virtuoso:8890/sparql") - @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://virtuoso:8890/sparql-graph-crud-auth/") + @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://localhost:8890/sparql") + @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://localhost:8890/sparql") + @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://localhost:8890/sparql-graph-crud-auth/") @Mapping(target = "configuration.triplestore.graphPrefix", defaultValue = "https://synbiohub.org/") @Mapping(target = "configuration.triplestore.isql", defaultValue = "isql-vt") @Mapping(source = "virtuosoINI", target = "configuration.triplestore.virtuosoINI") diff --git a/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java b/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java index d156ceb3..3c4a90d7 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java @@ -366,8 +366,8 @@ public String setupInstance(Map allParams) { try { if (file.createNewFile()) { - allParams.put("sparqlEndpoint", "http://virtuoso3:8890/sparql"); - allParams.put("graphStoreEndpoint", "http://virtuoso3:8890/sparql-graph-crud-auth/"); + allParams.put("sparqlEndpoint", "http://localhost:8890/sparql"); + allParams.put("graphStoreEndpoint", "http://localhost:8890/sparql-graph-crud-auth/"); allParams.put("firstLaunch", false); allParams.put("version", 1); Map wor = new HashMap<>(); diff --git a/backend/src/main/resources/config.json b/backend/src/main/resources/config.json index ddd59681..ace7ea39 100644 --- a/backend/src/main/resources/config.json +++ b/backend/src/main/resources/config.json @@ -12,8 +12,8 @@ }, "webOfRegistriesUrl" : "https://wor.synbiohub.org", "port": 6789, - "sparqlEndpoint": "http://virtuoso3:8890/sparql", - "graphStoreEndpoint": "http://virtuoso3:8890/sparql-graph-crud-auth/", + "sparqlEndpoint": "http://localhost:8890/sparql", + "graphStoreEndpoint": "http://localhost:8890/sparql-graph-crud-auth/", "defaultGraph": "https://synbiohub.org/public", "graphPrefix": "https://synbiohub.org/", "username": "dba", diff --git a/frontend/.env.development b/frontend/.env.development index 0734af5a..08697a0f 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,2 +1,2 @@ -backend="http://localhost:7777" -backendSS="http://localhost:7777" \ No newline at end of file +backend="http://localhost:6789" +backendSS="http://localhost:6789" \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index fbc01a5b..b0c3931f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -19,17 +19,31 @@ yarn install npm install ``` -In order to connect the frontend to your backend, go to the next.config.js file. -Then, change the env backendUrl variable to your backend's url. +In order to connect the frontend to your backend, set the `backend` environment +variable to your backend's URL. In development this is read from `.env.development`, +which ships pointing at this repo's backend (`http://localhost:6789`). Edit that +file if your backend runs elsewhere. (`backendSS` is the server-side equivalent and +should normally match `backend`.) Note: `next.config.js` exposes these via +`publicRuntimeConfig` — there is no `backendUrl` variable to change. Finally, run the development server: ```bash -npm run dev -# or yarn dev ``` +If you are on Node 17 or newer, `yarn dev` fails with an OpenSSL error +(`ERR_OSSL_EVP_UNSUPPORTED`), because Next.js 10 / webpack 4 rely on a legacy +algorithm. Use the legacy-OpenSSL script instead: + +```bash +yarn devNextGen +``` + +The recommended Node version is 14–16 (the Docker image uses Node 14), with which +plain `yarn dev` works. Once running, open +[http://localhost:3333](http://localhost:3333) in your browser. + ### Installing and running through Docker Prerequisites - Install following software: diff --git a/package.json b/package.json index aa798ff7..fccb62f5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,15 @@ { + "name": "synbiohub3", + "private": true, + "scripts": { + "dev": "npm run db:up && concurrently --names backend,frontend --prefix-colors blue,green --kill-others-on-fail \"npm run dev:backend\" \"npm run dev:frontend\"", + "dev:backend": "cd backend && ./mvnw spring-boot:run", + "dev:frontend": "cd frontend && yarn devNextGen", + "db:up": "docker start virtuoso 2>/dev/null || docker run --name virtuoso -d -p 8890:8890 -p 1111:1111 -e DBA_PASSWORD=dba -e SPARQL_UPDATE=true tenforce/virtuoso:virtuoso7.2.5", + "db:down": "docker stop virtuoso" + }, "devDependencies": { + "concurrently": "^10.0.3", "eslint-plugin-unused-imports": "^4.1.4" } } From 2e368af817e3c391db58ba847e796328ff38562a Mon Sep 17 00:00:00 2001 From: Mike Arpaia Date: Sun, 14 Jun 2026 14:03:53 -0600 Subject: [PATCH 2/2] Add ability to define config values via environment variables --- README.md | 25 ++++--- backend/README.md | 31 +++++++- .../synbiohub/sbh3/mapper/InstanceMapper.java | 12 +-- .../synbiohub/sbh3/services/UserService.java | 4 +- .../com/synbiohub/sbh3/utils/ConfigUtil.java | 75 +++++++++++++++++++ backend/src/main/resources/config.json | 4 +- package.json | 2 +- 7 files changed, 127 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 4e6f9202..dbb5d27e 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,22 @@ work with Spring Boot 3.0.1) and **Docker** (for Virtuoso). tenforce/virtuoso:virtuoso7.2.5 ``` -2. No extra configuration is needed — the bundled - `backend/src/main/resources/config.json` already points at `localhost:8890`, - matching the Virtuoso container above. If your Virtuoso runs elsewhere, override - the endpoints by creating `backend/data/config.local.json` (gitignored, takes - precedence per-key): - - ```json - { - "sparqlEndpoint": "http://your-host:8890/sparql", - "graphStoreEndpoint": "http://your-host:8890/sparql-graph-crud-auth/" - } +2. Point the backend at that Virtuoso. The bundled + `backend/src/main/resources/config.json` defaults to the `virtuoso3` Docker host + (used when the backend itself runs as a container). Running on the host, reach the + container above at `localhost` by exporting two environment variables — they take + precedence over `config.json` without editing it: + + ```bash + export SBH_SPARQL_ENDPOINT="http://localhost:8890/sparql" + export SBH_GRAPH_STORE_ENDPOINT="http://localhost:8890/sparql-graph-crud-auth/" ``` + (`npm run dev` already sets these for you — see above. To point at a Virtuoso + running elsewhere, change the host in the variables, or create + `backend/data/config.local.json`, which is gitignored and overrides `config.json` + per-key but is itself overridden by the environment variables above.) + 3. Run the backend from the `backend/` directory (config is read relative to the working directory). This uses the bundled Maven wrapper and the embedded H2 database — no external database required: diff --git a/backend/README.md b/backend/README.md index fea713d4..55fb13de 100644 --- a/backend/README.md +++ b/backend/README.md @@ -81,10 +81,33 @@ and click ``Reload all Maven projects``. #### _Connecting to Virtuoso (local development)_ The backend needs a Virtuoso triplestore for SPARQL queries (set up below). The -bundled `src/main/resources/config.json` points at `localhost:8890`, so a Virtuoso -running on the host needs no extra configuration. If your Virtuoso runs elsewhere, -override the endpoints by creating `backend/data/config.local.json` (this path is -gitignored and takes precedence over `config.json` on a per-key basis): +bundled `src/main/resources/config.json` defaults to the `virtuoso3` Docker host, +because the backend is normally run as a container alongside Virtuoso on the same +Docker network. When you run the backend directly via `java`/`./mvnw` on the host, +Virtuoso is reached at `localhost` instead. To point the backend there at runtime +set these environment variables before running the backend: + +```sh +export SBH_SPARQL_ENDPOINT="http://localhost:8890/sparql" +export SBH_GRAPH_STORE_ENDPOINT="http://localhost:8890/sparql-graph-crud-auth/" +./mvnw spring-boot:run +``` + +These take precedence over both `config.json` and `config.local.json`, so the +committed Docker defaults stay intact. (In IntelliJ, add them under +`Edit Configuration` → `Environment variables`.) + +> **Any** key in `config.json` can be overridden this way: prefix the key with +> `SBH_` in upper snake case (e.g. `sparqlEndpoint` → `SBH_SPARQL_ENDPOINT`, +> `useSBOLExplorer` → `SBH_USE_SBOL_EXPLORER`, `instanceUrl` → `SBH_INSTANCE_URL`). +> Values are parsed as JSON, so types are preserved — `SBH_USE_SBOL_EXPLORER=true` +> is a boolean, `SBH_FETCH_LIMIT=1000` a number — while plain strings like a URL are +> used as-is. This is the recommended way to supply secrets (`SBH_PASSWORD`, +> `SBH_SESSION_SECRET`, …) and per-environment settings without editing committed files. + +Alternatively — or to override other settings — create +`backend/data/config.local.json` (gitignored; takes precedence over `config.json` +on a per-key basis, but is itself overridden by the environment variables above): ```json { diff --git a/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java b/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java index 606e38e0..84ba102b 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java +++ b/backend/src/main/java/com/synbiohub/sbh3/mapper/InstanceMapper.java @@ -20,9 +20,9 @@ public interface InstanceMapper { @Mapping(target = "configuration.theme.themeName", defaultValue = "default") @Mapping(source = "instanceURL", target = "instanceUri") @Mapping(source = "frontPageText", target = "description") - @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://localhost:8890/sparql") - @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://localhost:8890/sparql") - @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://localhost:8890/sparql-graph-crud-auth/") + @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://virtuoso:8890/sparql") + @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://virtuoso:8890/sparql") + @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://virtuoso:8890/sparql-graph-crud-auth/") @Mapping(target = "configuration.triplestore.graphPrefix", defaultValue = "https://synbiohub.org/") @Mapping(target = "configuration.triplestore.isql", defaultValue = "isql-vt") @Mapping(source = "virtuosoINI", target = "configuration.triplestore.virtuosoINI") @@ -36,9 +36,9 @@ public interface InstanceMapper { @Mapping(target = "configuration.theme.themeName", defaultValue = "default") @Mapping(source = "instanceURL", target = "instanceUri") @Mapping(source = "frontPageText", target = "description") - @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://localhost:8890/sparql") - @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://localhost:8890/sparql") - @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://localhost:8890/sparql-graph-crud-auth/") + @Mapping(target = "configuration.triplestore.defaultGraph", defaultValue = "http://virtuoso:8890/sparql") + @Mapping(target = "configuration.triplestore.sparqlEndpoint", defaultValue = "http://virtuoso:8890/sparql") + @Mapping(target = "configuration.triplestore.graphStoreEndpoint", defaultValue = "http://virtuoso:8890/sparql-graph-crud-auth/") @Mapping(target = "configuration.triplestore.graphPrefix", defaultValue = "https://synbiohub.org/") @Mapping(target = "configuration.triplestore.isql", defaultValue = "isql-vt") @Mapping(source = "virtuosoINI", target = "configuration.triplestore.virtuosoINI") diff --git a/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java b/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java index 3c4a90d7..d156ceb3 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java @@ -366,8 +366,8 @@ public String setupInstance(Map allParams) { try { if (file.createNewFile()) { - allParams.put("sparqlEndpoint", "http://localhost:8890/sparql"); - allParams.put("graphStoreEndpoint", "http://localhost:8890/sparql-graph-crud-auth/"); + allParams.put("sparqlEndpoint", "http://virtuoso3:8890/sparql"); + allParams.put("graphStoreEndpoint", "http://virtuoso3:8890/sparql-graph-crud-auth/"); allParams.put("firstLaunch", false); allParams.put("version", 1); Map wor = new HashMap<>(); diff --git a/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java b/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java index 5208a480..74692e86 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java +++ b/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java @@ -1,15 +1,19 @@ package com.synbiohub.sbh3.utils; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; @Slf4j @Component @@ -19,9 +23,73 @@ public class ConfigUtil { private static JsonNode localjson; private static ObjectMapper mapper = new ObjectMapper(); + /** + * Prefix marking environment variables that override config values. Any key in + * {@code config.json} (or {@code config.local.json}) can be set this way — e.g. + * {@code SBH_SPARQL_ENDPOINT} overrides {@code sparqlEndpoint}. Environment variables take + * precedence over both config files, so settings such as the triplestore location can be + * supplied at runtime without editing either file. This is what lets the backend run directly + * via {@code java} against a Virtuoso on {@code localhost} while the committed defaults target + * the {@code virtuoso} Docker host. + */ + private static final String ENV_PREFIX = "SBH_"; + + /** + * Overrides keyed by the normalized config key (lower-cased, non-alphanumerics stripped) so + * that {@code SBH_SPARQL_ENDPOINT} matches {@code sparqlEndpoint} and {@code SBH_USE_SBOL_EXPLORER} + * matches {@code useSBOLExplorer} regardless of word boundaries. Built once from the process + * environment, which is immutable at runtime. + */ + private static Map envOverrides = Map.of(); + + private static String normalizeKey(String key) { + return key == null ? "" : key.toLowerCase().replaceAll("[^a-z0-9]", ""); + } + + /** + * Builds the {@link #envOverrides} index. Each {@code SBH_}-prefixed variable's value is parsed + * as JSON so booleans/numbers/objects keep their type (e.g. {@code SBH_USE_SBOL_EXPLORER=true} + * becomes a boolean, {@code SBH_FETCH_LIMIT=1000} a number). Values that are not valid JSON — + * a bare URL, for instance — are kept as plain strings. + */ + private static Map loadEnvOverrides() { + Map overrides = new HashMap<>(); + for (Map.Entry entry : System.getenv().entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + if (name == null || !name.startsWith(ENV_PREFIX) || value == null || value.isBlank()) { + continue; + } + String normalized = normalizeKey(name.substring(ENV_PREFIX.length())); + if (!normalized.isEmpty()) { + overrides.put(normalized, parseEnvValue(value)); + } + } + return overrides; + } + + private static JsonNode parseEnvValue(String value) { + try (JsonParser parser = mapper.getFactory().createParser(value)) { + JsonNode node = mapper.readTree(parser); + // Require the whole value to be valid JSON; otherwise (e.g. "150mb", a URL) keep it as text. + if (node != null && !node.isNull() && !node.isMissingNode() && parser.nextToken() == null) { + return node; + } + } catch (IOException ignored) { + // not JSON — fall through to a plain string + } + return TextNode.valueOf(value); + } + + private static JsonNode getEnvOverride(String key) { + JsonNode value = envOverrides.get(normalizeKey(key)); + return (value != null && !value.isNull()) ? value : null; + } + public ConfigUtil() { localjson = null; json = null; + envOverrides = loadEnvOverrides(); try { json = mapper.readValue(new File("src/main/resources/config.json"), JsonNode.class); if (Files.exists(new File("data/config.local.json").toPath())) { @@ -38,6 +106,13 @@ public static JsonNode get(String key) throws IOException { if (key.isEmpty()) return json; + + // Environment variables take precedence over both config files (see ENV_PREFIX). + JsonNode envValue = getEnvOverride(key); + if (envValue != null) { + return envValue; + } + try { var item = localjson.get(key); // TODO: need to rebuild the object into memory every time it is written to if (item != null && !item.isNull()) { diff --git a/backend/src/main/resources/config.json b/backend/src/main/resources/config.json index ace7ea39..ddd59681 100644 --- a/backend/src/main/resources/config.json +++ b/backend/src/main/resources/config.json @@ -12,8 +12,8 @@ }, "webOfRegistriesUrl" : "https://wor.synbiohub.org", "port": 6789, - "sparqlEndpoint": "http://localhost:8890/sparql", - "graphStoreEndpoint": "http://localhost:8890/sparql-graph-crud-auth/", + "sparqlEndpoint": "http://virtuoso3:8890/sparql", + "graphStoreEndpoint": "http://virtuoso3:8890/sparql-graph-crud-auth/", "defaultGraph": "https://synbiohub.org/public", "graphPrefix": "https://synbiohub.org/", "username": "dba", diff --git a/package.json b/package.json index fccb62f5..e0072d8d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "dev": "npm run db:up && concurrently --names backend,frontend --prefix-colors blue,green --kill-others-on-fail \"npm run dev:backend\" \"npm run dev:frontend\"", - "dev:backend": "cd backend && ./mvnw spring-boot:run", + "dev:backend": "cd backend && SBH_SPARQL_ENDPOINT=http://localhost:8890/sparql SBH_GRAPH_STORE_ENDPOINT=http://localhost:8890/sparql-graph-crud-auth/ ./mvnw spring-boot:run", "dev:frontend": "cd frontend && yarn devNextGen", "db:up": "docker start virtuoso 2>/dev/null || docker run --name virtuoso -d -p 8890:8890 -p 1111:1111 -e DBA_PASSWORD=dba -e SPARQL_UPDATE=true tenforce/virtuoso:virtuoso7.2.5", "db:down": "docker stop virtuoso"