mirror of
https://github.com/TautvydasDerzinskas/Thingport.git
synced 2026-10-19 05:10:01 -05:00
* feat: update system notifications * feat: add Thingport grab to system health check * ci: upgrade to Node.js 24 and centralize version management
263 lines
11 KiB
Markdown
263 lines
11 KiB
Markdown
# 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
|