forked from hailey.at/cocoon
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 ctx := e.Request().Context() 50 logger := s.logger.With("name", "handleListRecords") 51 52 var req ComAtprotoRepoListRecordsRequest 53 if err := e.Bind(&req); err != nil { 54 logger.Error("could not bind list records request", "error", err) 55 return helpers.ServerError(e, nil) 56 } 57 58 if err := e.Validate(req); err != nil { 59 return helpers.InputError(e, nil) 60 } 61 62 if req.Limit <= 0 { 63 req.Limit = 50 64 } else if req.Limit > 100 { 65 req.Limit = 100 66 } 67 68 limit, err := getLimitFromContext(e, 50) 69 if err != nil { 70 return helpers.InputError(e, nil) 71 } 72 73 sort := "DESC" 74 dir := "<" 75 cursorquery := "" 76 77 if req.Reverse { 78 sort = "ASC" 79 dir = ">" 80 } 81 82 did := req.Repo 83 if _, err := syntax.ParseDID(did); err != nil { 84 actor, err := s.getActorByHandle(ctx, req.Repo) 85 if err != nil { 86 return helpers.InputError(e, to.StringPtr("RepoNotFound")) 87 } 88 did = actor.Did 89 } 90 91 params := []any{did, req.Collection} 92 if req.Cursor != "" { 93 params = append(params, req.Cursor) 94 cursorquery = "AND created_at " + dir + " ?" 95 } 96 params = append(params, limit) 97 98 var records []models.Record 99 if err := s.db.Raw(ctx, "SELECT * FROM records WHERE did = ? AND nsid = ? "+cursorquery+" ORDER BY created_at "+sort+" limit ?", nil, params...).Scan(&records).Error; err != nil { 100 logger.Error("error getting records", "error", err) 101 return helpers.ServerError(e, nil) 102 } 103 104 items := []ComAtprotoRepoListRecordsRecordItem{} 105 for _, r := range records { 106 val, err := atdata.UnmarshalCBOR(r.Value) 107 if err != nil { 108 return err 109 } 110 111 items = append(items, ComAtprotoRepoListRecordsRecordItem{ 112 Uri: "at://" + r.Did + "/" + r.Nsid + "/" + r.Rkey, 113 Cid: r.Cid, 114 Value: val, 115 }) 116 } 117 118 var newcursor *string 119 if len(records) == limit { 120 newcursor = to.StringPtr(records[len(records)-1].CreatedAt) 121 } 122 123 return e.JSON(200, ComAtprotoRepoListRecordsResponse{ 124 Cursor: newcursor, 125 Records: items, 126 }) 127}