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}