Signed-off-by: Seongmin Lee git@boltless.me
+20
-23
appview/db/follow.go
+20
-23
appview/db/follow.go
···
6
6
"strings"
7
7
"time"
8
8
9
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
10
"tangled.org/core/appview/models"
10
11
"tangled.org/core/orm"
11
12
)
···
16
17
return err
17
18
}
18
19
19
-
// Get a follow record
20
-
func GetFollow(e Execer, userDid, subjectDid string) (*models.Follow, error) {
21
-
query := `select did, subject_did, created, rkey from follows where did = ? and subject_did = ?`
22
-
row := e.QueryRow(query, userDid, subjectDid)
23
-
24
-
var follow models.Follow
25
-
var followedAt string
26
-
err := row.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.Rkey)
20
+
// Remove a follow
21
+
func DeleteFollow(e Execer, did, subjectDid syntax.DID) ([]syntax.ATURI, error) {
22
+
var deleted []syntax.ATURI
23
+
rows, err := e.Query(
24
+
`delete from follows
25
+
where did = ? and subject_did = ?
26
+
returning at_uri`,
27
+
did,
28
+
subjectDid,
29
+
)
27
30
if err != nil {
28
-
return nil, err
31
+
return nil, fmt.Errorf("deleting stars: %w", err)
29
32
}
33
+
defer rows.Close()
30
34
31
-
followedAtTime, err := time.Parse(time.RFC3339, followedAt)
32
-
if err != nil {
33
-
log.Println("unable to determine followed at time")
34
-
follow.FollowedAt = time.Now()
35
-
} else {
36
-
follow.FollowedAt = followedAtTime
35
+
for rows.Next() {
36
+
var aturi syntax.ATURI
37
+
if err := rows.Scan(&aturi); err != nil {
38
+
return nil, fmt.Errorf("scanning at_uri: %w", err)
39
+
}
40
+
deleted = append(deleted, aturi)
37
41
}
38
-
39
-
return &follow, nil
40
-
}
41
-
42
-
// Remove a follow
43
-
func DeleteFollow(e Execer, userDid, subjectDid string) error {
44
-
_, err := e.Exec(`delete from follows where did = ? and subject_did = ?`, userDid, subjectDid)
45
-
return err
42
+
return deleted, nil
46
43
}
47
44
48
45
// Remove a follow
+24
-3
appview/db/reaction.go
+24
-3
appview/db/reaction.go
···
1
1
package db
2
2
3
3
import (
4
+
"fmt"
4
5
"log"
5
6
"time"
6
7
···
41
42
}
42
43
43
44
// Remove a reaction
44
-
func DeleteReaction(e Execer, did string, subjectAt syntax.ATURI, kind models.ReactionKind) error {
45
-
_, err := e.Exec(`delete from reactions where did = ? and subject_at = ? and kind = ?`, did, subjectAt, kind)
46
-
return err
45
+
func DeleteReaction(e Execer, did syntax.DID, subjectAt syntax.ATURI, kind models.ReactionKind) ([]syntax.ATURI, error) {
46
+
var deleted []syntax.ATURI
47
+
rows, err := e.Query(
48
+
`delete from reactions
49
+
where did = ? and subject_at = ? and kind = ?
50
+
returning at_uri`,
51
+
did,
52
+
subjectAt,
53
+
kind,
54
+
)
55
+
if err != nil {
56
+
return nil, fmt.Errorf("deleting stars: %w", err)
57
+
}
58
+
defer rows.Close()
59
+
60
+
for rows.Next() {
61
+
var aturi syntax.ATURI
62
+
if err := rows.Scan(&aturi); err != nil {
63
+
return nil, fmt.Errorf("scanning at_uri: %w", err)
64
+
}
65
+
deleted = append(deleted, aturi)
66
+
}
67
+
return deleted, nil
47
68
}
48
69
49
70
// Remove a reaction
+19
-27
appview/db/star.go
+19
-27
appview/db/star.go
···
4
4
"database/sql"
5
5
"errors"
6
6
"fmt"
7
-
"log"
8
7
"slices"
9
8
"strings"
10
9
"time"
···
25
24
return err
26
25
}
27
26
28
-
// Get a star record
29
-
func GetStar(e Execer, did string, subjectAt syntax.ATURI) (*models.Star, error) {
30
-
query := `
31
-
select did, subject_at, created, rkey
32
-
from stars
33
-
where did = ? and subject_at = ?`
34
-
row := e.QueryRow(query, did, subjectAt)
35
-
36
-
var star models.Star
37
-
var created string
38
-
err := row.Scan(&star.Did, &star.RepoAt, &created, &star.Rkey)
27
+
// Remove a star
28
+
func DeleteStar(tx *sql.Tx, did syntax.DID, subjectAt syntax.ATURI) ([]syntax.ATURI, error) {
29
+
var deleted []syntax.ATURI
30
+
rows, err := tx.Query(
31
+
`delete from stars
32
+
where did = ? and subject_at = ?
33
+
returning at_uri`,
34
+
did,
35
+
subjectAt,
36
+
)
39
37
if err != nil {
40
-
return nil, err
38
+
return nil, fmt.Errorf("deleting stars: %w", err)
41
39
}
40
+
defer rows.Close()
42
41
43
-
createdAtTime, err := time.Parse(time.RFC3339, created)
44
-
if err != nil {
45
-
log.Println("unable to determine followed at time")
46
-
star.Created = time.Now()
47
-
} else {
48
-
star.Created = createdAtTime
42
+
for rows.Next() {
43
+
var aturi syntax.ATURI
44
+
if err := rows.Scan(&aturi); err != nil {
45
+
return nil, fmt.Errorf("scanning at_uri: %w", err)
46
+
}
47
+
deleted = append(deleted, aturi)
49
48
}
50
-
51
-
return &star, nil
52
-
}
53
-
54
-
// Remove a star
55
-
func DeleteStar(e Execer, did string, subjectAt syntax.ATURI) error {
56
-
_, err := e.Exec(`delete from stars where did = ? and subject_at = ?`, did, subjectAt)
57
-
return err
49
+
return deleted, nil
58
50
}
59
51
60
52
// Remove a star
+32
-15
appview/state/follow.go
+32
-15
appview/state/follow.go
···
6
6
"time"
7
7
8
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
+
"github.com/bluesky-social/indigo/atproto/syntax"
9
10
lexutil "github.com/bluesky-social/indigo/lex/util"
10
11
"tangled.org/core/api/tangled"
11
12
"tangled.org/core/appview/db"
···
88
89
89
90
return
90
91
case http.MethodDelete:
91
-
// find the record in the db
92
-
follow, err := db.GetFollow(s.db, currentUser.Active.Did, subjectIdent.DID.String())
92
+
tx, err := s.db.BeginTx(r.Context(), nil)
93
93
if err != nil {
94
-
log.Println("failed to get follow relationship")
94
+
s.logger.Error("failed to start transaction", "err", err)
95
+
}
96
+
defer tx.Rollback()
97
+
98
+
follows, err := db.DeleteFollow(tx, syntax.DID(currentUser.Active.Did), subjectIdent.DID)
99
+
if err != nil {
100
+
s.logger.Error("failed to delete follows from db", "err", err)
95
101
return
96
102
}
97
103
98
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
99
-
Collection: tangled.GraphFollowNSID,
100
-
Repo: currentUser.Active.Did,
101
-
Rkey: follow.Rkey,
104
+
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
105
+
for _, followAt := range follows {
106
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
107
+
RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
108
+
Collection: tangled.GraphFollowNSID,
109
+
Rkey: followAt.RecordKey().String(),
110
+
},
111
+
})
112
+
}
113
+
_, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
114
+
Repo: currentUser.Active.Did,
115
+
Writes: writes,
102
116
})
103
-
104
117
if err != nil {
105
-
log.Println("failed to unfollow")
118
+
s.logger.Error("failed to delete follows from PDS", "err", err)
106
119
return
107
120
}
108
121
109
-
err = db.DeleteFollowByRkey(s.db, currentUser.Active.Did, follow.Rkey)
110
-
if err != nil {
111
-
log.Println("failed to delete follow from DB")
112
-
// this is not an issue, the firehose event might have already done this
122
+
if err := tx.Commit(); err != nil {
123
+
s.logger.Error("failed to commit transaction", "err", err)
124
+
// DB op failed but record is created in PDS. Ingester will backfill the missed operation
113
125
}
114
126
127
+
s.notifier.DeleteFollow(r.Context(), &models.Follow{
128
+
UserDid: currentUser.Active.Did,
129
+
SubjectDid: subjectIdent.DID.String(),
130
+
// Rkey
131
+
// FollowedAt
132
+
})
133
+
115
134
followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
116
135
if err != nil {
117
136
log.Println("failed to get follow stats", err)
···
123
142
FollowersCount: followStats.Followers,
124
143
})
125
144
126
-
s.notifier.DeleteFollow(r.Context(), follow)
127
-
128
145
return
129
146
}
130
147
+24
-12
appview/state/reaction.go
+24
-12
appview/state/reaction.go
···
87
87
88
88
return
89
89
case http.MethodDelete:
90
-
reaction, err := db.GetReaction(s.db, currentUser.Active.Did, subjectUri, reactionKind)
90
+
tx, err := s.db.BeginTx(r.Context(), nil)
91
91
if err != nil {
92
-
log.Println("failed to get reaction relationship for", currentUser.Active.Did, subjectUri)
92
+
s.logger.Error("failed to start transaction", "err", err)
93
+
}
94
+
defer tx.Rollback()
95
+
96
+
reactions, err := db.DeleteReaction(tx, syntax.DID(currentUser.Active.Did), subjectUri, reactionKind)
97
+
if err != nil {
98
+
s.logger.Error("failed to delete reactions from db", "err", err)
93
99
return
94
100
}
95
101
96
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
97
-
Collection: tangled.FeedReactionNSID,
98
-
Repo: currentUser.Active.Did,
99
-
Rkey: reaction.Rkey,
102
+
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
103
+
for _, reactionAt := range reactions {
104
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
105
+
RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
106
+
Collection: tangled.FeedReactionNSID,
107
+
Rkey: reactionAt.RecordKey().String(),
108
+
},
109
+
})
110
+
}
111
+
_, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
112
+
Repo: currentUser.Active.Did,
113
+
Writes: writes,
100
114
})
101
-
102
115
if err != nil {
103
-
log.Println("failed to remove reaction")
116
+
s.logger.Error("failed to delete reactions from PDS", "err", err)
104
117
return
105
118
}
106
119
107
-
err = db.DeleteReactionByRkey(s.db, currentUser.Active.Did, reaction.Rkey)
108
-
if err != nil {
109
-
log.Println("failed to delete reaction from DB")
110
-
// this is not an issue, the firehose event might have already done this
120
+
if err := tx.Commit(); err != nil {
121
+
s.logger.Error("failed to commit transaction", "err", err)
122
+
// DB op failed but record is created in PDS. Ingester will backfill the missed operation
111
123
}
112
124
113
125
reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
+31
-15
appview/state/star.go
+31
-15
appview/state/star.go
···
83
83
84
84
return
85
85
case http.MethodDelete:
86
-
// find the record in the db
87
-
star, err := db.GetStar(s.db, currentUser.Active.Did, subjectUri)
86
+
tx, err := s.db.BeginTx(r.Context(), nil)
88
87
if err != nil {
89
-
log.Println("failed to get star relationship")
88
+
s.logger.Error("failed to start transaction", "err", err)
89
+
}
90
+
defer tx.Rollback()
91
+
92
+
stars, err := db.DeleteStar(tx, syntax.DID(currentUser.Active.Did), subjectUri)
93
+
if err != nil {
94
+
s.logger.Error("failed to delete stars from db", "err", err)
90
95
return
91
96
}
92
97
93
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
94
-
Collection: tangled.FeedStarNSID,
95
-
Repo: currentUser.Active.Did,
96
-
Rkey: star.Rkey,
98
+
var writes []*comatproto.RepoApplyWrites_Input_Writes_Elem
99
+
for _, starAt := range stars {
100
+
writes = append(writes, &comatproto.RepoApplyWrites_Input_Writes_Elem{
101
+
RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{
102
+
Collection: tangled.FeedStarNSID,
103
+
Rkey: starAt.RecordKey().String(),
104
+
},
105
+
})
106
+
}
107
+
_, err = comatproto.RepoApplyWrites(r.Context(), client, &comatproto.RepoApplyWrites_Input{
108
+
Repo: currentUser.Active.Did,
109
+
Writes: writes,
97
110
})
98
-
99
111
if err != nil {
100
-
log.Println("failed to unstar")
112
+
s.logger.Error("failed to delete stars from PDS", "err", err)
101
113
return
102
114
}
103
115
104
-
err = db.DeleteStarByRkey(s.db, currentUser.Active.Did, star.Rkey)
105
-
if err != nil {
106
-
log.Println("failed to delete star from DB")
107
-
// this is not an issue, the firehose event might have already done this
116
+
if err := tx.Commit(); err != nil {
117
+
s.logger.Error("failed to commit transaction", "err", err)
118
+
// DB op failed but record is created in PDS. Ingester will backfill the missed operation
108
119
}
109
120
121
+
s.notifier.DeleteStar(r.Context(), &models.Star{
122
+
Did: currentUser.Active.Did,
123
+
RepoAt: subjectUri,
124
+
// Rkey
125
+
// Created
126
+
})
127
+
110
128
starCount, err := db.GetStarCount(s.db, subjectUri)
111
129
if err != nil {
112
130
log.Println("failed to get star count for ", subjectUri)
113
131
return
114
132
}
115
133
116
-
s.notifier.DeleteStar(r.Context(), star)
117
-
118
134
s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{
119
135
IsStarred: false,
120
136
SubjectAt: subjectUri,
Submissions
1 commit
expand
collapse
appview/{db,state}: delete all duplicate records on delete
Signed-off-by: Seongmin Lee <git@boltless.me>
3/3 success
expand
collapse
no conflicts, ready to merge