ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
1import { AuthenticatedHandler } from "./core/types";
2import { MatchRepository } from "./repositories";
3import { successResponse } from "./utils";
4import { withAuthErrorHandling } from "./core/middleware";
5import { ValidationError, NotFoundError } from "./core/errors";
6
7const DEFAULT_PAGE_SIZE = 50;
8const MAX_PAGE_SIZE = 100;
9
10const getUploadDetailsHandler: AuthenticatedHandler = async (context) => {
11 const uploadId = context.event.queryStringParameters?.uploadId;
12 const page = parseInt(context.event.queryStringParameters?.page || "1");
13 const pageSize = Math.min(
14 parseInt(
15 context.event.queryStringParameters?.pageSize ||
16 String(DEFAULT_PAGE_SIZE),
17 ),
18 MAX_PAGE_SIZE,
19 );
20
21 if (!uploadId) {
22 throw new ValidationError("uploadId is required");
23 }
24
25 if (page < 1 || pageSize < 1) {
26 throw new ValidationError("Invalid page or pageSize parameters");
27 }
28
29 const matchRepo = new MatchRepository();
30
31 // Fetch paginated results
32 const { results, totalUsers } = await matchRepo.getUploadDetails(
33 uploadId,
34 context.did,
35 page,
36 pageSize,
37 );
38
39 if (totalUsers === 0) {
40 throw new NotFoundError("Upload not found");
41 }
42
43 const totalPages = Math.ceil(totalUsers / pageSize);
44
45 // Group results by source username
46 const groupedResults = new Map<string, any>();
47
48 results.forEach((row: any) => {
49 const username = row.source_username;
50
51 // Get or create the entry for this username
52 let userResult = groupedResults.get(username);
53
54 if (!userResult) {
55 userResult = {
56 sourceUser: {
57 username: username,
58 date: row.source_date || "",
59 },
60 atprotoMatches: [],
61 };
62 groupedResults.set(username, userResult);
63 }
64
65 // Add the match (if it exists) to the array
66 if (row.atproto_did) {
67 userResult.atprotoMatches.push({
68 did: row.atproto_did,
69 handle: row.atproto_handle,
70 displayName: row.atproto_display_name,
71 avatar: row.atproto_avatar,
72 description: row.atproto_description,
73 matchScore: row.match_score,
74 postCount: row.post_count,
75 followerCount: row.follower_count,
76 foundAt: row.found_at,
77 dismissed: row.dismissed || false,
78 followStatus: row.follow_status || {},
79 });
80 }
81 });
82
83 const searchResults = Array.from(groupedResults.values());
84
85 return successResponse(
86 {
87 results: searchResults,
88 pagination: {
89 page,
90 pageSize,
91 totalPages,
92 totalUsers,
93 hasNextPage: page < totalPages,
94 hasPrevPage: page > 1,
95 },
96 },
97 200,
98 {
99 "Cache-Control": "private, max-age=600",
100 },
101 );
102};
103
104export const handler = withAuthErrorHandling(getUploadDetailsHandler);