An atproto PDS written in Go
at main 3.1 kB view raw
1package server 2 3import ( 4 "strconv" 5 6 "github.com/Azure/go-autorest/autorest/to" 7 "github.com/bluesky-social/indigo/atproto/atdata" 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "github.com/haileyok/cocoon/internal/helpers" 10 "github.com/haileyok/cocoon/models" 11 "github.com/labstack/echo/v4" 12) 13 14type ComAtprotoRepoListRecordsRequest struct { 15 Repo string `query:"repo" validate:"required"` 16 Collection string `query:"collection" validate:"required,atproto-nsid"` 17 Limit int64 `query:"limit"` 18 Cursor string `query:"cursor"` 19 Reverse bool `query:"reverse"` 20} 21 22type ComAtprotoRepoListRecordsResponse struct { 23 Cursor *string `json:"cursor,omitempty"` 24 Records []ComAtprotoRepoListRecordsRecordItem `json:"records"` 25} 26 27type ComAtprotoRepoListRecordsRecordItem struct { 28 Uri string `json:"uri"` 29 Cid string `json:"cid"` 30 Value map[string]any `json:"value"` 31} 32 33func getLimitFromContext(e echo.Context, def int) (int, error) { 34 limit := def 35 limitstr := e.QueryParam("limit") 36 37 if limitstr != "" { 38 l64, err := strconv.ParseInt(limitstr, 10, 32) 39 if err != nil { 40 return 0, err 41 } 42 limit = int(l64) 43 } 44 45 return limit, nil 46} 47 48func (s *Server) handleListRecords(e echo.Context) error { 49 var req ComAtprotoRepoListRecordsRequest 50 if err := e.Bind(&req); err != nil { 51 s.logger.Error("could not bind list records request", "error", err) 52 return helpers.ServerError(e, nil) 53 } 54 55 if err := e.Validate(req); err != nil { 56 return helpers.InputError(e, nil) 57 } 58 59 if req.Limit <= 0 { 60 req.Limit = 50 61 } else if req.Limit > 100 { 62 req.Limit = 100 63 } 64 65 limit, err := getLimitFromContext(e, 50) 66 if err != nil { 67 return helpers.InputError(e, nil) 68 } 69 70 sort := "DESC" 71 dir := "<" 72 cursorquery := "" 73 74 if req.Reverse { 75 sort = "ASC" 76 dir = ">" 77 } 78 79 did := req.Repo 80 if _, err := syntax.ParseDID(did); err != nil { 81 actor, err := s.getActorByHandle(req.Repo) 82 if err != nil { 83 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 84 } 85 did = actor.Did 86 } 87 88 params := []any{did, req.Collection} 89 if req.Cursor != "" { 90 params = append(params, req.Cursor) 91 cursorquery = "AND created_at " + dir + " ?" 92 } 93 params = append(params, limit) 94 95 var records []models.Record 96 if err := s.db.Raw("SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", nil, params...).Scan(&records).Error; err != nil { 97 s.logger.Error("error getting records", "error", err) 98 return helpers.ServerError(e, nil) 99 } 100 101 items := []ComAtprotoRepoListRecordsRecordItem{} 102 for _, r := range records { 103 val, err := atdata.UnmarshalCBOR(r.Value) 104 if err != nil { 105 return err 106 } 107 108 items = append(items, ComAtprotoRepoListRecordsRecordItem{ 109 Uri: "at://" + r.Did + "/" + r.Nsid + "/" + r.Rkey, 110 Cid: r.Cid, 111 Value: val, 112 }) 113 } 114 115 var newcursor *string 116 if len(records) == limit { 117 newcursor = to.StringPtr(records[len(records)-1].CreatedAt) 118 } 119 120 return e.JSON(200, ComAtprotoRepoListRecordsResponse{ 121 Cursor: newcursor, 122 Records: items, 123 }) 124}