A local/private Digital Asset Manager (DAM) for cataloging 2D & 3D creative assets
(.fbx, .obj, .gltf/.glb, .png, .jpg, .gif, videos, textures) with a searchable
visual browser and an interactive Three.js viewport.
- Backend: FastAPI + SQLAlchemy + Alembic + PostgreSQL (managed with
uv) - Frontend: React + Vite + TypeScript + react-three-fiber
- Auth: JWT email/password
- Docs: Swagger/OpenAPI at
/docs
- Python 3.13+ and uv
- Node 20+ and npm
- PostgreSQL 16 running on
localhost:5432
cd backend
uv sync # install dependencies into .venv
cp .env.example .env # then edit DB credentials if needed
uv run uvicorn app.main:app --reload- API: http://localhost:8000
- Swagger docs: http://localhost:8000/docs
- Health: http://localhost:8000/health
cd backend
uv run pytestPopulate the database with a couple of demo users and sample assets (generated on the fly — no files to download) so you can explore the app immediately, including the public / private feature:
cd backend
uv run python -m scripts.seed # create demo data (idempotent)
uv run python -m scripts.seed --reset # wipe the demo users first, then reseedThis creates:
demo@example.com/demopass1— your main account with folders, tags, categories, and five private assets.friend@example.com/friendpass1— a second account owning public assets that appear under Others' assets when you sign in as the demo user.
cd frontend
npm install
npm run dev # http://localhost:5173- The dev server proxies API routes to the backend. If the backend is not on
:8000, point the proxy at it:VITE_API_TARGET=http://127.0.0.1:8001 npm run dev(PowerShell:$env:VITE_API_TARGET="http://127.0.0.1:8001"; npm run dev). - Optional
frontend/.envkeys:VITE_API_TARGET,VITE_GOOGLE_CLIENT_ID.
cd frontend
npm run build # type-check + production build
npm test # Vitest component smoke tests
npm run test:e2e # Playwright end-to-end testsEnd-to-end (Playwright). Tests live in frontend/e2e/ and drive a real
browser against the Vite dev server (started automatically). There are two
suites:
e2e/mocked/— the network is stubbed withpage.route, so these need no backend and always run. They cover auth (login/register validation, success, error, redirects, logout) and the gallery (asset cards, empty state, search, type filters).e2e/live/— smoke tests against the real backend + seeded Postgres. Each test skips itself when the backend isn't reachable, sonpm run test:e2estays green with just the frontend up. To run them for real, start the backend and seed the demo data (see above), then runnpm run test:e2e.
First-time setup installs the browser binary (already done if you ran the repo
setup): npm run test:e2e:ui opens Playwright's interactive UI mode.
On this machine, Playwright's browser download also needs the system CA:
$env:NODE_OPTIONS="--use-system-ca"; npx playwright install chromium.
- System TLS certificates: package registries are behind a custom root CA.
uvis configured viabackend/pyproject.toml([tool.uv] system-certs = true) — no action needed.- For npm, run installs with the system CA:
NODE_OPTIONS=--use-system-ca npm install(PowerShell:$env:NODE_OPTIONS="--use-system-ca"; npm install).
- IPv4 vs IPv6: uvicorn binds IPv4
127.0.0.1; use127.0.0.1(notlocalhost) when curling the backend directly. The Vite dev proxy already targets127.0.0.1. - Postgres: local server on
:5432, defaultpostgres/postgres, databaseassetvault.
The API supports three ways to sign in, all of which return the same app JWT:
- Email + password —
POST /auth/register, thenPOST /auth/login. - Sign in with Google (passwordless) — the browser gets a Google ID token and
posts it to
POST /auth/google; the backend verifies it and logs the user in, creating (or linking) the account automatically. - Continue as guest —
POST /auth/guestissues a token for a single shared, read-only visitor account (created on first use). See Guest access below.
Google Sign-In is optional and stays disabled until you set GOOGLE_CLIENT_ID.
While disabled, POST /auth/google returns 503; the rest of auth is unaffected.
You need a free OAuth Client ID from Google. It's public (safe to commit to
.env locally), and there is no secret to manage with this flow.
- Go to the Google Cloud Console and create a project (or pick an existing one).
- APIs & Services → OAuth consent screen: choose External, give the app a name and your email, and save. Add yourself under Test users.
- APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Web application.
- Authorized JavaScript origins: add
http://localhost:5173(the frontend). - Create it and copy the Client ID (looks like
1234567890-abc123.apps.googleusercontent.com).
- Paste the same Client ID into both places:
backend/.env(verifies the token server-side):GOOGLE_CLIENT_ID=1234567890-abc123.apps.googleusercontent.comfrontend/.env(renders the button):VITE_GOOGLE_CLIENT_ID=1234567890-abc123.apps.googleusercontent.com
- Restart the backend and the frontend dev server. A "Sign in with Google"
button now appears on the login/register pages, and
POST /auth/googleverifies real Google tokens.
Until
VITE_GOOGLE_CLIENT_IDis set, the Google button is simply hidden and email/password auth works as normal.
The login page has a Continue as guest button for a look around without an account. Guests share one read-only account and can:
- browse the Shared assets gallery (every public asset),
- open an asset's details, download the original, and read its likes/comments.
Guests cannot upload, edit, delete, re-file, like, or comment — every write is refused server-side. Private assets stay invisible to guests.
Every asset is private by default — only its owner can see or edit it. On an asset's details page, the owner can flip the Visibility toggle to Public. Public assets:
- appear in every other user's Others' assets view in the sidebar,
- are read-only to those other users (they can open and view, but not edit, delete, or re-file them),
- stay editable and deletable only by their owner.
In the sidebar, All my assets holds your own folders (and Unfiled), while Others' assets lists every public asset — shared by others and your own public ones, so you can confirm something went public. A public asset's details page shows who shared it.
Public assets have a lightweight social layer (Pinterest-style):
- Any signed-in user can like a public asset (including their own) — the like count and a filled/outline heart show on the card and details page. Likes are the popularity signal; you can sort the gallery by Most liked.
- Any signed-in user can comment on a public asset. Comments show the author's display name (never their raw email).
- Comments support nested replies — reply to any comment, at any depth. Replies are collapsed by default behind a View N replies toggle. Deleting a comment removes its replies.
- A comment (or reply) can be deleted by its author or by the asset's owner (basic moderation).
- Likes and comments are only available while an asset is public; making it private again hides them (the data is kept and reappears if it's made public later).
- 3D models & videos: dominant colours are extracted from the preview snapshot the browser captures (the same colour analysis used for image uploads), so captured assets become colour-searchable.
- Upload size limit: capped by
MAX_UPLOAD_BYTES(default 50 MiB). Oversized requests are rejected early via theContent-Lengthheader.
Asset-Vault/
├── backend/ # FastAPI service
│ └── scripts/seed.py # demo-data seeder
└── frontend/ # Vite React app