# Overseer: family information system
A modular family information system in C++23.
## Overall requirements
- Use libmw when applicable. See `~/programs/libmw/includes/mw` for
headers.
- Web interface with `mw::HTTPServer`.
- All data in a single SQLite database, including attachments like
photos.
- Logging via `spdlog`.
- Configuration file at `/etc/overseer.yaml`.
- The SQLite database file lives at `/var/lib/overseer/overseer.db`.
- Modular: it will contain multiple modules that do different
things:
* Inventory system: record where things are stored at home
* Doc system: store and view documents
- Only the inventory system is planned for now.
## Web architecture
- Server-rendered HTML using the **Inja** template engine.
- Interactive bits (tree expand/collapse, live search, inline edit,
delete-without-reload) use **htmx**. Endpoints return HTML
fragments that htmx swaps into the page. No JSON API, no frontend
build step.
- Plain CSS. No Bootstrap.
- Inventory logic lives in a separate library
(e.g. `liboverseer_inventory`) that the web server links against,
so a future agent-facing surface (MCP, REST) can reuse it without
going through the web layer. Agent access itself is out of scope
for now.
## Configuration
`/etc/overseer.yaml` specifies at minimum:
- `bind_address` and `port` for the HTTP server.
- OIDC settings: `issuer_url` (or discovery endpoint), `client_id`,
`client_secret`, `redirect_uri`.
- Optional log level.
## Authentication
- External OpenID Connect service (a KeyCloak server) is used for
authentication.
- Authorization code flow. Tokens stay server-side; the
browser only holds an opaque session cookie.
- Multi-user from an auth perspective: every request is tied to an
authenticated OIDC user.
- The app manages a single family's data. Storages and stuffs are
**not** owned per-user — any authenticated user can read and
modify any row. No per-row ACLs.
## Database
- Schema is versioned using the `user_version` pragma.
- Whenever the schema version is increased, a function to migrate the
database from the previous version should be written. For example:
`migrateDB1To2()`. The server runs this automatically on startup.
- Migration rules:
* Each migration runs inside a single transaction.
* The server refuses to start if the database's `user_version` is
**higher** than the version the binary knows about.
* The server takes a backup copy of the database file before
running any migration.
## Attachments
- Photos (and future attachments) are stored in a dedicated
`attachment` table: `id`, `mime`, `sha256`, `bytes`. Storages and
stuffs reference attachments by id, so the same photo is not
duplicated.
- On upload, photos are processed with Magick++
(https://imagemagick.org/magick++/):
* Scaled down so the long side is at most 1024px (no upscaling).
* Encoded as AVIF.
- Attachments are served via a dedicated route (e.g.
`/attachment/{id}`) with the correct `Content-Type` and an `ETag`
for caching. Do not inline image bytes into HTML.
## Search
- Full-text search over `name` and `description` for both storages
and stuffs, using SQLite FTS5.
- The FTS virtual tables are created and kept in sync via triggers,
set up in the initial migration.
## Inventory system
### Storages
- A storage is a physical place to store stuff at home (for example
"the book shelf").
- Storages form a tree using an **adjacency list**
(`parent_id` → `storage.id`). No path caching in the database; the
server may compute paths on demand or cache them in memory. The app
is small-scale (≤2 concurrent users).
- `(parent_id, name)` is unique — siblings cannot share a name.
- Fields: `name` (required), `description` (optional),
`attachment_id` (optional, the photo).
- Users can create, edit, and delete storages.
### Stuff
- A stuff is an item stored in exactly one storage at a time.
- Fields: `name` (required), `description` (optional),
`attachment_id` (optional), `storage_id` (required — current
location).
- Users can add, edit, and delete stuff.
- **Move history**: each stuff keeps a rolling history of its last
10 storage locations in a separate `stuff_move` table
(`stuff_id`, `storage_id`, `moved_at`). When a stuff is moved to a
new storage, the previous `storage_id` is appended as a new row.
A trigger trims the oldest entries so that no stuff has more than
10 history rows.
### Search UI
- Users can search for storages and stuffs by name and description.
## URL design
- Each storage and stuff should have its own unique URL.
- Keep in mind that the inventory system is just one of multiple
modules.