oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package docs
2
3import (
4 "embed"
5 "time"
6
7 "amadan.net/rastrillo/rastrillo/migrate"
8)
9
10// migrationFS embeds this app's own numbered migrations — per
11// rastrillo SKILL.md's app shape ("Two *migrate.Set values live in
12// internal/<app>/migrations.go, beside models.go"; kept in this file
13// instead of a separate migrations.go because this task's file list
14// is models.go, store.go, store_test.go and a fourth file would only
15// split one small declaration across two places for no benefit).
16//
17//go:embed migrations/*.sql
18var migrationFS embed.FS
19
20// Schema is this app's own migrations — spec §4's documents and
21// doc_updates tables, only what this plan needs. It is idempotent
22// (CREATE TABLE IF NOT EXISTS today; later plans add further numbered
23// files, additive ALTERs, never edits to 0001) per rastrillo's
24// migration pattern.
25var Schema = migrate.MustFromFS(migrationFS, "docs")
26
27// Document is a document's row. Its text is deliberately not a
28// column: GUID and ClientID are what let Store open the real CRDT
29// core (internal/core) and replay doc_updates into it, which is where
30// the text actually comes from — see Store.Text.
31//
32// GUID and ClientID are assigned by the app (Store.Create) and stored
33// here; they are never drawn from yrs itself — on wasm the CRDT
34// library's entropy is a constant seed, so a value it generates is
35// identical in every process, and a repeated client id or GUID
36// corrupts the document. This is the Go side of the rule clippy.toml
37// enforces in Rust (disallowed-methods on yrs::Doc::new /
38// Doc::with_client_id).
39//
40// OwnerID is the document's owner — Person.ID, never taken from a
41// request body (Store.Create's owner parameter comes from the
42// signed-in person; see handlers.go's owner helper and rastrillo
43// SKILL.md §3/§4's mass-assignment rule). Every read and write is
44// scoped by it through store.go's own owned function, deliberately
45// not rastrillo's scope.Owned — see that function's doc comment and
46// spec §4.2.
47type Document struct {
48 ID int64 `gorm:"primaryKey"`
49 Sqid string `gorm:"column:sqid;uniqueIndex;not null"`
50 Title string `gorm:"column:title;not null"`
51
52 OwnerID int64 `gorm:"column:owner_id;not null;index;uniqueIndex:documents_creation,priority:1"`
53 CreationToken *string `gorm:"column:creation_token;uniqueIndex:documents_creation,priority:2"`
54
55 GUID string `gorm:"column:guid;not null"`
56 ClientID uint64 `gorm:"column:client_id;not null"`
57
58 CreatedAt time.Time `gorm:"column:created_at;not null"`
59 UpdatedAt time.Time `gorm:"column:updated_at;not null"`
60 DeletedAt *time.Time `gorm:"column:deleted_at"`
61}
62
63// Person is this app's own record of someone who has signed in —
64// deliberately separate from rastrillo/auth's session Subject, which
65// under the magic-link plugin is the verified email address itself,
66// not a numeric id (rastrillo SKILL.md §5: "Under auth, never
67// sessions.UserID: the Subject is the verified email... map the
68// address to your user row's id before scoping"). Store.EnsurePerson
69// is the one place that mapping happens, get-or-create, keyed on
70// Email. A document's OwnerID is a Person.ID, never an email — so an
71// email can change (a future feature) without rewriting every
72// document row it ever owned.
73type Person struct {
74 ID int64 `gorm:"primaryKey"`
75 Email string `gorm:"column:email;uniqueIndex;not null"`
76
77 CreatedAt time.Time `gorm:"column:created_at;not null"`
78}
79
80// DocUpdate is one Yjs update in a document's log, applied in Seq
81// order to rebuild the document's CRDT state (Store.replay).
82type DocUpdate struct {
83 ID int64 `gorm:"primaryKey"`
84 DocID int64 `gorm:"column:doc_id;not null;index"`
85 Seq int64 `gorm:"column:seq;not null"`
86
87 UpdateBin []byte `gorm:"column:update_bin;not null"`
88 ContentEncoding int `gorm:"column:content_encoding;not null"`
89
90 CreatedAt time.Time `gorm:"column:created_at;not null"`
91}
92
93// DocSnapshot is one document's entire state encoded as a single Yjs
94// update, as of Seq. It is an accelerator for replay and for the update
95// endpoint, never the source of truth — doc_updates keeps every row, and
96// a snapshot that cannot be read is discarded in favour of replaying
97// them (the snapshots design, D2).
98//
99// ABIVersion records the core that produced State. Without it, "discard
100// a snapshot from an older ABI" is unenforceable prose.
101type DocSnapshot struct {
102 ID int64 `gorm:"primaryKey"`
103 DocID int64 `gorm:"column:doc_id;not null;index"`
104 Seq int64 `gorm:"column:seq;not null"`
105
106 State []byte `gorm:"column:state;not null"`
107 ContentEncoding int `gorm:"column:content_encoding;not null"`
108 ABIVersion uint32 `gorm:"column:abi_version;not null"`
109
110 CreatedAt time.Time `gorm:"column:created_at;not null"`
111}
112