This repository has no description
1import { Result, ok, err } from '../../../../../shared/core/Result';
2import { BaseUseCase } from '../../../../../shared/core/UseCase';
3import { UseCaseError } from '../../../../../shared/core/UseCaseError';
4import { AppError } from '../../../../../shared/core/AppError';
5import { IEventPublisher } from '../../../../../shared/application/events/IEventPublisher';
6import { ICardRepository } from '../../../domain/ICardRepository';
7import { ICardQueryRepository } from '../../../domain/ICardQueryRepository';
8import { ICollectionQueryRepository } from '../../../domain/ICollectionQueryRepository';
9import { ICollectionRepository } from '../../../domain/ICollectionRepository';
10import { IProfileService } from '../../../domain/services/IProfileService';
11import { CuratorId } from '../../../domain/value-objects/CuratorId';
12import { URL } from '../../../domain/value-objects/URL';
13import { CollectionId } from '../../../domain/value-objects/CollectionId';
14import { CollectionDTO, UrlCard } from '@semble/types';
15import { AuthenticationError } from '../../../../../shared/core/AuthenticationError';
16
17export interface GetUrlStatusForMyLibraryQuery {
18 url: string;
19 curatorId: string;
20}
21
22export interface GetUrlStatusForMyLibraryResult {
23 card?: UrlCard;
24 collections?: CollectionDTO[];
25}
26
27export class ValidationError extends UseCaseError {
28 constructor(message: string) {
29 super(message);
30 }
31}
32
33export class GetUrlStatusForMyLibraryUseCase extends BaseUseCase<
34 GetUrlStatusForMyLibraryQuery,
35 Result<
36 GetUrlStatusForMyLibraryResult,
37 ValidationError | AuthenticationError | AppError.UnexpectedError
38 >
39> {
40 constructor(
41 private cardRepository: ICardRepository,
42 private cardQueryRepository: ICardQueryRepository,
43 private collectionQueryRepository: ICollectionQueryRepository,
44 private collectionRepo: ICollectionRepository,
45 private profileService: IProfileService,
46 eventPublisher: IEventPublisher,
47 ) {
48 super(eventPublisher);
49 }
50
51 async execute(
52 query: GetUrlStatusForMyLibraryQuery,
53 ): Promise<
54 Result<
55 GetUrlStatusForMyLibraryResult,
56 ValidationError | AuthenticationError | AppError.UnexpectedError
57 >
58 > {
59 try {
60 // Validate and create CuratorId
61 const curatorIdResult = CuratorId.create(query.curatorId);
62 if (curatorIdResult.isErr()) {
63 return err(
64 new ValidationError(
65 `Invalid curator ID: ${curatorIdResult.error.message}`,
66 ),
67 );
68 }
69 const curatorId = curatorIdResult.value;
70
71 // Validate URL
72 const urlResult = URL.create(query.url);
73 if (urlResult.isErr()) {
74 return err(
75 new ValidationError(`Invalid URL: ${urlResult.error.message}`),
76 );
77 }
78 const url = urlResult.value;
79
80 // Check if user has a URL card with this URL
81 const existingCardResult =
82 await this.cardRepository.findUsersUrlCardByUrl(url, curatorId);
83 if (existingCardResult.isErr()) {
84 return err(AppError.UnexpectedError.create(existingCardResult.error));
85 }
86
87 const card = existingCardResult.value;
88 const result: GetUrlStatusForMyLibraryResult = {};
89
90 if (card) {
91 // Get enriched card data with note
92 const cardView = await this.cardQueryRepository.getUrlCardBasic(
93 card.cardId.getStringValue(),
94 curatorId.value,
95 );
96
97 if (cardView) {
98 // Get card author profile
99 const authorProfileResult = await this.profileService.getProfile(
100 cardView.authorId,
101 curatorId.value,
102 );
103
104 if (authorProfileResult.isErr()) {
105 // Propagate authentication errors
106 if (authorProfileResult.error instanceof AuthenticationError) {
107 return err(authorProfileResult.error);
108 }
109 return err(
110 AppError.UnexpectedError.create(authorProfileResult.error),
111 );
112 }
113
114 const authorProfile = authorProfileResult.value;
115
116 // Transform to UrlCard format
117 result.card = {
118 id: cardView.id,
119 type: 'URL',
120 url: cardView.url,
121 uri: cardView.uri,
122 cardContent: {
123 url: cardView.cardContent.url,
124 title: cardView.cardContent.title,
125 description: cardView.cardContent.description,
126 author: cardView.cardContent.author,
127 publishedDate: cardView.cardContent.publishedDate?.toISOString(),
128 siteName: cardView.cardContent.siteName,
129 imageUrl: cardView.cardContent.imageUrl,
130 type: cardView.cardContent.type,
131 retrievedAt: cardView.cardContent.retrievedAt?.toISOString(),
132 doi: cardView.cardContent.doi,
133 isbn: cardView.cardContent.isbn,
134 },
135 libraryCount: cardView.libraryCount,
136 urlLibraryCount: cardView.urlLibraryCount,
137 urlInLibrary: cardView.urlInLibrary,
138 createdAt: cardView.createdAt.toISOString(),
139 updatedAt: cardView.updatedAt.toISOString(),
140 author: {
141 id: authorProfile.id,
142 name: authorProfile.name,
143 handle: authorProfile.handle,
144 avatarUrl: authorProfile.avatarUrl,
145 description: authorProfile.bio,
146 },
147 note: cardView.note, // This includes the note if it exists
148 };
149
150 // Get collections containing this card for the user
151 try {
152 const collections =
153 await this.collectionQueryRepository.getCollectionsContainingCardForUser(
154 card.cardId.getStringValue(),
155 curatorId.value,
156 );
157
158 // Enrich collections with full data
159 result.collections = await Promise.all(
160 collections.map(async (collection): Promise<CollectionDTO> => {
161 // Fetch full collection to get dates and cardCount
162 const collectionIdResult = CollectionId.createFromString(
163 collection.id,
164 );
165 if (collectionIdResult.isErr()) {
166 throw new Error(`Invalid collection ID: ${collection.id}`);
167 }
168 const collectionResult = await this.collectionRepo.findById(
169 collectionIdResult.value,
170 );
171 if (collectionResult.isErr() || !collectionResult.value) {
172 throw new Error(`Collection not found: ${collection.id}`);
173 }
174 const fullCollection = collectionResult.value;
175
176 // Fetch author profile
177 const authorProfileResult =
178 await this.profileService.getProfile(
179 fullCollection.authorId.value,
180 );
181 if (authorProfileResult.isErr()) {
182 // Propagate authentication errors
183 if (
184 authorProfileResult.error instanceof AuthenticationError
185 ) {
186 throw authorProfileResult.error;
187 }
188 throw new Error(
189 `Failed to fetch author profile: ${authorProfileResult.error.message}`,
190 );
191 }
192 const authorProfile = authorProfileResult.value;
193
194 return {
195 id: collection.id,
196 uri: collection.uri,
197 name: collection.name,
198 description: collection.description,
199 accessType: fullCollection.accessType,
200 author: {
201 id: authorProfile.id,
202 name: authorProfile.name,
203 handle: authorProfile.handle,
204 avatarUrl: authorProfile.avatarUrl,
205 description: authorProfile.bio,
206 },
207 cardCount: fullCollection.cardCount,
208 createdAt: fullCollection.createdAt.toISOString(),
209 updatedAt: fullCollection.updatedAt.toISOString(),
210 };
211 }),
212 );
213 } catch (error) {
214 // Propagate authentication errors
215 if (error instanceof AuthenticationError) {
216 return err(error);
217 }
218 return err(AppError.UnexpectedError.create(error));
219 }
220 }
221 }
222
223 return ok(result);
224 } catch (error) {
225 return err(AppError.UnexpectedError.create(error));
226 }
227 }
228}