Signed-off-by: Seongmin Lee git@boltless.me
+20
-23
appview/db/follow.go
+20
-23
appview/db/follow.go
···
6
"strings"
7
"time"
8
9
"tangled.org/core/appview/models"
10
"tangled.org/core/orm"
11
)
···
16
return err
17
}
18
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)
27
if err != nil {
28
-
return nil, err
29
}
30
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
37
}
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
46
}
47
48
// Remove a follow
···
6
"strings"
7
"time"
8
9
+
"github.com/bluesky-social/indigo/atproto/syntax"
10
"tangled.org/core/appview/models"
11
"tangled.org/core/orm"
12
)
···
17
return err
18
}
19
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
+
)
30
if err != nil {
31
+
return nil, fmt.Errorf("deleting stars: %w", err)
32
}
33
+
defer rows.Close()
34
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)
41
}
42
+
return deleted, nil
43
}
44
45
// Remove a follow
+24
-3
appview/db/reaction.go
+24
-3
appview/db/reaction.go
···
1
package db
2
3
import (
4
"log"
5
"time"
6
···
41
}
42
43
// 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
47
}
48
49
// Remove a reaction
···
1
package db
2
3
import (
4
+
"fmt"
5
"log"
6
"time"
7
···
42
}
43
44
// Remove a reaction
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
68
}
69
70
// Remove a reaction
+19
-27
appview/db/star.go
+19
-27
appview/db/star.go
···
4
"database/sql"
5
"errors"
6
"fmt"
7
-
"log"
8
"slices"
9
"strings"
10
"time"
···
25
return err
26
}
27
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)
39
if err != nil {
40
-
return nil, err
41
}
42
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
49
}
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
58
}
59
60
// Remove a star
···
4
"database/sql"
5
"errors"
6
"fmt"
7
"slices"
8
"strings"
9
"time"
···
24
return err
25
}
26
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
+
)
37
if err != nil {
38
+
return nil, fmt.Errorf("deleting stars: %w", err)
39
}
40
+
defer rows.Close()
41
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)
48
}
49
+
return deleted, nil
50
}
51
52
// Remove a star
+32
-15
appview/state/follow.go
+32
-15
appview/state/follow.go
···
6
"time"
7
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
lexutil "github.com/bluesky-social/indigo/lex/util"
10
"tangled.org/core/api/tangled"
11
"tangled.org/core/appview/db"
···
88
89
return
90
case http.MethodDelete:
91
-
// find the record in the db
92
-
follow, err := db.GetFollow(s.db, currentUser.Active.Did, subjectIdent.DID.String())
93
if err != nil {
94
-
log.Println("failed to get follow relationship")
95
return
96
}
97
98
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
99
-
Collection: tangled.GraphFollowNSID,
100
-
Repo: currentUser.Active.Did,
101
-
Rkey: follow.Rkey,
102
})
103
-
104
if err != nil {
105
-
log.Println("failed to unfollow")
106
return
107
}
108
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
113
}
114
115
followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
116
if err != nil {
117
log.Println("failed to get follow stats", err)
···
123
FollowersCount: followStats.Followers,
124
})
125
126
-
s.notifier.DeleteFollow(r.Context(), follow)
127
-
128
return
129
}
130
···
6
"time"
7
8
comatproto "github.com/bluesky-social/indigo/api/atproto"
9
+
"github.com/bluesky-social/indigo/atproto/syntax"
10
lexutil "github.com/bluesky-social/indigo/lex/util"
11
"tangled.org/core/api/tangled"
12
"tangled.org/core/appview/db"
···
89
90
return
91
case http.MethodDelete:
92
+
tx, err := s.db.BeginTx(r.Context(), nil)
93
if err != nil {
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)
101
return
102
}
103
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,
116
})
117
if err != nil {
118
+
s.logger.Error("failed to delete follows from PDS", "err", err)
119
return
120
}
121
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
125
}
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
+
134
followStats, err := db.GetFollowerFollowingCount(s.db, subjectIdent.DID.String())
135
if err != nil {
136
log.Println("failed to get follow stats", err)
···
142
FollowersCount: followStats.Followers,
143
})
144
145
return
146
}
147
+24
-12
appview/state/reaction.go
+24
-12
appview/state/reaction.go
···
87
88
return
89
case http.MethodDelete:
90
-
reaction, err := db.GetReaction(s.db, currentUser.Active.Did, subjectUri, reactionKind)
91
if err != nil {
92
-
log.Println("failed to get reaction relationship for", currentUser.Active.Did, subjectUri)
93
return
94
}
95
96
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
97
-
Collection: tangled.FeedReactionNSID,
98
-
Repo: currentUser.Active.Did,
99
-
Rkey: reaction.Rkey,
100
})
101
-
102
if err != nil {
103
-
log.Println("failed to remove reaction")
104
return
105
}
106
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
111
}
112
113
reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
···
87
88
return
89
case http.MethodDelete:
90
+
tx, err := s.db.BeginTx(r.Context(), nil)
91
if err != nil {
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)
99
return
100
}
101
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,
114
})
115
if err != nil {
116
+
s.logger.Error("failed to delete reactions from PDS", "err", err)
117
return
118
}
119
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
123
}
124
125
reactionMap, err := db.GetReactionMap(s.db, 20, subjectUri)
+31
-15
appview/state/star.go
+31
-15
appview/state/star.go
···
83
84
return
85
case http.MethodDelete:
86
-
// find the record in the db
87
-
star, err := db.GetStar(s.db, currentUser.Active.Did, subjectUri)
88
if err != nil {
89
-
log.Println("failed to get star relationship")
90
return
91
}
92
93
-
_, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
94
-
Collection: tangled.FeedStarNSID,
95
-
Repo: currentUser.Active.Did,
96
-
Rkey: star.Rkey,
97
})
98
-
99
if err != nil {
100
-
log.Println("failed to unstar")
101
return
102
}
103
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
108
}
109
110
starCount, err := db.GetStarCount(s.db, subjectUri)
111
if err != nil {
112
log.Println("failed to get star count for ", subjectUri)
113
return
114
}
115
116
-
s.notifier.DeleteStar(r.Context(), star)
117
-
118
s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{
119
IsStarred: false,
120
SubjectAt: subjectUri,
···
83
84
return
85
case http.MethodDelete:
86
+
tx, err := s.db.BeginTx(r.Context(), nil)
87
if err != nil {
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)
95
return
96
}
97
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,
110
})
111
if err != nil {
112
+
s.logger.Error("failed to delete stars from PDS", "err", err)
113
return
114
}
115
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
119
}
120
121
+
s.notifier.DeleteStar(r.Context(), &models.Star{
122
+
Did: currentUser.Active.Did,
123
+
RepoAt: subjectUri,
124
+
// Rkey
125
+
// Created
126
+
})
127
+
128
starCount, err := db.GetStarCount(s.db, subjectUri)
129
if err != nil {
130
log.Println("failed to get star count for ", subjectUri)
131
return
132
}
133
134
s.pages.StarBtnFragment(w, pages.StarBtnFragmentParams{
135
IsStarred: false,
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