this repo has no description
1package server
2
3import (
4 "context"
5 "fmt"
6 "strings"
7 "time"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "github.com/gocql/gocql"
11 vyletdatabase "github.com/vylet-app/go/database/proto"
12 "github.com/vylet-app/go/internal/helpers"
13 "google.golang.org/protobuf/types/known/timestamppb"
14)
15
16func (s *Server) CreateLike(ctx context.Context, req *vyletdatabase.CreateLikeRequest) (*vyletdatabase.CreateLikeResponse, error) {
17 logger := s.logger.With("name", "CreateLike")
18
19 aturi, err := syntax.ParseATURI(req.Like.Uri)
20 if err != nil {
21 return nil, fmt.Errorf("failed to parse aturi: %w", err)
22 }
23
24 did := aturi.Authority().String()
25 now := time.Now().UTC()
26
27 batch := s.cqlSession.NewBatch(gocql.LoggedBatch).WithContext(ctx)
28
29 likeArgs := []any{
30 req.Like.Uri,
31 req.Like.Cid,
32 req.Like.SubjectUri,
33 req.Like.SubjectCid,
34 did,
35 req.Like.CreatedAt.AsTime(),
36 now,
37 }
38
39 likeQuery := `
40 INSERT INTO %s
41 (uri, cid, subject_uri, subject_cid, author_did, created_at, indexed_at)
42 VALUES
43 (?, ?, ?, ?, ?, ?, ?)
44 `
45
46 batch.Query(fmt.Sprintf(likeQuery, "likes_by_subject"), likeArgs...)
47 batch.Query(fmt.Sprintf(likeQuery, "likes_by_actor"), likeArgs...)
48 batch.Query(fmt.Sprintf(likeQuery, "likes_by_uri"), likeArgs...)
49 batch.Query(fmt.Sprintf(likeQuery, "likes_by_actor_subject"), likeArgs...)
50
51 if err := s.cqlSession.ExecuteBatch(batch); err != nil {
52 logger.Error("failed to create like", "uri", req.Like.Uri, "err", err)
53 return &vyletdatabase.CreateLikeResponse{
54 Error: helpers.ToStringPtr(err.Error()),
55 }, nil
56 }
57
58 if err := s.cqlSession.Query(`
59 UPDATE post_interaction_counts
60 SET like_count = like_count + 1
61 WHERE post_uri = ?
62 `, req.Like.SubjectUri).WithContext(ctx).Exec(); err != nil {
63 logger.Error("failed to increment like count", "subject_uri", req.Like.SubjectUri, "err", err)
64 return &vyletdatabase.CreateLikeResponse{
65 Error: helpers.ToStringPtr(err.Error()),
66 }, nil
67 }
68
69 return &vyletdatabase.CreateLikeResponse{}, nil
70}
71
72func (s *Server) DeleteLike(ctx context.Context, req *vyletdatabase.DeleteLikeRequest) (*vyletdatabase.DeleteLikeResponse, error) {
73 logger := s.logger.With("name", "DeleteLike", "uri", req.Uri)
74
75 var (
76 createdAt time.Time
77 subjectUri string
78 authorDid string
79 )
80
81 query := `
82 SELECT created_at, subject_uri, author_did
83 FROM likes_by_uri
84 WHERE uri = ?
85 `
86 if err := s.cqlSession.Query(query, req.Uri).WithContext(ctx).Scan(&createdAt, &subjectUri, &authorDid); err != nil {
87 if err == gocql.ErrNotFound {
88 logger.Warn("like not found", "uri", req.Uri)
89 return &vyletdatabase.DeleteLikeResponse{
90 Error: helpers.ToStringPtr("like not found"),
91 }, nil
92 }
93 logger.Error("failed to fetch like", "uri", req.Uri, "err", err)
94 return &vyletdatabase.DeleteLikeResponse{
95 Error: helpers.ToStringPtr(err.Error()),
96 }, nil
97 }
98
99 batch := s.cqlSession.NewBatch(gocql.LoggedBatch).WithContext(ctx)
100
101 batch.Query(`
102 DELETE FROM likes_by_uri
103 WHERE uri = ?
104 `, req.Uri)
105
106 batch.Query(`
107 DELETE FROM likes_by_subject
108 WHERE subject_uri = ? AND created_at = ? AND uri = ?
109 `, subjectUri, createdAt, req.Uri)
110
111 batch.Query(`
112 DELETE FROM likes_by_actor
113 WHERE author_did = ? AND created_at = ? AND uri = ?
114 `, authorDid, createdAt, req.Uri)
115
116 batch.Query(`
117 DELETE FROM likes_by_actor_subject
118 WHERE author_did = ? AND subject_uri = ?
119 `, authorDid, subjectUri)
120
121 if err := s.cqlSession.ExecuteBatch(batch); err != nil {
122 logger.Error("failed to delete like", "uri", req.Uri, "err", err)
123 return &vyletdatabase.DeleteLikeResponse{
124 Error: helpers.ToStringPtr(err.Error()),
125 }, nil
126 }
127
128 if err := s.cqlSession.Query(`
129 UPDATE post_interaction_counts
130 SET like_count = like_count - 1
131 WHERE post_uri = ?
132 `, subjectUri).WithContext(ctx).Exec(); err != nil {
133 logger.Error("failed to increment like count", "subject_uri", subjectUri, "err", err)
134 return &vyletdatabase.DeleteLikeResponse{
135 Error: helpers.ToStringPtr(err.Error()),
136 }, nil
137 }
138
139 return &vyletdatabase.DeleteLikeResponse{}, nil
140}
141
142func (s *Server) GetLikesBySubject(ctx context.Context, req *vyletdatabase.GetLikesBySubjectRequest) (*vyletdatabase.GetLikesBySubjectResponse, error) {
143 logger := s.logger.With("name", "GetLikesBySubject", "subjectUri", req.SubjectUri)
144
145 if req.Limit <= 0 {
146 return nil, fmt.Errorf("limit must be greater than 0")
147 }
148
149 var (
150 query string
151 args []any
152 )
153
154 if req.Cursor != nil && *req.Cursor != "" {
155 cursorParts := strings.SplitN(*req.Cursor, "|", 2)
156 if len(cursorParts) != 2 {
157 logger.Error("invalid cursor format", "cursor", *req.Cursor)
158 return &vyletdatabase.GetLikesBySubjectResponse{
159 Error: helpers.ToStringPtr("invalid cursor format"),
160 }, nil
161 }
162
163 cursorTime, err := time.Parse(time.RFC3339Nano, cursorParts[0])
164 if err != nil {
165 logger.Error("failed to parse cursor timestamp", "cursor", *req.Cursor, "err", err)
166 return &vyletdatabase.GetLikesBySubjectResponse{
167 Error: helpers.ToStringPtr("invalid cursor format"),
168 }, nil
169 }
170 cursorUri := cursorParts[1]
171
172 query = `
173 SELECT uri, cid, subject_uri, subject_cid, author_did, created_at, indexed_at
174 FROM likes_by_subject
175 WHERE subject_uri = ? AND (created_at, uri) < (?, ?)
176 ORDER BY created_at DESC, uri ASC
177 LIMIT ?
178 `
179 args = []any{req.SubjectUri, cursorTime, cursorUri, req.Limit + 1}
180 } else {
181 query = `
182 SELECT uri, cid, subject_uri, subject_cid, author_did, created_at, indexed_at
183 FROM likes_by_subject
184 WHERE subject_uri = ?
185 ORDER BY created_at DESC, uri ASC
186 LIMIT ?
187 `
188 args = []any{req.SubjectUri, req.Limit}
189 }
190
191 iter := s.cqlSession.Query(query, args...).WithContext(ctx).Iter()
192 defer iter.Close()
193
194 var likes []*vyletdatabase.Like
195
196 var createdAt time.Time
197 var indexedAt time.Time
198 for {
199 like := &vyletdatabase.Like{}
200 if !iter.Scan(
201 &like.Uri,
202 &like.Cid,
203 &like.SubjectUri,
204 &like.SubjectCid,
205 &like.AuthorDid,
206 &createdAt,
207 &indexedAt,
208 ) {
209 break
210 }
211 like.CreatedAt = timestamppb.New(createdAt)
212 like.IndexedAt = timestamppb.New(indexedAt)
213
214 likes = append(likes, like)
215 }
216 if err := iter.Close(); err != nil {
217 logger.Error("failed to iterate likes", "err", err)
218 return &vyletdatabase.GetLikesBySubjectResponse{
219 Error: helpers.ToStringPtr(err.Error()),
220 }, nil
221 }
222
223 var nextCursor *string
224 if len(likes) > int(req.Limit) {
225 likes = likes[:req.Limit]
226 lastLike := likes[len(likes)-1]
227 cursorStr := fmt.Sprintf("%s|%s",
228 lastLike.CreatedAt.AsTime().Format(time.RFC3339Nano),
229 lastLike.Uri)
230 nextCursor = &cursorStr
231 }
232
233 return &vyletdatabase.GetLikesBySubjectResponse{
234 Likes: likes,
235 Cursor: nextCursor,
236 }, nil
237}