Files
beardrive/internal/webapp/db.go
Snow LeeandClaude Opus 4.8 f14601a983 refactor(web): extract MetaStore + typed repos; file backend (phase 1)
Introduce a pluggable metadata persistence layer: a MetaStore of typed
repositories (AccountRepo, ProjectRepo, OrgRepo, ShareRepo, DeviceRepo).
The five registries keep their in-memory maps + logic and now persist each
change as one record through a repo, instead of rewriting a whole JSON file
inline. The `file` backend (db_file.go) reproduces the exact on-disk JSON
formats, so a running hub loads unchanged. Open*(path) constructors stay as
thin wrappers over the file backend.

Zero behavior change: full webapp suite green; the example hub boots and
existing auth.json/orgs.json/projects.json load and sign-in works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
2026-07-10 00:01:56 -07:00

59 lines
1.8 KiB
Go

package webapp
// MetaStore is the hub's metadata persistence, split into one typed repository
// per entity. It holds ONLY the control plane — accounts, tokens, projects,
// orgs, invites, shares, devices. File content and the append-only journals
// live in the object store and never touch this; ephemeral state (one-time
// login and device codes, rate-limit buckets) stays in memory.
//
// A deployment chooses the backend: `file` (JSON on disk, the zero-dependency
// default) or `sql` (SQLite locally, Postgres/Supabase in production). The
// service structs (BuiltinAuth, OrgDB, …) keep their in-memory maps, mutexes,
// and business logic and persist each change through these repos — so reads
// stay in memory and writes are a single record apiece, which every backend
// implements as one real row.
type MetaStore interface {
Accounts() AccountRepo
Projects() ProjectRepo
Orgs() OrgRepo
Shares() ShareRepo
Devices() DeviceRepo
Close() error
}
// AccountRepo persists accounts, device tokens, and the (singleton) signup
// policy. Load returns everything at open; every other method is one record.
type AccountRepo interface {
Load() (users []*authUser, tokens []authToken, policy *authPolicy, err error)
PutAccount(u *authUser) error
DeleteAccount(id string) error
PutToken(t authToken) error
DeleteToken(hash string) error
PutPolicy(p authPolicy) error
}
type ProjectRepo interface {
Load() ([]Project, error)
Put(p Project) error
Delete(id string) error
}
type OrgRepo interface {
Load() (orgs []Org, invites []OrgInvite, err error)
PutOrg(o Org) error
DeleteOrg(id string) error
PutInvite(i OrgInvite) error
DeleteInvite(token string) error
}
type ShareRepo interface {
Load() ([]Share, error)
Put(s Share) error
Delete(token string) error
}
type DeviceRepo interface {
Load() ([]DeviceInfo, error)
Put(d DeviceInfo) error
}