nandi/gleanpublic⑂ Fork 0
⑂ 5c03e2f
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Handle duplicate likes from PDSUnverified

Julien Robert committed 2026-04-21T14:00:07+02:00 Browse files
5c03e2f parent: 1756815
modified internal/atproto/stream_handler.go +5 -1
@@ -94,7 +94,7 @@ func (h *StreamDBHandler) handleLike(ctx context.Context, event *Event) error {
9494 }
9595
9696 t, _ := time.Parse(time.RFC3339, rec.CreatedAt)
97- return h.db.CreateLike(ctx, &db.Like{
97+ err = h.db.CreateLike(ctx, &db.Like{
9898 URI: event.URI,
9999 AuthorDID: event.DID,
100100 FeedURL: rec.FeedURL,
@@ -102,6 +102,10 @@ func (h *StreamDBHandler) handleLike(ctx context.Context, event *Event) error {
102102 CreatedAt: sql.NullTime{Time: t, Valid: true},
103103 CID: sql.NullString{String: event.CID, Valid: event.CID != ""},
104104 })
105+ if errors.Is(err, db.ErrDuplicateLike) {
106+ return nil
107+ }
108+ return err
105109
106110 case "delete":
107111 return h.db.DeleteLike(ctx, event.URI)
@@ -94,7 +94,7 @@ func (h *StreamDBHandler) handleLike(ctx context.Context, event *Event) error {
94 }94 }
95 95
96 t, _ := time.Parse(time.RFC3339, rec.CreatedAt)96 t, _ := time.Parse(time.RFC3339, rec.CreatedAt)
97- return h.db.CreateLike(ctx, &db.Like{97+ err = h.db.CreateLike(ctx, &db.Like{
98 URI: event.URI,98 URI: event.URI,
99 AuthorDID: event.DID,99 AuthorDID: event.DID,
100 FeedURL: rec.FeedURL,100 FeedURL: rec.FeedURL,
@@ -102,6 +102,10 @@ func (h *StreamDBHandler) handleLike(ctx context.Context, event *Event) error {
102 CreatedAt: sql.NullTime{Time: t, Valid: true},102 CreatedAt: sql.NullTime{Time: t, Valid: true},
103 CID: sql.NullString{String: event.CID, Valid: event.CID != ""},103 CID: sql.NullString{String: event.CID, Valid: event.CID != ""},
104 })104 })
105+ if errors.Is(err, db.ErrDuplicateLike) {
106+ return nil
107+ }
108+ return err
105 109
106 case "delete":110 case "delete":
107 return h.db.DeleteLike(ctx, event.URI)111 return h.db.DeleteLike(ctx, event.URI)
modified internal/atproto/sync.go +5 -1
@@ -132,7 +132,11 @@ func (s *Sync) reconcileLike(ctx context.Context, userDID, uri, cid string, valu
132132 CreatedAt: db.NullTime(t),
133133 CID: db.NullStr(cid),
134134 }
135- return s.db.CreateLike(ctx, like)
135+ err = s.db.CreateLike(ctx, like)
136+ if errors.Is(err, db.ErrDuplicateLike) {
137+ return nil
138+ }
139+ return err
136140 }
137141
138142 func (s *Sync) reconcileAnnotation(ctx context.Context, userDID, uri, cid string, value json.RawMessage) error {
@@ -132,7 +132,11 @@ func (s *Sync) reconcileLike(ctx context.Context, userDID, uri, cid string, valu
132 CreatedAt: db.NullTime(t),132 CreatedAt: db.NullTime(t),
133 CID: db.NullStr(cid),133 CID: db.NullStr(cid),
134 }134 }
135- return s.db.CreateLike(ctx, like)135+ err = s.db.CreateLike(ctx, like)
136+ if errors.Is(err, db.ErrDuplicateLike) {
137+ return nil
138+ }
139+ return err
136 }140 }
137 141
138 func (s *Sync) reconcileAnnotation(ctx context.Context, userDID, uri, cid string, value json.RawMessage) error {142 func (s *Sync) reconcileAnnotation(ctx context.Context, userDID, uri, cid string, value json.RawMessage) error {
modified internal/db/social.go +13 -3
@@ -3,9 +3,12 @@ package db
33 import (
44 "context"
55 "database/sql"
6+ "errors"
67 "strings"
78 )
89
10+var ErrDuplicateLike = errors.New("already liked this article")
11+
912 type Annotation struct {
1013 ID int64
1114 URI string
@@ -116,11 +119,18 @@ func (db *DB) ListAnnotations(ctx context.Context, feedURL, articleURL, authorDI
116119 }
117120
118121 func (db *DB) CreateLike(ctx context.Context, l *Like) error {
119- _, err := db.ExecContext(ctx, `
120- INSERT INTO likes (uri, author_did, feed_url, article_url, created_at, cid)
122+ result, err := db.ExecContext(ctx, `
123+ INSERT OR IGNORE INTO likes (uri, author_did, feed_url, article_url, created_at, cid)
121124 VALUES (?, ?, ?, ?, ?, ?)
122125 `, l.URI, l.AuthorDID, l.FeedURL, l.ArticleURL, l.CreatedAt, l.CID)
123- return err
126+ if err != nil {
127+ return err
128+ }
129+ n, _ := result.RowsAffected()
130+ if n == 0 {
131+ return ErrDuplicateLike
132+ }
133+ return nil
124134 }
125135
126136 func (db *DB) DeleteLike(ctx context.Context, uri string) error {
@@ -3,9 +3,12 @@ package db
3 import (3 import (
4 "context"4 "context"
5 "database/sql"5 "database/sql"
6+ "errors"
6 "strings"7 "strings"
7 )8 )
8 9
10+var ErrDuplicateLike = errors.New("already liked this article")
11+
9 type Annotation struct {12 type Annotation struct {
10 ID int6413 ID int64
11 URI string14 URI string
@@ -116,11 +119,18 @@ func (db *DB) ListAnnotations(ctx context.Context, feedURL, articleURL, authorDI
116 }119 }
117 120
118 func (db *DB) CreateLike(ctx context.Context, l *Like) error {121 func (db *DB) CreateLike(ctx context.Context, l *Like) error {
119- _, err := db.ExecContext(ctx, `122+ result, err := db.ExecContext(ctx, `
120- INSERT INTO likes (uri, author_did, feed_url, article_url, created_at, cid)123+ INSERT OR IGNORE INTO likes (uri, author_did, feed_url, article_url, created_at, cid)
121 VALUES (?, ?, ?, ?, ?, ?)124 VALUES (?, ?, ?, ?, ?, ?)
122 `, l.URI, l.AuthorDID, l.FeedURL, l.ArticleURL, l.CreatedAt, l.CID)125 `, l.URI, l.AuthorDID, l.FeedURL, l.ArticleURL, l.CreatedAt, l.CID)
123- return err126+ if err != nil {
127+ return err
128+ }
129+ n, _ := result.RowsAffected()
130+ if n == 0 {
131+ return ErrDuplicateLike
132+ }
133+ return nil
124 }134 }
125 135
126 func (db *DB) DeleteLike(ctx context.Context, uri string) error {136 func (db *DB) DeleteLike(ctx context.Context, uri string) error {
modified internal/server/articles_handler.go +3 -2
@@ -2,6 +2,7 @@ package server
22
33 import (
44 "database/sql"
5+ "errors"
56 "fmt"
67 "net/http"
78 "strconv"
@@ -244,7 +245,7 @@ func (s *Server) handleLikeArticle(w http.ResponseWriter, r *http.Request) {
244245 ArticleURL: article.URL.String,
245246 CreatedAt: sql.NullTime{Time: time.Now(), Valid: true},
246247 }
247- if err := s.db.CreateLike(r.Context(), like); err != nil {
248+ if err := s.db.CreateLike(r.Context(), like); err != nil && !errors.Is(err, db.ErrDuplicateLike) {
248249 http.Error(w, err.Error(), http.StatusInternalServerError)
249250 return
250251 }
@@ -256,7 +257,7 @@ func (s *Server) handleLikeArticle(w http.ResponseWriter, r *http.Request) {
256257 ArticleURL: article.URL.String,
257258 CreatedAt: sql.NullTime{Time: time.Now(), Valid: true},
258259 }
259- if err := s.db.CreateLike(r.Context(), like); err != nil {
260+ if err := s.db.CreateLike(r.Context(), like); err != nil && !errors.Is(err, db.ErrDuplicateLike) {
260261 http.Error(w, err.Error(), http.StatusInternalServerError)
261262 return
262263 }
@@ -2,6 +2,7 @@ package server
2 2
3 import (3 import (
4 "database/sql"4 "database/sql"
5+ "errors"
5 "fmt"6 "fmt"
6 "net/http"7 "net/http"
7 "strconv"8 "strconv"
@@ -244,7 +245,7 @@ func (s *Server) handleLikeArticle(w http.ResponseWriter, r *http.Request) {
244 ArticleURL: article.URL.String,245 ArticleURL: article.URL.String,
245 CreatedAt: sql.NullTime{Time: time.Now(), Valid: true},246 CreatedAt: sql.NullTime{Time: time.Now(), Valid: true},
246 }247 }
247- if err := s.db.CreateLike(r.Context(), like); err != nil {248+ if err := s.db.CreateLike(r.Context(), like); err != nil && !errors.Is(err, db.ErrDuplicateLike) {
248 http.Error(w, err.Error(), http.StatusInternalServerError)249 http.Error(w, err.Error(), http.StatusInternalServerError)
249 return250 return
250 }251 }
@@ -256,7 +257,7 @@ func (s *Server) handleLikeArticle(w http.ResponseWriter, r *http.Request) {
256 ArticleURL: article.URL.String,257 ArticleURL: article.URL.String,
257 CreatedAt: sql.NullTime{Time: time.Now(), Valid: true},258 CreatedAt: sql.NullTime{Time: time.Now(), Valid: true},
258 }259 }
259- if err := s.db.CreateLike(r.Context(), like); err != nil {260+ if err := s.db.CreateLike(r.Context(), like); err != nil && !errors.Is(err, db.ErrDuplicateLike) {
260 http.Error(w, err.Error(), http.StatusInternalServerError)261 http.Error(w, err.Error(), http.StatusInternalServerError)
261 return262 return
262 }263 }