Archives
- ZIP, 7Z, RAR and TAR/.tar.gz uploads are indexed in the background into
archive_entries (7-Zip compiled to WASM in a worker thread; node-tar for
tar). macOS/Windows clutter and paths that could escape the extraction
folder are skipped.
- Models inside archives are extracted once per archive to hash and
geometry-fingerprint them, so duplicates are found inside packs too.
Pictures, PDFs and readmes are listed but never matched.
- New routes list an archive's entries and serve/convert a single entry,
extracted on first use and cached under extracted/.
Viewer
- Model detail lists what's inside each archive with view/download links;
opening an entry adds a viewer tab.
- New 3D formats: FBX (with sibling textures), OBJ with its MTL and
textures, STEP/STP via occt-import-js, and Rhino 3DM via rhino3dm — the
WASM readers are served from node_modules, loaded on first use.
- Pictures, PDFs and text files (jpg/png/gif/webp/bmp/svg, pdf, txt/md)
can be uploaded and preview in the viewer; a model without an embedded
3MF preview uses its first picture as the card thumbnail.
- Inline file responses send nosniff, and SVGs are sandboxed.
- GLB conversions write to a temp file and rename, so a half-written
preview is never served; a later tab can't be overdrawn by a slow load.
- Better 3MF card image: the project thumbnail or plate 1 render, not a
slicer pick map.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
trimesh's 3MF loader re-reads a whole production-extension part file for
every component that points into it. A Bambu Studio project with 884
components across four ~50 MB part files grew past 3 GB within seconds
and failed both the preview conversion and the geometry fingerprint.
- Add tools/threemf_stream.py: parses each part once with lxml iterparse,
resolves each component to the single object it names, and returns each
mesh once plus its placements. The same project now converts in ~8 s at
~380 MB peak (fingerprinting ~190 MB).
- convert_3mf.py and fingerprint.py use it; the GLB stores each object once
and instances repeats. Stats are computed per placement (mirrored
placements keep a positive volume), without concatenating meshes.
- Run sidecar scripts through one queue (lib/python.js) so a preview and
background fingerprinting never load big meshes at the same time;
previews go first, and 3MFs are fingerprinted one per run.
- Surface the converter's JSON error / timeout instead of "Command failed".
- Tests with a Bambu-style component fixture (shared part, mirrored
placement, decoy object) and STL/3MF fingerprint parity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Fingerprint every file in the background: SHA-256 for all formats plus a
trimesh geometry fingerprint for STL/OBJ/3MF, so renamed, re-exported or
converted copies are still matched. Backfills existing files on startup.
- Duplicates view groups models that share files (exact copies vs same
shape), recommends which to keep, merges tags/collections/notes into the
kept model and trashes the rest; groups can be dismissed.
- Deleting now moves models to a restorable trash; files are only removed
from disk on permanent delete or when the trash is emptied.
- Per-model notes with autosave.
- Search across names, notes, tags, files and designers; filter by format,
source and license; include/exclude tags with any/all matching; sort and
group by source or format. Preferences persist per browser.
- Add node:test unit tests for duplicate grouping and fingerprints.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brand hexagon in amber on the dark graphite, linked as the icon +
apple-touch-icon, plus a theme-color meta.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Suggests existing library tags matching the token being typed (after the
last comma), with click or arrow-key/Enter selection; excludes tags
already entered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Changing a license policy applies to all models with that license, and
opening Settings reconciles old imports to current rules (store.reconcileModelsToRules)
- Editing a model's license re-resolves its sellability from the rule
- Removed the per-model commercial-use override (set by license now)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- license_rules table (name -> commercial); licenses auto-register on
import with a best-effort seed, overridable in Settings (applies the
choice to every model with that license)
- resolveCommercial() replaces ad-hoc inferCommercial across import/refresh
- GET/PUT /api/licenses; "License types" section in Settings
- Fix CC short codes: BY / BY-SA / BY-ND now classed sellable (MakerWorld
stores e.g. "BY-SA"); NC variants stay non-commercial
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- users + password_resets tables; bcrypt hashing; JWT session in an
httpOnly cookie (auth_secret auto-generated in settings)
- /api gated behind auth (except /api/config and /api/auth/*)
- First-run creates the account (no open registration after that);
login, logout, forgot/reset-password flows
- SMTP settings (host/port/secure/user/pass/from) in Settings menu;
nodemailer sends reset links
- Auth screen (sign in / create account / forgot / reset) gates the SPA;
Sign out button; COOKIE_SECURE=true in compose for HTTPS
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 404 was a router that wouldn't load: cert resolver is named `le`
(not `letsencrypt`) and the explicit `tls=true` label was missing.
Mirror the working paperless service.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
That's the actual network Traefik runs on; was defaulting to a
non-existent `traefik` network, causing "network ... could not be found".
Still overridable via TRAEFIK_NETWORK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Traefik labels so the Docker provider auto-discovers the app.
Includes traefik.docker.network (the app is on two networks) which is
the usual fix for routing not resolving. Host/entrypoint/certresolver
overridable via PRINTHIVE_HOST / TRAEFIK_ENTRYPOINT / TRAEFIK_CERTRESOLVER.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the published host port (it clashed on the deploy host) and attach
the app to the external Traefik network so Traefik routes to it directly.
Stable container_name printhive-app for the file provider to target;
network name configurable via TRAEFIK_NETWORK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Host port 3000 can clash with other services on the deploy host; allow
overriding via APP_PORT (default 3000). Only needed for Traefik to reach
the app — can be dropped entirely if Traefik shares the Docker network.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a fresh install (no integration settings yet), prompt once for the
Thingiverse/MakerWorld/Printables tokens and FlareSolverr URL, with a
"skip for now" option. /api/config exposes setupComplete (true if any
setting is configured or setup was dismissed); POST /api/setup/complete
records dismissal. DB password stays a deploy-time env var by necessity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dockhand pulls the CI-built image, so drop `build: .` (no build context
on the deploy host) and add pull_policy: always.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The auto GITHUB_TOKEN lacks package-write scope (push failed with
unauthorized: reqPackageAccess). Use a REGISTRY_TOKEN secret (Forgejo
token with write:package) and log in as the repo owner instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On push to main (and v* tags), builds the Docker image and pushes it to
the Forgejo container registry at git.plexultra.com/dlawler489/printhive
(:latest and :<short-sha>). Uses the auto-provided Actions token with
packages:write; plain docker CLI so it only needs the checkout action.
Compose image reference updated to the Forgejo registry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Dockerfile: node:22-bookworm-slim + Python venv with trimesh for the
3MF->GLB sidecar (PYTHON_BIN env points the converter at it)
- docker-compose.yml: app + postgres 16 with healthcheck/depends_on,
named volumes for pgdata/uploads/thumbnails/glb, token env passthrough
- .dockerignore, tools/requirements.txt
- converter.js honours PYTHON_BIN (defaults to local venv)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>