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 { ICollectionRepository } from '../../../domain/ICollectionRepository';
6import { Collection, CollectionAccessType } from '../../../domain/Collection';
7import { CuratorId } from '../../../domain/value-objects/CuratorId';
8import { ICollectionPublisher } from '../../ports/ICollectionPublisher';
9import { AuthenticationError } from '../../../../../shared/core/AuthenticationError';
10
11export interface CreateCollectionDTO {
12 name: string;
13 description?: string;
14 curatorId: string;
15}
16
17export interface CreateCollectionResponseDTO {
18 collectionId: string;
19}
20
21export class ValidationError extends UseCaseError {
22 constructor(message: string) {
23 super(message);
24 }
25}
26
27export class CreateCollectionUseCase
28 implements
29 UseCase<
30 CreateCollectionDTO,
31 Result<
32 CreateCollectionResponseDTO,
33 ValidationError | AuthenticationError | AppError.UnexpectedError
34 >
35 >
36{
37 constructor(
38 private collectionRepository: ICollectionRepository,
39 private collectionPublisher: ICollectionPublisher,
40 ) {}
41
42 async execute(
43 request: CreateCollectionDTO,
44 ): Promise<
45 Result<
46 CreateCollectionResponseDTO,
47 ValidationError | AuthenticationError | AppError.UnexpectedError
48 >
49 > {
50 try {
51 // Validate and create CuratorId
52 const curatorIdResult = CuratorId.create(request.curatorId);
53 if (curatorIdResult.isErr()) {
54 return err(
55 new ValidationError(
56 `Invalid curator ID: ${curatorIdResult.error.message}`,
57 ),
58 );
59 }
60 const curatorId = curatorIdResult.value;
61
62 // Create collection
63 const collectionResult = Collection.create({
64 authorId: curatorId,
65 name: request.name,
66 description: request.description,
67 accessType: CollectionAccessType.CLOSED,
68 collaboratorIds: [],
69 createdAt: new Date(),
70 updatedAt: new Date(),
71 });
72
73 if (collectionResult.isErr()) {
74 return err(new ValidationError(collectionResult.error.message));
75 }
76
77 const collection = collectionResult.value;
78
79 // Save collection
80 const saveResult = await this.collectionRepository.save(collection);
81 if (saveResult.isErr()) {
82 return err(AppError.UnexpectedError.create(saveResult.error));
83 }
84
85 // Publish collection
86 const publishResult = await this.collectionPublisher.publish(collection);
87 if (publishResult.isErr()) {
88 // Propagate authentication errors
89 if (publishResult.error instanceof AuthenticationError) {
90 return err(publishResult.error);
91 }
92 return err(
93 new ValidationError(
94 `Failed to publish collection: ${publishResult.error.message}`,
95 ),
96 );
97 }
98
99 // Mark collection as published
100 collection.markAsPublished(publishResult.value);
101
102 // Save updated collection with published record ID
103 const saveUpdatedResult =
104 await this.collectionRepository.save(collection);
105 if (saveUpdatedResult.isErr()) {
106 return err(AppError.UnexpectedError.create(saveUpdatedResult.error));
107 }
108
109 return ok({
110 collectionId: collection.collectionId.getStringValue(),
111 });
112 } catch (error) {
113 return err(AppError.UnexpectedError.create(error));
114 }
115 }
116}