+14
-8
netlify/functions/batch-search-actors.ts
+14
-8
netlify/functions/batch-search-actors.ts
···
1
-
import { AuthenticatedHandler } from "./core/types";
2
import { SessionService } from "./services/SessionService";
3
import { successResponse, validateArrayInput, ValidationSchemas } from "./utils";
4
import { withAuthErrorHandling } from "./core/middleware";
···
28
const normalizedUsername = normalize(username);
29
30
const rankedActors = response.data.actors
31
-
.map((actor: any) => {
32
const handlePart = actor.handle.split(".")[0];
33
const normalizedHandle = normalize(handlePart);
34
const normalizedFullHandle = normalize(actor.handle);
···
51
did: actor.did,
52
};
53
})
54
-
.filter((actor: any) => actor.matchScore > 0)
55
-
.sort((a: any, b: any) => b.matchScore - a.matchScore)
56
.slice(0, 5);
57
58
return {
···
72
const results = await Promise.all(searchPromises);
73
74
const allDids = results
75
-
.flatMap((r) => r.actors.map((a: any) => a.did))
76
.filter((did): did is string => !!did);
77
78
if (allDids.length > 0) {
···
89
actors: batch,
90
});
91
92
-
profilesResponse.data.profiles.forEach((profile: any) => {
93
profileDataMap.set(profile.did, {
94
postCount: profile.postsCount || 0,
95
followerCount: profile.followersCount || 0,
···
101
}
102
103
results.forEach((result) => {
104
-
result.actors = result.actors.map((actor: any) => {
105
const enrichedData = profileDataMap.get(actor.did);
106
return {
107
...actor,
···
124
);
125
126
results.forEach((result) => {
127
-
result.actors = result.actors.map((actor: any) => ({
128
...actor,
129
followStatus: {
130
[followLexicon]: followStatus[actor.did] || false,
···
1
+
import {
2
+
AuthenticatedHandler,
3
+
ATProtoActor,
4
+
ATProtoProfile,
5
+
RankedActor,
6
+
EnrichedActor,
7
+
} from "./core/types";
8
import { SessionService } from "./services/SessionService";
9
import { successResponse, validateArrayInput, ValidationSchemas } from "./utils";
10
import { withAuthErrorHandling } from "./core/middleware";
···
34
const normalizedUsername = normalize(username);
35
36
const rankedActors = response.data.actors
37
+
.map((actor: ATProtoActor): RankedActor => {
38
const handlePart = actor.handle.split(".")[0];
39
const normalizedHandle = normalize(handlePart);
40
const normalizedFullHandle = normalize(actor.handle);
···
57
did: actor.did,
58
};
59
})
60
+
.filter((actor: RankedActor) => actor.matchScore > 0)
61
+
.sort((a: RankedActor, b: RankedActor) => b.matchScore - a.matchScore)
62
.slice(0, 5);
63
64
return {
···
78
const results = await Promise.all(searchPromises);
79
80
const allDids = results
81
+
.flatMap((r) => r.actors.map((a: RankedActor) => a.did))
82
.filter((did): did is string => !!did);
83
84
if (allDids.length > 0) {
···
95
actors: batch,
96
});
97
98
+
profilesResponse.data.profiles.forEach((profile: ATProtoProfile) => {
99
profileDataMap.set(profile.did, {
100
postCount: profile.postsCount || 0,
101
followerCount: profile.followersCount || 0,
···
107
}
108
109
results.forEach((result) => {
110
+
result.actors = result.actors.map((actor: RankedActor): EnrichedActor => {
111
const enrichedData = profileDataMap.get(actor.did);
112
return {
113
...actor,
···
130
);
131
132
results.forEach((result) => {
133
+
result.actors = result.actors.map((actor: EnrichedActor): EnrichedActor => ({
134
...actor,
135
followStatus: {
136
[followLexicon]: followStatus[actor.did] || false,
+64
netlify/functions/core/types/atproto.types.ts
+64
netlify/functions/core/types/atproto.types.ts
···
···
1
+
/**
2
+
* AT Protocol type definitions
3
+
* Based on @atproto/api response schemas
4
+
*/
5
+
6
+
/**
7
+
* Actor profile from app.bsky.actor.searchActors
8
+
*/
9
+
export interface ATProtoActor {
10
+
did: string;
11
+
handle: string;
12
+
displayName?: string;
13
+
avatar?: string;
14
+
description?: string;
15
+
indexedAt?: string;
16
+
labels?: any[]; // Moderation labels
17
+
}
18
+
19
+
/**
20
+
* Detailed profile from app.bsky.actor.getProfiles
21
+
*/
22
+
export interface ATProtoProfile {
23
+
did: string;
24
+
handle: string;
25
+
displayName?: string;
26
+
avatar?: string;
27
+
description?: string;
28
+
followersCount?: number;
29
+
followsCount?: number;
30
+
postsCount?: number;
31
+
indexedAt?: string;
32
+
labels?: any[];
33
+
}
34
+
35
+
/**
36
+
* Actor with match score (search result)
37
+
*/
38
+
export interface RankedActor extends ATProtoActor {
39
+
matchScore: number;
40
+
}
41
+
42
+
/**
43
+
* Enriched actor with profile data and follow status
44
+
*/
45
+
export interface EnrichedActor extends RankedActor {
46
+
postCount: number;
47
+
followerCount: number;
48
+
followStatus?: Record<string, boolean>;
49
+
}
50
+
51
+
/**
52
+
* Search actors response
53
+
*/
54
+
export interface SearchActorsResponse {
55
+
actors: ATProtoActor[];
56
+
cursor?: string;
57
+
}
58
+
59
+
/**
60
+
* Get profiles response
61
+
*/
62
+
export interface GetProfilesResponse {
63
+
profiles: ATProtoProfile[];
64
+
}