+14
-8
netlify/functions/batch-search-actors.ts
+14
-8
netlify/functions/batch-search-actors.ts
···
1
-
import { AuthenticatedHandler } from "./core/types";
1
+
import {
2
+
AuthenticatedHandler,
3
+
ATProtoActor,
4
+
ATProtoProfile,
5
+
RankedActor,
6
+
EnrichedActor,
7
+
} from "./core/types";
2
8
import { SessionService } from "./services/SessionService";
3
9
import { successResponse, validateArrayInput, ValidationSchemas } from "./utils";
4
10
import { withAuthErrorHandling } from "./core/middleware";
···
28
34
const normalizedUsername = normalize(username);
29
35
30
36
const rankedActors = response.data.actors
31
-
.map((actor: any) => {
37
+
.map((actor: ATProtoActor): RankedActor => {
32
38
const handlePart = actor.handle.split(".")[0];
33
39
const normalizedHandle = normalize(handlePart);
34
40
const normalizedFullHandle = normalize(actor.handle);
···
51
57
did: actor.did,
52
58
};
53
59
})
54
-
.filter((actor: any) => actor.matchScore > 0)
55
-
.sort((a: any, b: any) => b.matchScore - a.matchScore)
60
+
.filter((actor: RankedActor) => actor.matchScore > 0)
61
+
.sort((a: RankedActor, b: RankedActor) => b.matchScore - a.matchScore)
56
62
.slice(0, 5);
57
63
58
64
return {
···
72
78
const results = await Promise.all(searchPromises);
73
79
74
80
const allDids = results
75
-
.flatMap((r) => r.actors.map((a: any) => a.did))
81
+
.flatMap((r) => r.actors.map((a: RankedActor) => a.did))
76
82
.filter((did): did is string => !!did);
77
83
78
84
if (allDids.length > 0) {
···
89
95
actors: batch,
90
96
});
91
97
92
-
profilesResponse.data.profiles.forEach((profile: any) => {
98
+
profilesResponse.data.profiles.forEach((profile: ATProtoProfile) => {
93
99
profileDataMap.set(profile.did, {
94
100
postCount: profile.postsCount || 0,
95
101
followerCount: profile.followersCount || 0,
···
101
107
}
102
108
103
109
results.forEach((result) => {
104
-
result.actors = result.actors.map((actor: any) => {
110
+
result.actors = result.actors.map((actor: RankedActor): EnrichedActor => {
105
111
const enrichedData = profileDataMap.get(actor.did);
106
112
return {
107
113
...actor,
···
124
130
);
125
131
126
132
results.forEach((result) => {
127
-
result.actors = result.actors.map((actor: any) => ({
133
+
result.actors = result.actors.map((actor: EnrichedActor): EnrichedActor => ({
128
134
...actor,
129
135
followStatus: {
130
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
+
}