A social knowledge tool for researchers built on ATProto
1import { Result, ok, err } from '../../../../../shared/core/Result';
2import { UseCase } from '../../../../../shared/core/UseCase';
3import { UseCaseError } from '../../../../../shared/core/UseCaseError';
4import { AppError } from '../../../../../shared/core/AppError';
5import { ICardRepository } from '../../../domain/ICardRepository';
6import { CardId } from '../../../domain/value-objects/CardId';
7import { CuratorId } from '../../../domain/value-objects/CuratorId';
8import { CardTypeEnum } from '../../../domain/value-objects/CardType';
9import { CardContent } from '../../../domain/value-objects/CardContent';
10import { ICardPublisher } from '../../ports/ICardPublisher';
11import { AuthenticationError } from '../../../../../shared/core/AuthenticationError';
12
13export interface UpdateNoteCardDTO {
14 cardId: string;
15 note: string;
16 curatorId: string;
17}
18
19export interface UpdateNoteCardResponseDTO {
20 cardId: string;
21}
22
23export class ValidationError extends UseCaseError {
24 constructor(message: string) {
25 super(message);
26 }
27}
28
29export class UpdateNoteCardUseCase
30 implements
31 UseCase<
32 UpdateNoteCardDTO,
33 Result<
34 UpdateNoteCardResponseDTO,
35 ValidationError | AuthenticationError | AppError.UnexpectedError
36 >
37 >
38{
39 constructor(
40 private cardRepository: ICardRepository,
41 private cardPublisher: ICardPublisher,
42 ) {}
43
44 async execute(
45 request: UpdateNoteCardDTO,
46 ): Promise<
47 Result<
48 UpdateNoteCardResponseDTO,
49 ValidationError | AuthenticationError | AppError.UnexpectedError
50 >
51 > {
52 try {
53 // Validate and create CuratorId
54 const curatorIdResult = CuratorId.create(request.curatorId);
55 if (curatorIdResult.isErr()) {
56 return err(
57 new ValidationError(
58 `Invalid curator ID: ${curatorIdResult.error.message}`,
59 ),
60 );
61 }
62 const curatorId = curatorIdResult.value;
63
64 // Validate and create CardId
65 const cardIdResult = CardId.createFromString(request.cardId);
66 if (cardIdResult.isErr()) {
67 return err(
68 new ValidationError(`Invalid card ID: ${cardIdResult.error.message}`),
69 );
70 }
71 const cardId = cardIdResult.value;
72
73 // Find the card
74 const cardResult = await this.cardRepository.findById(cardId);
75 if (cardResult.isErr()) {
76 return err(AppError.UnexpectedError.create(cardResult.error));
77 }
78
79 const card = cardResult.value;
80 if (!card) {
81 return err(new ValidationError(`Card not found: ${request.cardId}`));
82 }
83
84 // Verify it's a note card
85 if (card.type.value !== CardTypeEnum.NOTE) {
86 return err(
87 new ValidationError('Card is not a note card and cannot be updated'),
88 );
89 }
90
91 // Get the note content and verify authorship
92 const noteContent = card.content.noteContent;
93 if (!noteContent) {
94 return err(
95 new ValidationError('Card does not have valid note content'),
96 );
97 }
98
99 if (!card.curatorId.equals(curatorId)) {
100 return err(
101 new ValidationError('Only the author can update this note card'),
102 );
103 }
104
105 // Update the note content
106 const updatedNoteContentResult = noteContent.updateText(request.note);
107 if (updatedNoteContentResult.isErr()) {
108 return err(new ValidationError(updatedNoteContentResult.error.message));
109 }
110
111 // Create new card content with updated note
112 const updatedCardContentResult = CardContent.createNoteContent(
113 request.note,
114 );
115 if (updatedCardContentResult.isErr()) {
116 return err(new ValidationError(updatedCardContentResult.error.message));
117 }
118
119 // Update the card content
120 const updateResult = card.updateContent(updatedCardContentResult.value);
121 if (updateResult.isErr()) {
122 return err(new ValidationError(updateResult.error.message));
123 }
124
125 // Publish the updated card to library first
126 const publishResult = await this.cardPublisher.publishCardToLibrary(
127 card,
128 curatorId,
129 card.publishedRecordId,
130 );
131 if (publishResult.isErr()) {
132 // Propagate authentication errors
133 if (publishResult.error instanceof AuthenticationError) {
134 return err(publishResult.error);
135 }
136 return err(AppError.UnexpectedError.create(publishResult.error));
137 }
138
139 // Mark the card in library as published with the new record ID
140 const markPublishedResult = card.markCardInLibraryAsPublished(
141 curatorId,
142 publishResult.value,
143 );
144 if (markPublishedResult.isErr()) {
145 return err(new ValidationError(markPublishedResult.error.message));
146 }
147
148 // Save the updated card to repository
149 const saveResult = await this.cardRepository.save(card);
150 if (saveResult.isErr()) {
151 return err(AppError.UnexpectedError.create(saveResult.error));
152 }
153
154 return ok({
155 cardId: card.cardId.getStringValue(),
156 });
157 } catch (error) {
158 return err(AppError.UnexpectedError.create(error));
159 }
160 }
161}