+110
-11
hydration/actor.go
+110
-11
hydration/actor.go
···
10
10
11
11
"github.com/bluesky-social/indigo/api/bsky"
12
12
"github.com/bluesky-social/indigo/atproto/syntax"
13
+
"github.com/whyrusleeping/market/models"
13
14
)
14
15
15
16
// ActorInfo contains hydrated actor information
···
60
61
FollowCount int64
61
62
FollowerCount int64
62
63
PostCount int64
64
+
ViewerState *bsky.ActorDefs_ViewerState
63
65
}
64
66
65
-
func (h *Hydrator) HydrateActorDetailed(ctx context.Context, did string) (*ActorInfoDetailed, error) {
67
+
func (h *Hydrator) HydrateActorDetailed(ctx context.Context, did string, viewer string) (*ActorInfoDetailed, error) {
66
68
act, err := h.HydrateActor(ctx, did)
67
69
if err != nil {
68
70
return nil, err
···
73
75
}
74
76
75
77
var wg sync.WaitGroup
76
-
wg.Add(3)
77
-
go func() {
78
-
defer wg.Done()
78
+
wg.Go(func() {
79
79
c, err := h.getFollowCountForUser(ctx, did)
80
80
if err != nil {
81
81
slog.Error("failed to get follow count", "did", did, "error", err)
82
82
}
83
83
actd.FollowCount = c
84
-
}()
85
-
go func() {
86
-
defer wg.Done()
84
+
})
85
+
wg.Go(func() {
87
86
c, err := h.getFollowerCountForUser(ctx, did)
88
87
if err != nil {
89
88
slog.Error("failed to get follower count", "did", did, "error", err)
90
89
}
91
90
actd.FollowerCount = c
92
-
}()
93
-
go func() {
94
-
defer wg.Done()
91
+
})
92
+
wg.Go(func() {
95
93
c, err := h.getPostCountForUser(ctx, did)
96
94
if err != nil {
97
95
slog.Error("failed to get post count", "did", did, "error", err)
98
96
}
99
97
actd.PostCount = c
100
-
}()
98
+
})
99
+
100
+
if viewer != "" {
101
+
wg.Go(func() {
102
+
vs, err := h.getProfileViewerState(ctx, did, viewer)
103
+
if err != nil {
104
+
slog.Error("failed to get viewer state", "did", did, "viewer", viewer, "error", err)
105
+
}
106
+
actd.ViewerState = vs
107
+
})
108
+
}
109
+
101
110
wg.Wait()
102
111
103
112
return &actd, nil
113
+
}
114
+
115
+
func (h *Hydrator) getProfileViewerState(ctx context.Context, did, viewer string) (*bsky.ActorDefs_ViewerState, error) {
116
+
vs := &bsky.ActorDefs_ViewerState{}
117
+
118
+
var wg sync.WaitGroup
119
+
120
+
// Check if viewer is blocked by the target account
121
+
wg.Go(func() {
122
+
blockedBy, err := h.getBlockPair(ctx, did, viewer)
123
+
if err != nil {
124
+
slog.Error("failed to get blockedBy relationship", "did", did, "viewer", viewer, "error", err)
125
+
return
126
+
}
127
+
128
+
if blockedBy != nil {
129
+
v := true
130
+
vs.BlockedBy = &v
131
+
}
132
+
})
133
+
134
+
// Check if viewer is blocking the target account
135
+
wg.Go(func() {
136
+
blocking, err := h.getBlockPair(ctx, viewer, did)
137
+
if err != nil {
138
+
slog.Error("failed to get blocking relationship", "did", did, "viewer", viewer, "error", err)
139
+
return
140
+
}
141
+
142
+
if blocking != nil {
143
+
uri := fmt.Sprintf("at://%s/app.bsky.graph.block/%s", viewer, blocking.Rkey)
144
+
vs.Blocking = &uri
145
+
}
146
+
})
147
+
148
+
// Check if viewer is following the target account
149
+
wg.Go(func() {
150
+
following, err := h.getFollowPair(ctx, viewer, did)
151
+
if err != nil {
152
+
slog.Error("failed to get following relationship", "did", did, "viewer", viewer, "error", err)
153
+
return
154
+
}
155
+
156
+
if following != nil {
157
+
uri := fmt.Sprintf("at://%s/app.bsky.graph.follow/%s", viewer, following.Rkey)
158
+
vs.Following = &uri
159
+
}
160
+
})
161
+
162
+
// Check if target account is following the viewer
163
+
wg.Go(func() {
164
+
followedBy, err := h.getFollowPair(ctx, did, viewer)
165
+
if err != nil {
166
+
slog.Error("failed to get followedBy relationship", "did", did, "viewer", viewer, "error", err)
167
+
return
168
+
}
169
+
170
+
if followedBy != nil {
171
+
uri := fmt.Sprintf("at://%s/app.bsky.graph.follow/%s", did, followedBy.Rkey)
172
+
vs.FollowedBy = &uri
173
+
}
174
+
})
175
+
176
+
wg.Wait()
177
+
178
+
return vs, nil
179
+
}
180
+
181
+
func (h *Hydrator) getBlockPair(ctx context.Context, a, b string) (*models.Block, error) {
182
+
var blk models.Block
183
+
if err := h.db.Raw("SELECT * FROM blocks WHERE author = (SELECT id FROM repos WHERE did = ?) AND subject = (SELECT id FROM repos WHERE did = ?)", a, b).Scan(&blk).Error; err != nil {
184
+
return nil, err
185
+
}
186
+
if blk.ID == 0 {
187
+
return nil, nil
188
+
}
189
+
190
+
return &blk, nil
191
+
}
192
+
193
+
func (h *Hydrator) getFollowPair(ctx context.Context, a, b string) (*models.Follow, error) {
194
+
var fol models.Follow
195
+
if err := h.db.Raw("SELECT * FROM follows WHERE author = (SELECT id FROM repos WHERE did = ?) AND subject = (SELECT id FROM repos WHERE did = ?)", a, b).Scan(&fol).Error; err != nil {
196
+
return nil, err
197
+
}
198
+
if fol.ID == 0 {
199
+
return nil, nil
200
+
}
201
+
202
+
return &fol, nil
104
203
}
105
204
106
205
func (h *Hydrator) getFollowCountForUser(ctx context.Context, did string) (int64, error) {
+9
pgbackend.go
+9
pgbackend.go
···
21
21
. "github.com/whyrusleeping/konbini/models"
22
22
)
23
23
24
+
func (b *PostgresBackend) DidToID(ctx context.Context, did string) (uint, error) {
25
+
r, err := b.getOrCreateRepo(ctx, did)
26
+
if err != nil {
27
+
return 0, err
28
+
}
29
+
30
+
return r.ID, nil
31
+
}
32
+
24
33
func (b *PostgresBackend) getOrCreateRepo(ctx context.Context, did string) (*Repo, error) {
25
34
r, ok := b.repoCache.Get(did)
26
35
if !ok {
+5
views/actor.go
+5
views/actor.go
+3
-1
xrpc/actor/getProfile.go
+3
-1
xrpc/actor/getProfile.go
···
20
20
21
21
ctx := c.Request().Context()
22
22
23
+
viewer, _ := c.Get("viewer").(string)
24
+
23
25
// Resolve actor to DID
24
26
did, err := hydrator.ResolveDID(ctx, actorParam)
25
27
if err != nil {
···
30
32
}
31
33
32
34
// Hydrate actor info
33
-
actorInfo, err := hydrator.HydrateActorDetailed(ctx, did)
35
+
actorInfo, err := hydrator.HydrateActorDetailed(ctx, did, viewer)
34
36
if err != nil {
35
37
return c.JSON(http.StatusNotFound, map[string]interface{}{
36
38
"error": "ActorNotFound",
+2
-1
xrpc/actor/getProfiles.go
+2
-1
xrpc/actor/getProfiles.go
···
27
27
}
28
28
29
29
ctx := c.Request().Context()
30
+
viewer, _ := c.Get("viewer").(string)
30
31
31
32
// Resolve all actors to DIDs and hydrate profiles
32
33
profiles := make([]*bsky.ActorDefs_ProfileViewDetailed, 0, len(actors))
···
39
40
}
40
41
41
42
// Hydrate actor info
42
-
actorInfo, err := hydrator.HydrateActorDetailed(ctx, did)
43
+
actorInfo, err := hydrator.HydrateActorDetailed(ctx, did, viewer)
43
44
if err != nil {
44
45
// Skip actors that can't be hydrated
45
46
continue
+3
-3
xrpc/server.go
+3
-3
xrpc/server.go
···
94
94
// app.bsky.actor.*
95
95
xrpcGroup.GET("/app.bsky.actor.getProfile", func(c echo.Context) error {
96
96
return actor.HandleGetProfile(c, s.hydrator)
97
-
})
97
+
}, s.optionalAuth)
98
98
xrpcGroup.GET("/app.bsky.actor.getProfiles", func(c echo.Context) error {
99
99
return actor.HandleGetProfiles(c, s.db, s.hydrator)
100
-
})
100
+
}, s.optionalAuth)
101
101
xrpcGroup.GET("/app.bsky.actor.getPreferences", func(c echo.Context) error {
102
102
return actor.HandleGetPreferences(c, s.db, s.hydrator)
103
103
}, s.requireAuth)
···
131
131
}, s.requireAuth)
132
132
xrpcGroup.GET("/app.bsky.feed.getFeed", func(c echo.Context) error {
133
133
return feed.HandleGetFeed(c, s.db, s.hydrator, s.dir)
134
-
})
134
+
}, s.optionalAuth)
135
135
xrpcGroup.GET("/app.bsky.feed.getFeedGenerator", func(c echo.Context) error {
136
136
return feed.HandleGetFeedGenerator(c, s.db, s.hydrator, s.dir)
137
137
})