- TypeScript 97.2%
- CSS 2.1%
- Dockerfile 0.5%
- JavaScript 0.2%
| docs/superpowers | ||
| drizzle | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| components.json | ||
| docker-compose.yml | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| eslint.config.mjs | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.mts | ||
Things
A small personal asset database: what you own, what model it is, where and when you bought it, when the warranty runs out, plus tags and free-form key/value details. Think "CMDB for one person".
Design decisions live in docs/superpowers/specs/, the build plan in
docs/superpowers/plans/.
Run locally
npm install
npm run dev # http://localhost:3000
npm run seed:demo # optional: a handful of example things
The SQLite file is created at data/things.db on first start. Migrations
run automatically when the server boots (src/instrumentation.ts).
Files attached to a thing (receipts, manuals, photos, ...) are stored in an S3-compatible object store. For local development run MiniStack, a free local AWS emulator, before uploading anything:
npm run s3:dev # docker compose up -d ministack (port 4566)
The app defaults to http://localhost:4566, bucket things-receipts,
credentials test/test, and creates the bucket on boot. Without it the app
still runs; only file uploads fail. Override with the S3_* variables in
.env.example to point at real S3, MinIO, R2 or similar.
Other scripts:
| Script | What it does |
|---|---|
npm test |
vitest: validation + repository tests against an in-memory SQLite |
npm run typecheck |
tsc --noEmit |
npm run lint |
eslint |
npm run db:generate |
generate a new migration after editing src/db/schema.ts |
npm run db:studio |
Drizzle Studio, a GUI over the local database |
npm run s3:dev |
start the local MiniStack S3 emulator in Docker |
S3_TEST=1 npm test |
also run the storage tests against the live emulator |
Run in Docker
docker compose up --build
# or
docker build -t sophies-things .
docker run -p 3000:3000 -v things-data:/data sophies-things
The image is a multi-stage build on node:24-bookworm-slim using Next.js
standalone output. Data lives on the /data volume
(DATABASE_URL=file:/data/things.db). The compose file also starts a
ministack service for attached files, persisted on its own volume; swap the
S3_* environment for a real bucket in production. GET /api/health returns
{ "ok": true } once the database answers; use it for Kubernetes liveness
and readiness probes. The container runs as a non-root user.
Users and auth
There is no login yet. src/lib/current-user.ts is the single place that
answers "who is acting"; today it always returns the seeded dev-user.
Every asset records created_by / updated_by, and every create, update
and delete is appended to activity_log with the acting user, so wiring in
real auth later means changing that one function.
Data model
models manufacturer · name · identifier? (deduplicated)
assets asset_tag? (unique) · name? · model_id · status
purchase_date? · purchased_from? · purchase_amount? · purchase_currency
warranty_expiry? · created/updated by/at
asset_attributes asset_id · key · value · position (unique per asset+key)
tags / asset_tags free-form labels, many-to-many
attachment_types name · position (Receipt, Manual, ... editable in Settings)
attachments asset_id · type_id · file_name · content_type · size · storage_key · uploaded by/at
asset_notes asset_id · body · created/updated by/at (many per thing)
settings key · value (app preferences, e.g. notes per page)
activity_log who did what, when
-
Adding a thing only needs a manufacturer and model; the model row is found-or-created in the same transaction, so a first-of-its-kind item is a single form.
-
Money is a decimal string plus an ISO currency code (default
AUD). -
Dates are
YYYY-MM-DDstrings; timestamps are unix milliseconds. -
Status is one of
active,stored,lent,disposed. -
Files (PDF, JPEG, PNG, WebP, HEIC, up to 10 MB) are uploaded from a thing's page through a server action, each tagged with a file type (Receipt, Manual, Warranty, Repair, Photo, Other by default; edit the list in Settings), and stored at
files/<asset>/<file id>/<name>in the bucket; the browser reads them back viaGET /api/files/:id(?download=1to save), so the bucket never needs to be public. Removing a type in Settings moves its files to Other, which cannot be removed. -
Notes are a journal: add, edit or delete one straight from a thing's page, sort them newest or oldest first, and page through them. The number shown per page (5, 10, 20 or 50) is a preference saved in
settings.
Suggested key/value details
Offered as one-click chips on the form; any key works.
serial_number, mac_address, ip_address, hostname, location,
colour, capacity, os_version, firmware_version, account,
licence_key, support_url, receipt_ref, dimensions, weight,
power_rating.
Moving to Postgres
Everything database-specific is in src/db/:
schema.ts: swapdrizzle-orm/sqlite-corefordrizzle-orm/pg-core. Same table and column names;integertimestamps becometimestamporbigint,textstaystext.client.ts: replace@libsql/client+drizzle-orm/libsqlwithpg+drizzle-orm/node-postgres, and the migrator import todrizzle-orm/node-postgres/migrator. Drop thePRAGMAline.drizzle.config.ts:dialect: "postgresql", thennpm run db:generateto produce fresh migrations.- Set
DATABASE_URL=postgres://....
The repositories under src/db/repositories/ use only the dialect-agnostic
query builder, so they should not need changes. The tests create their own
database via createDatabase(url) and will point at whatever you give them.
Layout
src/app routes (App Router)
page.tsx dashboard
things/ list, new, [id], [id]/edit, actions.ts (server actions)
models/ tags/ settings/
api/health probe · api/models model autocomplete
src/components/shell sidebar, top bar, page header
src/components/things asset card, form, badges, delete dialog
src/components/ui shadcn/ui
src/db schema, client, seed, repositories (all SQL lives here)
src/lib validation (zod), formatting, current-user seam
drizzle/ generated SQL migrations (applied at boot)
scripts/seed-demo.ts example data