Skip to content

Commit 2dc4deb

Browse files
committed
add support for self-service opt out
in particular: follow: - (no record) -> PENDING - OPTED_OUT -> APPROVED unfollow: - PENDING -> NONE - APPROVED -> OPTED_OUT this also tries to normalize NONE status with record not present
1 parent afeda80 commit 2dc4deb

27 files changed

+219
-46
lines changed

ingester/candidate_actor_cache.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package ingester
33
import (
44
"context"
55
"fmt"
6-
"github.com/jonboulle/clockwork"
76
"sync"
87
"time"
98

9+
"github.com/jonboulle/clockwork"
10+
1011
v1 "github.com/strideynet/bsky-furry-feed/proto/bff/v1"
1112
"github.com/strideynet/bsky-furry-feed/store"
1213
"go.uber.org/zap"
@@ -78,6 +79,9 @@ func (crc *ActorCache) Sync(ctx context.Context) error {
7879

7980
mapped := map[string]*v1.Actor{}
8081
for _, cr := range data {
82+
if cr.Status == v1.ActorStatus_ACTOR_STATUS_NONE {
83+
continue
84+
}
8185
mapped[cr.Did] = cr
8286
}
8387

@@ -128,3 +132,44 @@ func (crc *ActorCache) CreatePendingCandidateActor(ctx context.Context, did stri
128132
crc.cached[ca.Did] = ca
129133
return nil
130134
}
135+
136+
func (crc *ActorCache) OptIn(ctx context.Context, did string) (err error) {
137+
ctx, span := tracer.Start(ctx, "actor_cache.opt_in")
138+
defer func() {
139+
endSpan(span, err)
140+
}()
141+
142+
status, err := crc.store.OptInActor(ctx, did)
143+
if err != nil {
144+
return fmt.Errorf("opting in actor: %w", err)
145+
}
146+
147+
crc.mu.Lock()
148+
defer crc.mu.Unlock()
149+
ca := crc.cached[did]
150+
if ca != nil {
151+
ca.Status = status
152+
}
153+
154+
return nil
155+
}
156+
157+
func (crc *ActorCache) OptOutOrForget(ctx context.Context, did string) (err error) {
158+
ctx, span := tracer.Start(ctx, "actor_cache.opt_out")
159+
defer func() {
160+
endSpan(span, err)
161+
}()
162+
163+
status, err := crc.store.OptOutOrForgetActor(ctx, did)
164+
if err != nil {
165+
return fmt.Errorf("opting out actor: %w", err)
166+
}
167+
168+
crc.mu.Lock()
169+
defer crc.mu.Unlock()
170+
if status == v1.ActorStatus_ACTOR_STATUS_NONE {
171+
delete(crc.cached, did)
172+
}
173+
174+
return nil
175+
}

ingester/handle_graph_follow.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,38 @@ func (fi *FirehoseIngester) handleGraphFollowCreate(
3939
return fmt.Errorf("creating follow: %w", err)
4040
}
4141

42+
if fi.IsFurryFeedDID(data.Subject) {
43+
if err := fi.actorCache.OptIn(ctx, repoDID); err != nil {
44+
return fmt.Errorf("opting in: %w", err)
45+
}
46+
}
47+
4248
return nil
4349
}
4450

4551
func (fi *FirehoseIngester) handleGraphFollowDelete(
4652
ctx context.Context,
53+
repoDID string,
4754
recordUri string,
4855
) (err error) {
4956
ctx, span := tracer.Start(ctx, "firehose_ingester.handle_feed_follow_delete")
5057
defer func() {
5158
endSpan(span, err)
5259
}()
5360

54-
if err := fi.store.DeleteFollow(
61+
subjectDID, err := fi.store.DeleteFollow(
5562
ctx, store.DeleteFollowOpts{URI: recordUri},
56-
); err != nil {
63+
)
64+
65+
if err != nil {
5766
return fmt.Errorf("deleting follow: %w", err)
5867
}
5968

69+
if fi.IsFurryFeedDID(subjectDID) {
70+
if err := fi.actorCache.OptOutOrForget(ctx, repoDID); err != nil {
71+
return fmt.Errorf("opting out: %w", err)
72+
}
73+
}
74+
6075
return nil
6176
}

ingester/ingester.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"context"
66
"errors"
77
"fmt"
8-
"github.com/bluesky-social/indigo/events/schedulers/sequential"
98
"strconv"
109

10+
"github.com/bluesky-social/indigo/events/schedulers/sequential"
11+
1112
"github.com/bluesky-social/indigo/util"
1213
"github.com/ipfs/go-cid"
1314

@@ -49,6 +50,8 @@ var workItemsProcessed = promauto.NewSummaryVec(prometheus.SummaryOpts{
4950
type actorCacher interface {
5051
GetByDID(did string) *v1.Actor
5152
CreatePendingCandidateActor(ctx context.Context, did string) (err error)
53+
OptIn(ctx context.Context, did string) (err error)
54+
OptOutOrForget(ctx context.Context, did string) (err error)
5255
}
5356

5457
var workerCursors = promauto.NewGaugeVec(prometheus.GaugeOpts{
@@ -342,15 +345,18 @@ func (fi *FirehoseIngester) handleCommit(ctx context.Context, evt *atproto.SyncS
342345
return nil
343346
}
344347

348+
func (fi *FirehoseIngester) IsFurryFeedDID(did string) bool {
349+
// TODO: Make this not hard coded
350+
// https://bsky.app/profile/furryli.st
351+
return did == "did:plc:jdkvwye2lf4mingzk7qdebzc"
352+
}
353+
345354
func (fi *FirehoseIngester) isFurryFeedFollow(record typegen.CBORMarshaler) bool {
346355
follow, ok := record.(*bsky.GraphFollow)
347356
if !ok {
348357
return false
349358
}
350-
351-
// TODO: Make this not hard coded
352-
// https://bsky.app/profile/furryli.st
353-
return follow.Subject == "did:plc:jdkvwye2lf4mingzk7qdebzc"
359+
return fi.IsFurryFeedDID(follow.Subject)
354360
}
355361

356362
func endSpan(span trace.Span, err error) {
@@ -461,7 +467,7 @@ func (fi *FirehoseIngester) handleRecordDelete(
461467
case "app.bsky.feed.like":
462468
err = fi.handleFeedLikeDelete(ctx, recordUri)
463469
case "app.bsky.graph.follow":
464-
err = fi.handleGraphFollowDelete(ctx, recordUri)
470+
err = fi.handleGraphFollowDelete(ctx, repoDID, recordUri)
465471
default:
466472
span.AddEvent("ignoring record due to unrecognized type")
467473
}

proto/bff/v1/types.pb.go

Lines changed: 11 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/bff/v1/types.proto

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ enum ActorStatus {
1212
ACTOR_STATUS_APPROVED = 2;
1313
ACTOR_STATUS_BANNED = 3;
1414
ACTOR_STATUS_NONE = 4;
15+
ACTOR_STATUS_OPTED_OUT = 5;
1516
}
1617

1718
message Actor {
@@ -40,4 +41,4 @@ message Actor {
4041
// is ignored in the queue to be processed later, e.g. when the actor doesn’t
4142
// have an avatar
4243
google.protobuf.Timestamp held_until = 8;
43-
}
44+
}

store/gen/candidate_actors.sql.go

Lines changed: 41 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

store/gen/candidate_follows.sql.go

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

store/gen/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- Sorry, can't actually undo this!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TYPE actor_status ADD VALUE IF NOT EXISTS 'opted_out';

0 commit comments

Comments
 (0)