this repo has no description
at main 237 lines 6.6 kB view raw
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}