This repository has no description
4.4 kB
151 lines
1import { Result, ok, err } from '../../../../../shared/core/Result';
2import { UseCase } from '../../../../../shared/core/UseCase';
3import {
4 ICardQueryRepository,
5 CardSortField,
6 SortOrder,
7} from '../../../domain/ICardQueryRepository';
8import { URL } from '../../../domain/value-objects/URL';
9import { IProfileService } from '../../../domain/services/IProfileService';
10import { NoteCardDTO, PaginationDTO, CardSortingDTO } from '../../dtos';
11
12export interface GetNoteCardsForUrlQuery {
13 url: string;
14 callingUserId?: string;
15 page?: number;
16 limit?: number;
17 sortBy?: CardSortField;
18 sortOrder?: SortOrder;
19}
20
21export interface GetNoteCardsForUrlResult {
22 notes: NoteCardDTO[];
23 pagination: PaginationDTO;
24 sorting: CardSortingDTO;
25}
26
27export class ValidationError extends Error {
28 constructor(message: string) {
29 super(message);
30 this.name = 'ValidationError';
31 }
32}
33
34export class GetNoteCardsForUrlUseCase
35 implements UseCase<GetNoteCardsForUrlQuery, Result<GetNoteCardsForUrlResult>>
36{
37 constructor(
38 private cardQueryRepo: ICardQueryRepository,
39 private profileService: IProfileService,
40 ) {}
41
42 async execute(
43 query: GetNoteCardsForUrlQuery,
44 ): Promise<Result<GetNoteCardsForUrlResult>> {
45 // Validate URL
46 const urlResult = URL.create(query.url);
47 if (urlResult.isErr()) {
48 return err(
49 new ValidationError(`Invalid URL: ${urlResult.error.message}`),
50 );
51 }
52
53 // Set defaults
54 const page = query.page || 1;
55 const limit = Math.min(query.limit || 20, 100); // Cap at 100
56 const sortBy = query.sortBy || CardSortField.UPDATED_AT;
57 const sortOrder = query.sortOrder || SortOrder.DESC;
58
59 try {
60 // Execute query to get note cards for the URL (raw data with authorId)
61 const result = await this.cardQueryRepo.getNoteCardsForUrl(query.url, {
62 page,
63 limit,
64 sortBy,
65 sortOrder,
66 });
67
68 // Enrich with author profiles
69 const uniqueAuthorIds = Array.from(
70 new Set(result.items.map((item) => item.authorId)),
71 );
72
73 const profilePromises = uniqueAuthorIds.map((authorId) =>
74 this.profileService.getProfile(authorId, query.callingUserId),
75 );
76
77 const profileResults = await Promise.all(profilePromises);
78
79 // Create a map of profiles
80 const profileMap = new Map<
81 string,
82 {
83 id: string;
84 name: string;
85 handle: string;
86 avatarUrl?: string;
87 description?: string;
88 }
89 >();
90
91 for (let i = 0; i < uniqueAuthorIds.length; i++) {
92 const profileResult = profileResults[i];
93 const authorId = uniqueAuthorIds[i];
94 if (!profileResult || !authorId) {
95 return err(new Error('Missing profile result or author ID'));
96 }
97 if (profileResult.isErr()) {
98 return err(
99 new Error(
100 `Failed to fetch author profile: ${profileResult.error instanceof Error ? profileResult.error.message : 'Unknown error'}`,
101 ),
102 );
103 }
104 const profile = profileResult.value;
105 profileMap.set(authorId, {
106 id: profile.id,
107 name: profile.name,
108 handle: profile.handle,
109 avatarUrl: profile.avatarUrl,
110 description: profile.bio,
111 });
112 }
113
114 // Map items with enriched author data
115 const enrichedNotes: NoteCardDTO[] = result.items.map((item) => {
116 const author = profileMap.get(item.authorId);
117 if (!author) {
118 throw new Error(`Profile not found for author ${item.authorId}`);
119 }
120 return {
121 id: item.id,
122 note: item.note,
123 author,
124 createdAt: item.createdAt.toISOString(),
125 updatedAt: item.updatedAt.toISOString(),
126 };
127 });
128
129 return ok({
130 notes: enrichedNotes,
131 pagination: {
132 currentPage: page,
133 totalPages: Math.ceil(result.totalCount / limit),
134 totalCount: result.totalCount,
135 hasMore: page * limit < result.totalCount,
136 limit,
137 },
138 sorting: {
139 sortBy,
140 sortOrder,
141 },
142 });
143 } catch (error) {
144 return err(
145 new Error(
146 `Failed to retrieve note cards for URL: ${error instanceof Error ? error.message : 'Unknown error'}`,
147 ),
148 );
149 }
150 }
151}