Files
beardrive/internal/webapp/db.go
T
Snow LeeandClaude Fable 5 28dc882c66 feat(web): read heatmap phase 1 — ledger, heat API, folder heat dots
Read telemetry per docs/design/read-heatmap.md: a ReadLedger over a new
batch-oriented MetaStore ReadRepo (file reads.json + SQL read_stats)
aggregates viewer and share reads into daily per-actor buckets, debounced
to visits, folded into all-time rows past retention. GET /api/p/<id>/heat
serves per-path counts (human/agent/share, distinct readers, last read) —
never identities. /store sync traffic and history blob views are not reads.
The viewer shows heat dots and read counts on folder listings and the file
meta line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
2026-07-11 14:41:07 -07:00

70 lines
2.3 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
Reads() ReadRepo
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
}
// ReadRepo persists read-telemetry buckets (see ReadStat). Unlike the other
// repos it is batch-oriented: reads are telemetry, and the ledger flushes many
// dirty buckets at once — one file rewrite / one SQL transaction per flush,
// not one write per bucket.
type ReadRepo interface {
Load() ([]ReadStat, error)
PutBatch(stats []ReadStat) error // upsert by (project, path, day, kind, actor)
DeleteBatch(keys []ReadStatKey) error
}