Files
Tautvydas Derzinskas 358dfc1a03 feat: system notifications updates, Thingport grab in System health check, node 24+ (#52)
* feat: update system notifications

* feat: add Thingport grab to system health check

* ci: upgrade to Node.js 24 and centralize version management
2026-10-17 10:44:13 +02:00

263 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Development
How to run Thingport locally with hot reload, so you can work _on_ it. If you just want to run an
instance, follow the Docker Compose instructions in the [README](../README.md) instead.
## What's in the repo
| Directory | What it is |
| ------------ | ---------------------------------------------------------------------------- |
| `backend/` | Express + Prisma API (TypeScript). Serves everything under `/api/*`. |
| `frontend/` | React + Vite single-page app. |
| `extension/` | "Thingport Grab" browser extension -- TypeScript + SCSS, built with esbuild. |
| `bridge/` | Slicer bridge helper app (Go). |
| `docs/` | This documentation. |
## Prerequisites
- **Node.js 24 or newer.** The root `.nvmrc` says 24 (`nvm use` picks it up), and CI and the Docker images run on it.
- **Docker**, to run Postgres. Everything else runs natively so you get hot reload.
- **Go 1.21+** -- only if you're changing `bridge/`.
## 1. Install dependencies
```bash
git clone https://github.com/TautvydasDerzinskas/Thingport.git
cd Thingport
npm install # root -- installs the husky pre-commit hook
npm --prefix backend ci
npm --prefix frontend ci
```
Each project has its own `package-lock.json`; there is no workspace root tying them together.
## 2. Start Postgres
The `db` service in `docker-compose.yml` has **no host port mapping** on purpose -- it's only
reachable from inside the Compose network, so a natively-run backend cannot connect to it. Run
your own container for development:
```bash
docker run -d --name thingport-db \
-e POSTGRES_USER=thingport \
-e POSTGRES_PASSWORD=thingport \
-e POSTGRES_DB=thingport_dev \
-p 5432:5432 \
postgres:16-alpine
```
If something already listens on 5432, map a different host port and adjust `DATABASE_URL` below.
## 3. Configure the backend
```bash
cp backend/.env.example backend/.env
```
`backend/.env.example` is written for the Docker Compose deployment, so two values **must** change
for local development:
| Variable | Set it to | Why |
| -------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `DATABASE_URL` | `postgresql://thingport:thingport@localhost:5432/thingport_dev?schema=public` | The example points at `db:5432` -- the Compose service hostname, which doesn't resolve outside Docker. |
| `FILE_STORAGE` | an absolute or relative path you own, e.g. `./storage` | The example uses `/app/storage`, the path inside the container. |
Also set `AUTH_SECRET` to any non-empty random string -- it signs login tokens, and the example
value is a placeholder.
Leave `API_PORT=8000`. The Vite dev server proxies `/api` to `http://localhost:8000`
(`frontend/vite.config.js`), so changing one means changing both.
`backend/.env` is gitignored. Never commit it.
## 4. Create the database schema
```bash
cd backend
npm run prisma:generate # required -- there is no postinstall hook, so the client isn't generated by `npm ci`
npx prisma migrate deploy # applies the 27 committed migrations
```
## 5. Run it
In two terminals:
```bash
npm --prefix backend run dev # tsx watch -> http://localhost:8000
npm --prefix frontend run dev # vite --host -> http://localhost:5173
```
Open <http://localhost:5173>. **The first account you register becomes the admin** -- see
`backend/src/routes/auth.ts`, which bootstraps the first user as `ADMIN` and everyone after as
`MEMBER`.
## Optional: FlareSolverr
Thingiverse sits behind Cloudflare, and some imports need FlareSolverr to get through. It's
optional -- without it those imports will simply fail.
```bash
docker run -d --name thingport-flaresolverr -p 8191:8191 ghcr.io/flaresolverr/flaresolverr:latest
```
Then set its URL (`http://localhost:8191`, with or without `/v1`) in **Administration → Connections →
FlareSolverr**. `FLARESOLVERR_URL` in `backend/.env` is copied into that setting on the first startup; after that the
saved value wins and the env var is ignored.
## Running the tests
### Backend
19 test files, 151 tests. They run against a **real Postgres database**, registering accounts and
creating and deleting rows as they go.
> [!WARNING]
> `npm test` loads `DATABASE_URL` from `backend/.env` through dotenv. If that points at your
> development database, **the suite will mutate it**. Always pass the variable on the command
> line -- an explicit shell variable takes precedence over `.env`, so this is safe.
```bash
docker run -d --name thingport-test-db \
-e POSTGRES_USER=thingport -e POSTGRES_PASSWORD=thingport -e POSTGRES_DB=thingport_test \
-p 55432:5432 postgres:16-alpine
cd backend
export TEST_DB="postgresql://thingport:thingport@localhost:55432/thingport_test?schema=public"
DATABASE_URL="$TEST_DB" npx prisma migrate deploy
DATABASE_URL="$TEST_DB" FILE_STORAGE=/tmp/thingport-test-storage npm test
```
The suite takes about 30 seconds. It runs test files sequentially on purpose -- several files
mutate the same global `Setting` rows, and parallel workers race each other. See the comment in
`backend/vitest.config.ts`.
### Frontend
There are no frontend tests yet. `npm run test:run` uses `--passWithNoTests`, so it succeeds
against an empty suite. New tests are welcome.
### Regression pack
Playwright scenarios in `regression/` that drive the real app in Chromium: sign-up, sign-in,
uploading (single and multiple files, zips, folder trees), the consume folder, collections, and the
sidebar (navigation, the admin-only route, bookmarks and their drag-to-reorder, collapsing). Upload
fixtures are generated in `tests/fixtures.ts` rather than checked in. The consume folder is
`regression/consume/`, bind-mounted into the backend for the run. `run.sh` builds the backend and
frontend images from your checkout, starts them with Postgres on port 8090, runs the scenarios, and
then removes the stack. The database and storage live on tmpfs, so every run starts empty and leaves nothing behind.
It never touches your development database. It needs Docker.
```bash
cd regression
npm install
npx playwright install chromium
./run.sh # whole pack
./run.sh tests/models.spec.ts # extra arguments go to `playwright test`
```
CI runs it every Saturday at 00:00 UTC (`.github/workflows/regression.yml`). You can also start it
from the Actions tab. When it fails, the HTML report, traces and container logs are uploaded as the
`regression-report` artifact.
### App screenshots
The README and website screenshots in `frontend/src/assets/screenshots/` are captured from a running
instance with a populated library, by `regression/screenshots.mjs` (the extension's screenshots
aren't covered). It signs in, captures every shot at 1400×790 and 2× scale, and overwrites the files.
The account's theme and Categories/Folders choice are switched for the shots and restored afterwards.
```bash
cd regression
THINGPORT_PASSWORD='…' npm run screenshots -- --url https://thingport.example --email you@example.com
npm run screenshots -- --url … --email … --only 03,04 # just some shots
npm run screenshots -- --url … --email … --model "Benchy" # model for the details and 3D shots
```
`--out` writes somewhere else instead, e.g. to compare before replacing. Self-signed certificates
are accepted.
## Quality checks
| Command | What it runs |
| ------------------------------------- | -------------------------------- |
| `npm run format` | Prettier, gofmt, `prisma format` |
| `npm run format:check` | the same, check only (CI) |
| `npm --prefix backend run lint` | oxlint |
| `npm --prefix frontend run lint` | oxlint |
| `npm --prefix frontend run typecheck` | `tsc --noEmit` |
| `npm --prefix frontend run verify` | typecheck + lint + tests + build |
A husky pre-commit hook formats staged files and runs oxlint over whichever project you touched (see
`lint-staged.config.js`). It does **not** typecheck, so run `typecheck` yourself before pushing
frontend changes -- CI will.
## Extension
```bash
cd extension
npm install
npm run dev # watch build into dist/chrome (npm run dev:firefox for dist/firefox)
```
- **Chrome / Edge**: `chrome://extensions` -> enable **Developer mode** -> **Load unpacked** -> select `extension/dist/chrome`.
- **Firefox**: `about:debugging#/runtime/this-firefox` -> **Load Temporary Add-on** -> select `extension/dist/firefox/manifest.json`.
Reload the extension after each rebuild, and point it at `http://localhost:5173` in the popup.
Chrome and Edge run the background bundle as a service worker while Firefox runs it as an event
page -- if you add top-level code there, make sure it works in both. See
[extension/CONTRIBUTING.md](../extension/CONTRIBUTING.md) for the code layout, the per-browser
builds, the store zips and the screenshot generator.
## Bridge
```bash
cd bridge
go test ./...
go run ./cmd/thingport-bridge
```
macOS URL-scheme handling uses Cocoa and needs cgo, so the macOS build only works on a Mac.
Windows and Linux binaries cross-compile with `CGO_ENABLED=0`.
## System notifications
Announcements for every instance, such as a known bug or a new feature, live in
[`system-notifications.json`](../system-notifications.json) at the repo root. Each backend fetches it
from `main` at startup and every 6 hours, and sends each entry once to each user it's meant for, as an
ordinary notification (the System tab). To post one, add an entry to `entries` and merge it:
```json
{
"entries": [
{
"id": "2026-10-imports-fail-on-printables",
"title": "Printables imports fail since Oct 10",
"body": "Printables changed its API. A fix is on the way; update when it lands.",
"url": "https://github.com/TautvydasDerzinskas/Thingport/issues/123",
"published_at": "2026-10-11T12:00:00Z",
"audience": "admins",
"version": "<1.2.0"
}
]
}
```
- `id` must be unique and never reused: it's what stops an entry being sent twice.
- `body` and `url` are optional; `url` must be `https://`, and the notification shows its domain.
- `path` is an optional in-app page opened on click, e.g. `/admin`.
- `published_at` is the date shown; a future date waits until then.
- `audience` is `admins` (the default) or `all` for every user.
- `version` limits it to instances on certain versions: `1.2.0` exactly, or `<`, `<=`, `>`, `>=` a
version. Leave it out for all versions. An instance that later updates into the range still gets it.
- Versions before 1.0.0 read only the `notifications` list and ignore `version`, so add there only what
every one of them should get; everything else goes in `entries`.
- Entries older than 90 days aren't sent, so old ones can stay in the file or be removed.
- `SYSTEM_NOTIFICATIONS_URL` points a backend at another file; an empty value turns fetching off.
## Related documents
- [CONTRIBUTING.md](../CONTRIBUTING.md) -- branch, commit and pull request workflow
- [PROVIDER_SETUP.md](PROVIDER_SETUP.md) -- MakerWorld / Thingiverse / Printables credentials