GoAT Site is library that implements Standard.site in Go.
atprotocol standard-site atproto library
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(xrpc): document crud

+309 -61
+111 -4
document.go
··· 1 1 package site 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 6 + "errors" 7 + "fmt" 5 8 "strings" 6 9 "time" 10 + 11 + "github.com/bluesky-social/indigo/api/agnostic" 12 + "github.com/bluesky-social/indigo/api/atproto" 13 + "github.com/bluesky-social/indigo/atproto/syntax" 14 + lexutil "github.com/bluesky-social/indigo/lex/util" 7 15 ) 8 16 9 - const LexiconDocument = LexiconBase + ".document" 17 + const CollectionDocument = CollectionBase + ".document" 10 18 11 19 // Document may be standalone or associated with a [Publication]. 12 - // This lexicon can be used to store a document's content and its associated metadata. 20 + // This [Record] can be used to store a document's content and its associated metadata. 13 21 type Document struct { 14 22 // Site points to a [Publication] record `at://` or a [Publication.URL] `https://` for loose documents. 15 23 // Avoid trailing slashes. ··· 31 39 // Less than 1MB in size. 32 40 CoverImage *Blob `json:"coverImage,omitempty"` 33 41 // Content is a custom [Lexicon] used to define the [Document]'s content. 34 - Content *LexiconJSON `json:"content,omitempty"` 42 + Content *RecordJSON `json:"content,omitempty"` 35 43 // TextContent is a plaintext representation of the [Document.Content]. 36 44 // Should not contain markdown or other formatting. 37 45 TextContent string `json:"textContent,omitempty"` ··· 53 61 } 54 62 55 63 func (d *Document) Type() string { 56 - return LexiconDocument 64 + return CollectionDocument 57 65 } 58 66 59 67 func (d *Document) MarshalMap() (map[string]any, error) { ··· 97 105 *d = Document(v.t) 98 106 return nil 99 107 } 108 + 109 + // GetDocument returns the [Document] in the repo associated with the rkey. 110 + // Automatically uses the latest CID. 111 + func GetDocument(ctx context.Context, client lexutil.LexClient, repo string, rkey syntax.RecordKey) (*Document, error) { 112 + rec, err := agnostic.RepoGetRecord(ctx, client, "", CollectionDocument, repo, string(rkey)) 113 + if err != nil { 114 + return nil, err 115 + } 116 + var v *RecordJSON 117 + err = json.Unmarshal(*rec.Value, &v) 118 + if err != nil { 119 + return nil, err 120 + } 121 + if v.Record == nil { 122 + return nil, errors.Join(ErrInvalidType, fmt.Errorf("expected %s, not %s", CollectionDocument, v.Type)) 123 + } 124 + return v.Record.(*Document), nil 125 + } 126 + 127 + // ListDocuments returns all the [Document]s stored in the repo and the cursor. 128 + // 129 + // See [MaxItemsPerList]. 130 + func ListDocuments(ctx context.Context, client lexutil.LexClient, repo, cursor string, reverse bool) ([]*Document, *string, error) { 131 + rec, err := agnostic.RepoListRecords(ctx, client, CollectionDocument, cursor, MaxItemsPerList, repo, reverse) 132 + if err != nil { 133 + return nil, nil, err 134 + } 135 + docs := make([]*Document, MaxItemsPerList) 136 + i := 0 137 + for i < len(rec.Records) { 138 + r := rec.Records[i] 139 + err = json.Unmarshal(*r.Value, &docs[i]) 140 + if err != nil { 141 + return nil, nil, err 142 + } 143 + i++ 144 + } 145 + return docs[:i], rec.Cursor, nil 146 + } 147 + 148 + // CreateDocument in a repo with the given rkey. 149 + // Always tries to validate the [Document] against the [Record] saved. 150 + // 151 + // Rkey can be nil. 152 + func CreateDocument(ctx context.Context, client lexutil.LexClient, repo string, rkey *syntax.RecordKey, doc *Document) (*Result, error) { 153 + mp, err := MarshalToMap(&RecordJSON{Record: doc}) 154 + if err != nil { 155 + return nil, err 156 + } 157 + var cv *string 158 + if rkey != nil { 159 + t := string(*rkey) 160 + cv = &t 161 + } 162 + t := true 163 + out, err := agnostic.RepoCreateRecord(ctx, client, &agnostic.RepoCreateRecord_Input{ 164 + Collection: CollectionDocument, 165 + Record: mp, 166 + Repo: repo, 167 + Rkey: cv, 168 + Validate: &t, 169 + }) 170 + if err != nil { 171 + return nil, err 172 + } 173 + return &Result{out.Uri, out.Cid, out.ValidationStatus, out.Commit}, nil 174 + } 175 + 176 + // UpdateDocument in a repo with the given rkey. 177 + // Always tries to validate the [Document] against the [Record] saved. 178 + func UpdateDocument(ctx context.Context, client lexutil.LexClient, repo string, rkey syntax.RecordKey, doc *Document) (*Result, error) { 179 + mp, err := MarshalToMap(&RecordJSON{Record: doc}) 180 + if err != nil { 181 + return nil, err 182 + } 183 + t := true 184 + out, err := agnostic.RepoPutRecord(ctx, client, &agnostic.RepoPutRecord_Input{ 185 + Collection: CollectionDocument, 186 + Record: mp, 187 + Repo: repo, 188 + Rkey: string(rkey), 189 + Validate: &t, 190 + //SwapRecord: &cid, 191 + }) 192 + if err != nil { 193 + return nil, err 194 + } 195 + return &Result{out.Uri, out.Cid, out.ValidationStatus, out.Commit}, nil 196 + } 197 + 198 + // DeleteDocument in a repo with the given rkey. 199 + func DeleteDocument(ctx context.Context, client lexutil.LexClient, repo string, rkey syntax.RecordKey) error { 200 + _, err := atproto.RepoDeleteRecord(ctx, client, &atproto.RepoDeleteRecord_Input{ 201 + Collection: CollectionDocument, 202 + Repo: repo, 203 + Rkey: string(rkey), 204 + }) 205 + return err 206 + }
+74 -4
document_test.go
··· 1 1 package site_test 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 6 "slices" 6 7 "testing" 7 8 "time" 8 9 10 + "github.com/bluesky-social/indigo/atproto/atclient" 11 + "github.com/bluesky-social/indigo/atproto/identity" 12 + "github.com/bluesky-social/indigo/atproto/syntax" 13 + lexutil "github.com/bluesky-social/indigo/lex/util" 9 14 site "tangled.org/anhgelus.world/goat-site" 10 15 ) 11 16 ··· 14 19 ` 15 20 16 21 func TestDocument_JSON(t *testing.T) { 17 - var v *site.LexiconJSON 22 + var v *site.RecordJSON 18 23 err := json.Unmarshal([]byte(sampleDoc), &v) 19 24 if err != nil { 20 25 t.Fatal(err) 21 26 } 22 - doc := v.Lexicon.(*site.Document) 27 + doc := v.Record.(*site.Document) 23 28 if doc.Site != `at://did:plc:jdhpqeb4cb4mng533dx56cbc/site.standard.publication/3mhm4m2tets2y` { 24 29 t.Errorf("invalid site: %s", doc.Site) 25 30 } ··· 34 39 t.Errorf("invalid path: %s", *doc.Path) 35 40 } 36 41 37 - if doc.Content.Lexicon != nil { 38 - t.Errorf("invalid content lexicon: %v", doc.Content.Lexicon) 42 + if doc.Content.Record != nil { 43 + t.Errorf("invalid content lexicon: %v", doc.Content.Record) 39 44 } 40 45 if doc.Content.Type != `pub.leaflet.content` { 41 46 t.Errorf("invalid content type: %s", doc.Content.Type) ··· 53 58 } 54 59 t.Log(string(b)) 55 60 } 61 + 62 + const testDoc = "at://did:plc:jdhpqeb4cb4mng533dx56cbc/site.standard.document/3mhm4obhnx22y" 63 + 64 + var ( 65 + docURI syntax.ATURI 66 + docClient *lexutil.LexClient 67 + ) 68 + 69 + func getClient(t *testing.T) (syntax.ATURI, lexutil.LexClient) { 70 + var err error 71 + defer func() { 72 + if err == nil { 73 + t.Log(docURI.String()) 74 + } 75 + }() 76 + if docClient != nil { 77 + return docURI, *docClient 78 + } 79 + dir := identity.DefaultDirectory() 80 + docURI, err = syntax.ParseATURI(testDoc) 81 + if err != nil { 82 + t.Fatal(err) 83 + } 84 + var id *identity.Identity 85 + id, err = dir.Lookup(context.Background(), docURI.Authority()) 86 + if err != nil { 87 + t.Fatal(err) 88 + } 89 + client := lexutil.LexClient(atclient.NewAPIClient(id.PDSEndpoint())) 90 + docClient = &client 91 + return docURI, *docClient 92 + } 93 + 94 + func TestGetDocument(t *testing.T) { 95 + if testing.Short() { 96 + t.Skip() 97 + } 98 + uri, client := getClient(t) 99 + doc, err := site.GetDocument(context.Background(), client, uri.Authority().String(), uri.RecordKey()) 100 + if err != nil { 101 + t.Fatal(err) 102 + } 103 + if doc == nil { 104 + t.Errorf("doc is nil") 105 + } 106 + } 107 + 108 + func TestListDocuments(t *testing.T) { 109 + if testing.Short() { 110 + t.Skip() 111 + } 112 + uri, client := getClient(t) 113 + docs, _, err := site.ListDocuments(context.Background(), client, uri.Authority().String(), "", false) 114 + if err != nil { 115 + t.Fatal(err) 116 + } 117 + if docs == nil { 118 + t.Errorf("docs is nil") 119 + } 120 + for i, doc := range docs { 121 + if doc == nil { 122 + t.Errorf("doc %d is nil", i) 123 + } 124 + } 125 + }
+15 -1
go.mod
··· 2 2 3 3 go 1.25.0 4 4 5 + require github.com/bluesky-social/indigo v0.0.0-20260318212431-cbaa83aee9dd 6 + 5 7 require ( 6 - github.com/bluesky-social/indigo v0.0.0-20260318212431-cbaa83aee9dd // indirect 8 + github.com/beorn7/perks v1.0.1 // indirect 9 + github.com/cespare/xxhash/v2 v2.2.0 // indirect 10 + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 11 + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 7 12 github.com/ipfs/go-cid v0.4.1 // indirect 8 13 github.com/klauspost/cpuid/v2 v2.2.7 // indirect 14 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 9 15 github.com/minio/sha256-simd v1.0.1 // indirect 10 16 github.com/mr-tron/base58 v1.2.0 // indirect 11 17 github.com/multiformats/go-base32 v0.1.0 // indirect ··· 13 19 github.com/multiformats/go-multibase v0.2.0 // indirect 14 20 github.com/multiformats/go-multihash v0.2.3 // indirect 15 21 github.com/multiformats/go-varint v0.0.7 // indirect 22 + github.com/prometheus/client_golang v1.17.0 // indirect 23 + github.com/prometheus/client_model v0.5.0 // indirect 24 + github.com/prometheus/common v0.45.0 // indirect 25 + github.com/prometheus/procfs v0.12.0 // indirect 16 26 github.com/spaolacci/murmur3 v1.1.0 // indirect 17 27 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect 28 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 29 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 18 30 golang.org/x/crypto v0.21.0 // indirect 19 31 golang.org/x/sys v0.22.0 // indirect 32 + golang.org/x/time v0.3.0 // indirect 20 33 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 34 + google.golang.org/protobuf v1.33.0 // indirect 21 35 lukechampine.com/blake3 v1.2.1 // indirect 22 36 )
+36
go.sum
··· 1 + github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 + github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 1 3 github.com/bluesky-social/indigo v0.0.0-20260318212431-cbaa83aee9dd h1:FZSMlxClfm7jCA6A/vwTNw5EPxSngPPpK09MxuEx9l0= 2 4 github.com/bluesky-social/indigo v0.0.0-20260318212431-cbaa83aee9dd/go.mod h1:VG/LeqLGNI3Ew7lsYixajnZGFfWPv144qbUddh+Oyag= 5 + github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 + github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 + github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 10 + github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 11 + github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 12 + github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 13 + github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 14 + github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 3 15 github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= 4 16 github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 5 17 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 6 18 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 19 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 20 + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 7 21 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 8 22 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 9 23 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= ··· 18 32 github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 19 33 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 20 34 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 35 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 36 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 37 + github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 38 + github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 39 + github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 40 + github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 41 + github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 42 + github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 43 + github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 44 + github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 21 45 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 22 46 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 47 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 48 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 23 49 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4= 24 50 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 51 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 52 + gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 53 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 54 + gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 25 55 golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 26 56 golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 27 57 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 58 golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 29 59 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 60 + golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 61 + golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 30 62 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 31 63 golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 64 + google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 65 + google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 66 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 67 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 32 68 lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 33 69 lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
+53 -33
lexicons.go
··· 2 2 3 3 import ( 4 4 "encoding/json" 5 + "errors" 6 + 7 + "github.com/bluesky-social/indigo/api/agnostic" 5 8 ) 6 9 7 - type Lexicon interface { 10 + // Record represents an ATProto record. 11 + type Record interface { 8 12 Type() string 9 13 } 10 14 11 15 const ( 12 - LexiconBase = "site.standard" 13 - LexiconBlob = "blob" 16 + // CollectionBase is the base NSID for Standard.site. 17 + CollectionBase = "site.standard" 18 + CollectionBlob = "blob" 14 19 15 20 TimeFormat = "2006-01-02T15:04:05.000Z" 16 21 ) 17 22 18 - // LexiconJSON is used to encode and decode [Lexicon] from JSON. 19 - type LexiconJSON struct { 20 - // Lexicon parsed. 21 - // Nil if [Lexicon] is unknown. 22 - Lexicon Lexicon 23 - // Type stored if [Lexicon] is unknown. 23 + // RecordJSON is used to encode and to decode [Record] from JSON. 24 + type RecordJSON struct { 25 + // Record parsed. 26 + // Nil if [Record] is unknown. 27 + Record Record 28 + // Type stored if [Record] is unknown. 24 29 // Set after [json.Unmarshal]. 25 30 Type string 26 - // Raw returns bytes stored if [Lexicon] is unknown. 31 + // Raw returns bytes stored if [Record] is unknown. 27 32 // Set after [json.Unmarshal]. 28 33 Raw []byte 29 34 } 30 35 31 - func (l *LexiconJSON) MarshalJSON() ([]byte, error) { 32 - if l.Lexicon == nil { 36 + func (l *RecordJSON) MarshalJSON() ([]byte, error) { 37 + if l.Record == nil { 33 38 return l.Raw, nil 34 39 } 35 40 mp, err := l.MarshalMap() 36 41 if err != nil { 37 42 return nil, err 38 43 } 39 - mp["$type"] = l.Lexicon.Type() 44 + mp["$type"] = l.Record.Type() 40 45 return json.Marshal(mp) 41 46 } 42 47 43 - func (l *LexiconJSON) MarshalMap() (mp map[string]any, err error) { 44 - if l.Lexicon == nil { 48 + func (l *RecordJSON) MarshalMap() (mp map[string]any, err error) { 49 + if l.Record == nil { 45 50 err = json.Unmarshal(l.Raw, &mp) 46 51 return 47 52 } 48 - mp, err = MarshalToMap(l.Lexicon) 53 + mp, err = MarshalToMap(l.Record) 49 54 return 50 55 } 51 56 52 - func (l *LexiconJSON) UnmarshalJSON(b []byte) error { 57 + func (l *RecordJSON) UnmarshalJSON(b []byte) error { 53 58 var v struct { 54 59 Type string `json:"$type"` 55 60 } ··· 58 63 return err 59 64 } 60 65 switch v.Type { 61 - case LexiconPublication: 62 - l.Lexicon = &Publication{} 63 - case LexiconDocument: 64 - l.Lexicon = &Document{} 65 - case LexiconSubscription: 66 - l.Lexicon = &Subscription{} 67 - case LexiconThemeBasic: 68 - l.Lexicon = &Theme{} 69 - case LexiconThemeColorRGB: 70 - l.Lexicon = &RGB{} 71 - case LexiconThemeColorRGBA: 72 - l.Lexicon = &RGBA{} 73 - case LexiconBlob: 74 - l.Lexicon = &Blob{} 66 + case CollectionPublication: 67 + l.Record = &Publication{} 68 + case CollectionDocument: 69 + l.Record = &Document{} 70 + case CollectionSubscription: 71 + l.Record = &Subscription{} 72 + case CollectionThemeBasic: 73 + l.Record = &Theme{} 74 + case CollectionThemeColorRGB: 75 + l.Record = &RGB{} 76 + case CollectionThemeColorRGBA: 77 + l.Record = &RGBA{} 78 + case CollectionBlob: 79 + l.Record = &Blob{} 75 80 default: 76 81 l.Raw = b 77 82 l.Type = v.Type 78 83 return nil 79 84 } 80 - return json.Unmarshal(b, l.Lexicon) 85 + return json.Unmarshal(b, l.Record) 81 86 } 82 87 83 88 // Blob represents an ATProto `blob` type. ··· 88 93 } 89 94 90 95 func (b *Blob) Type() string { 91 - return LexiconBlob 96 + return CollectionBlob 92 97 } 93 98 94 99 func (b *Blob) MarshalMap() (map[string]any, error) { ··· 115 120 b.CID = v.Ref.Link 116 121 return nil 117 122 } 123 + 124 + var ( 125 + ErrInvalidType = errors.New("invalid collection type") 126 + ) 127 + 128 + // MaxItemsPerList is the number of items per list call. 129 + const MaxItemsPerList = 25 130 + 131 + // Result is returned when after creating a record. 132 + type Result struct { 133 + URI string 134 + CID string 135 + ValidationStatus *string 136 + Commit *agnostic.RepoDefs_CommitMeta 137 + }
+2 -2
map.go
··· 40 40 return nil, nil 41 41 } 42 42 val := v.Interface() 43 - if conv, ok := val.(Lexicon); ok { 44 - val = &LexiconJSON{Lexicon: conv} 43 + if conv, ok := val.(Record); ok { 44 + val = &RecordJSON{Record: conv} 45 45 } 46 46 if conv, ok := val.(MarshalerMap); ok { 47 47 return conv.MarshalMap()
+6 -5
publication.go
··· 2 2 3 3 import "strings" 4 4 5 - const LexiconPublication = LexiconBase + ".publication" 5 + const CollectionPublication = CollectionBase + ".publication" 6 6 7 - // Publication represents a collection of documents published to the web. 7 + // Publication represents a collection of [Document]s published to the web. 8 8 // It includes important information about a publication including its location on the web, theming information, user 9 - // preferences, and more. 9 + // [Preferences], and more. 10 10 // 11 - // The publication lexicon is not a requirement, but is recommended when publishing collections of related documents. 11 + // The [Publication] [Record] is not a requirement, but is recommended when publishing collections of related 12 + // [Document]s. 12 13 type Publication struct { 13 14 // Base URL of the [Publication]. 14 15 // This value will be combined with the [Document.Path] to construct a full URL for the document. ··· 32 33 } 33 34 34 35 func (p *Publication) Type() string { 35 - return LexiconPublication 36 + return CollectionPublication 36 37 } 37 38 38 39 func (p *Publication) MarshalMap() (map[string]any, error) {
+2 -2
publication_test.go
··· 72 72 }` 73 73 74 74 func TestPublication_JSON(t *testing.T) { 75 - var v *site.LexiconJSON 75 + var v *site.RecordJSON 76 76 err := json.Unmarshal([]byte(samplePub), &v) 77 77 if err != nil { 78 78 t.Fatal(err) 79 79 } 80 - pub := v.Lexicon.(*site.Publication) 80 + pub := v.Record.(*site.Publication) 81 81 if pub.Name != "pckt - Dev Journal" { 82 82 t.Errorf("invalid name: %s", pub.Name) 83 83 }
+2 -2
subscription.go
··· 1 1 package site 2 2 3 - const LexiconSubscription = LexiconBase + ".graph.subscription" 3 + const CollectionSubscription = CollectionBase + ".graph.subscription" 4 4 5 5 // Subscription enable users to follow publications and receive updates about new content. 6 6 // They represent the social connection between readers and the publications they're interested in. ··· 11 11 } 12 12 13 13 func (s *Subscription) Type() string { 14 - return LexiconSubscription 14 + return CollectionSubscription 15 15 }
+8 -8
theme.go
··· 7 7 ) 8 8 9 9 const ( 10 - LexiconTheme = LexiconBase + ".theme" 11 - LexiconThemeBasic = LexiconTheme + ".basic" 12 - LexiconThemeColor = LexiconTheme + ".color" 13 - LexiconThemeColorRGB = LexiconThemeColor + "#rgb" 14 - LexiconThemeColorRGBA = LexiconThemeColor + "#rgba" 10 + CollectionTheme = CollectionBase + ".theme" 11 + CollectionThemeBasic = CollectionTheme + ".basic" 12 + CollectionThemeColor = CollectionTheme + ".color" 13 + CollectionThemeColorRGB = CollectionThemeColor + "#rgb" 14 + CollectionThemeColorRGBA = CollectionThemeColor + "#rgba" 15 15 ) 16 16 17 17 // Theme ensures [Publication]s maintain their visual identity across different reading applications and platforms by ··· 28 28 } 29 29 30 30 func (t *Theme) Type() string { 31 - return LexiconThemeBasic 31 + return CollectionThemeBasic 32 32 } 33 33 34 34 // RGB represents a RGB color. ··· 45 45 } 46 46 47 47 func (r *RGB) Type() string { 48 - return LexiconThemeColorRGB 48 + return CollectionThemeColorRGB 49 49 } 50 50 51 51 func (r *RGB) RGBA() *RGBA { ··· 81 81 } 82 82 83 83 func (r *RGBA) Type() string { 84 - return LexiconThemeColorRGBA 84 + return CollectionThemeColorRGBA 85 85 } 86 86 87 87 func (r *RGBA) String() string {