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.

refactor(xrpc): generalize crud record

+130 -87
+10 -81
document.go
··· 3 3 import ( 4 4 "context" 5 5 "encoding/json" 6 - "errors" 7 - "fmt" 8 6 "strings" 9 7 "time" 10 8 11 - "github.com/bluesky-social/indigo/api/agnostic" 12 - "github.com/bluesky-social/indigo/api/atproto" 13 9 "github.com/bluesky-social/indigo/atproto/syntax" 14 10 lexutil "github.com/bluesky-social/indigo/lex/util" 15 11 ) ··· 108 104 109 105 // GetDocument returns the [Document] in the repo associated with the rkey. 110 106 // 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 107 + func GetDocument(ctx context.Context, client lexutil.LexClient, repo syntax.AtIdentifier, rkey syntax.RecordKey) (*Document, error) { 108 + return get[*Document](ctx, client, CollectionDocument, repo, rkey) 125 109 } 126 110 127 111 // ListDocuments returns all the [Document]s stored in the repo and the cursor. 128 112 // 129 113 // 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 114 + func ListDocuments(ctx context.Context, client lexutil.LexClient, repo syntax.AtIdentifier, cursor string, reverse bool) ([]*Document, *string, error) { 115 + return listRecord[*Document](ctx, client, CollectionDocument, repo, cursor, reverse) 146 116 } 147 117 148 118 // CreateDocument in a repo with the given rkey. 149 119 // Always tries to validate the [Document] against the [Record] saved. 150 120 // 151 121 // 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 122 + func CreateDocument(ctx context.Context, client lexutil.LexClient, repo syntax.AtIdentifier, rkey *syntax.RecordKey, doc *Document) (*Result, error) { 123 + return createRecord(ctx, client, CollectionDocument, repo, rkey, doc) 174 124 } 175 125 176 126 // UpdateDocument in a repo with the given rkey. 177 127 // 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 128 + func UpdateDocument(ctx context.Context, client lexutil.LexClient, repo syntax.AtIdentifier, rkey syntax.RecordKey, doc *Document) (*Result, error) { 129 + return updateRecord(ctx, client, CollectionDocument, repo, rkey, doc) 196 130 } 197 131 198 132 // 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 133 + func DeleteDocument(ctx context.Context, client lexutil.LexClient, repo syntax.AtIdentifier, rkey syntax.RecordKey) error { 134 + return deleteRecord(ctx, client, CollectionDocument, repo, rkey) 206 135 }
+2 -2
document_test.go
··· 96 96 t.Skip() 97 97 } 98 98 uri, client := getClient(t) 99 - doc, err := site.GetDocument(context.Background(), client, uri.Authority().String(), uri.RecordKey()) 99 + doc, err := site.GetDocument(context.Background(), client, uri.Authority(), uri.RecordKey()) 100 100 if err != nil { 101 101 t.Fatal(err) 102 102 } ··· 110 110 t.Skip() 111 111 } 112 112 uri, client := getClient(t) 113 - docs, _, err := site.ListDocuments(context.Background(), client, uri.Authority().String(), "", false) 113 + docs, _, err := site.ListDocuments(context.Background(), client, uri.Authority(), "", false) 114 114 if err != nil { 115 115 t.Fatal(err) 116 116 }
+118 -4
lexicons.go
··· 1 1 package site 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 - "errors" 6 + "fmt" 6 7 7 8 "github.com/bluesky-social/indigo/api/agnostic" 9 + "github.com/bluesky-social/indigo/api/atproto" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 11 + lexutil "github.com/bluesky-social/indigo/lex/util" 8 12 ) 9 13 10 14 // Record represents an ATProto record. ··· 121 125 return nil 122 126 } 123 127 124 - var ( 125 - ErrInvalidType = errors.New("invalid collection type") 126 - ) 128 + type ErrInvalidType struct { 129 + expected, got string 130 + } 131 + 132 + func (err ErrInvalidType) Error() string { 133 + return fmt.Sprintf("invalid collection type: expected %s, got %s", err.expected, err.got) 134 + } 127 135 128 136 // MaxItemsPerList is the number of items per list call. 129 137 const MaxItemsPerList = 25 ··· 135 143 ValidationStatus *string 136 144 Commit *agnostic.RepoDefs_CommitMeta 137 145 } 146 + 147 + // get returns the T in the repo associated with the rkey. 148 + // Automatically uses the latest CID. 149 + func get[T Record](ctx context.Context, client lexutil.LexClient, collection string, repo syntax.AtIdentifier, rkey syntax.RecordKey) (t T, err error) { 150 + var rec *agnostic.RepoGetRecord_Output 151 + rec, err = agnostic.RepoGetRecord(ctx, client, "", collection, repo.String(), rkey.String()) 152 + if err != nil { 153 + return 154 + } 155 + var v *RecordJSON 156 + err = json.Unmarshal(*rec.Value, &v) 157 + if err != nil { 158 + return 159 + } 160 + if v.Record == nil { 161 + err = ErrInvalidType{collection, v.Type} 162 + return 163 + } 164 + return v.Record.(T), nil 165 + } 166 + 167 + // listRecord returns all the Ts stored in the repo and the cursor. 168 + // 169 + // See [MaxItemsPerList]. 170 + func listRecord[T Record](ctx context.Context, client lexutil.LexClient, collection string, repo syntax.AtIdentifier, cursor string, reverse bool) ([]T, *string, error) { 171 + rec, err := agnostic.RepoListRecords(ctx, client, collection, cursor, MaxItemsPerList, repo.String(), reverse) 172 + if err != nil { 173 + return nil, nil, err 174 + } 175 + docs := make([]T, MaxItemsPerList) 176 + i := 0 177 + for i < len(rec.Records) { 178 + r := rec.Records[i] 179 + var v *RecordJSON 180 + err = json.Unmarshal(*r.Value, &v) 181 + if err != nil { 182 + return nil, nil, err 183 + } 184 + if v.Record == nil { 185 + return nil, nil, ErrInvalidType{collection, v.Type} 186 + } 187 + docs[i] = v.Record.(T) 188 + i++ 189 + } 190 + return docs[:i], rec.Cursor, nil 191 + } 192 + 193 + // createRecord a T in a repo with the given rkey. 194 + // Always tries to validate the [Document] against the [Record] saved. 195 + // 196 + // Rkey can be nil. 197 + func createRecord[T Record](ctx context.Context, client lexutil.LexClient, collection string, repo syntax.AtIdentifier, rkey *syntax.RecordKey, v T) (*Result, error) { 198 + mp, err := MarshalToMap(&RecordJSON{Record: v}) 199 + if err != nil { 200 + return nil, err 201 + } 202 + var cv *string 203 + if rkey != nil { 204 + t := rkey.String() 205 + cv = &t 206 + } 207 + t := true 208 + out, err := agnostic.RepoCreateRecord(ctx, client, &agnostic.RepoCreateRecord_Input{ 209 + Collection: collection, 210 + Record: mp, 211 + Repo: repo.String(), 212 + Rkey: cv, 213 + Validate: &t, 214 + }) 215 + if err != nil { 216 + return nil, err 217 + } 218 + return &Result{out.Uri, out.Cid, out.ValidationStatus, out.Commit}, nil 219 + } 220 + 221 + // updateRecord T in a repo with the given rkey. 222 + // Always tries to validate the [Document] against the [Record] saved. 223 + func updateRecord[T Record](ctx context.Context, client lexutil.LexClient, collection string, repo syntax.AtIdentifier, rkey syntax.RecordKey, v T) (*Result, error) { 224 + mp, err := MarshalToMap(&RecordJSON{Record: v}) 225 + if err != nil { 226 + return nil, err 227 + } 228 + t := true 229 + out, err := agnostic.RepoPutRecord(ctx, client, &agnostic.RepoPutRecord_Input{ 230 + Collection: collection, 231 + Record: mp, 232 + Repo: repo.String(), 233 + Rkey: rkey.String(), 234 + Validate: &t, 235 + //SwapRecord: &cid, 236 + }) 237 + if err != nil { 238 + return nil, err 239 + } 240 + return &Result{out.Uri, out.Cid, out.ValidationStatus, out.Commit}, nil 241 + } 242 + 243 + // delete in a repo with the given rkey. 244 + func deleteRecord(ctx context.Context, client lexutil.LexClient, collection string, repo syntax.AtIdentifier, rkey syntax.RecordKey) error { 245 + _, err := atproto.RepoDeleteRecord(ctx, client, &atproto.RepoDeleteRecord_Input{ 246 + Collection: collection, 247 + Repo: repo.String(), 248 + Rkey: rkey.String(), 249 + }) 250 + return err 251 + }