From 2c014401f149e2ccfde752a801af4d3e7c518ea0 Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Fri, 1 Aug 2025 09:16:30 -0500 Subject: [PATCH] update Activity to FeedActivity and simplify abstractions --- src/modules/feeds/domain/Activity.ts | 154 ------------------ src/modules/feeds/domain/FeedActivity.ts | 94 +++++++++++ src/modules/feeds/domain/IFeedRepository.ts | 12 +- .../feeds/domain/services/FeedService.ts | 47 +++--- 4 files changed, 126 insertions(+), 181 deletions(-) delete mode 100644 src/modules/feeds/domain/Activity.ts create mode 100644 src/modules/feeds/domain/FeedActivity.ts diff --git a/src/modules/feeds/domain/Activity.ts b/src/modules/feeds/domain/Activity.ts deleted file mode 100644 index 040a8d1f..00000000 --- a/src/modules/feeds/domain/Activity.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Entity } from '../../../shared/domain/Entity'; -import { UniqueEntityID } from '../../../shared/domain/UniqueEntityID'; -import { Result, ok, err } from '../../../shared/core/Result'; -import { ActivityId } from './value-objects/ActivityId'; -import { ActivityType, ActivityTypeEnum } from './value-objects/ActivityType'; -import { CuratorId } from '../../cards/domain/value-objects/CuratorId'; -import { CardId } from '../../cards/domain/value-objects/CardId'; -import { CollectionId } from '../../cards/domain/value-objects/CollectionId'; - -export class ActivityValidationError extends Error { - constructor(message: string) { - super(message); - this.name = 'ActivityValidationError'; - } -} - -// Activity metadata for different activity types -export interface CardAddedToLibraryMetadata { - cardId: string; - cardTitle?: string; - cardUrl?: string; -} - -export interface CardAddedToCollectionMetadata { - cardId: string; - cardTitle?: string; - cardUrl?: string; - collectionIds: string[]; - collectionNames: string[]; -} - -export type ActivityMetadata = - | CardAddedToLibraryMetadata - | CardAddedToCollectionMetadata; - -interface ActivityProps { - actorId: CuratorId; // The user who performed the activity - type: ActivityType; - metadata: ActivityMetadata; - createdAt: Date; -} - -export class Activity extends Entity { - get activityId(): ActivityId { - return ActivityId.create(this._id).unwrap(); - } - - get actorId(): CuratorId { - return this.props.actorId; - } - - get type(): ActivityType { - return this.props.type; - } - - get metadata(): ActivityMetadata { - return this.props.metadata; - } - - get createdAt(): Date { - return this.props.createdAt; - } - - // Type guards for metadata - get isCardAddedToLibrary(): boolean { - return this.props.type.value === ActivityTypeEnum.CARD_ADDED_TO_LIBRARY; - } - - get isCardAddedToCollection(): boolean { - return this.props.type.value === ActivityTypeEnum.CARD_ADDED_TO_COLLECTION; - } - - get cardAddedToLibraryMetadata(): CardAddedToLibraryMetadata | null { - return this.isCardAddedToLibrary ? this.props.metadata as CardAddedToLibraryMetadata : null; - } - - get cardAddedToCollectionMetadata(): CardAddedToCollectionMetadata | null { - return this.isCardAddedToCollection ? this.props.metadata as CardAddedToCollectionMetadata : null; - } - - private constructor(props: ActivityProps, id?: UniqueEntityID) { - super(props, id); - } - - public static createCardAddedToLibrary( - actorId: CuratorId, - cardId: CardId, - cardTitle?: string, - cardUrl?: string, - createdAt?: Date, - id?: UniqueEntityID, - ): Result { - const typeResult = ActivityType.cardAddedToLibrary(); - if (typeResult.isErr()) { - return err(new ActivityValidationError(typeResult.error.message)); - } - - const metadata: CardAddedToLibraryMetadata = { - cardId: cardId.getStringValue(), - cardTitle, - cardUrl, - }; - - const props: ActivityProps = { - actorId, - type: typeResult.value, - metadata, - createdAt: createdAt || new Date(), - }; - - return ok(new Activity(props, id)); - } - - public static createCardAddedToCollection( - actorId: CuratorId, - cardId: CardId, - collectionIds: CollectionId[], - collectionNames: string[], - cardTitle?: string, - cardUrl?: string, - createdAt?: Date, - id?: UniqueEntityID, - ): Result { - if (collectionIds.length !== collectionNames.length) { - return err(new ActivityValidationError('Collection IDs and names arrays must have the same length')); - } - - if (collectionIds.length === 0) { - return err(new ActivityValidationError('At least one collection must be specified')); - } - - const typeResult = ActivityType.cardAddedToCollection(); - if (typeResult.isErr()) { - return err(new ActivityValidationError(typeResult.error.message)); - } - - const metadata: CardAddedToCollectionMetadata = { - cardId: cardId.getStringValue(), - cardTitle, - cardUrl, - collectionIds: collectionIds.map(id => id.getStringValue()), - collectionNames, - }; - - const props: ActivityProps = { - actorId, - type: typeResult.value, - metadata, - createdAt: createdAt || new Date(), - }; - - return ok(new Activity(props, id)); - } -} diff --git a/src/modules/feeds/domain/FeedActivity.ts b/src/modules/feeds/domain/FeedActivity.ts new file mode 100644 index 00000000..ebe4d9d2 --- /dev/null +++ b/src/modules/feeds/domain/FeedActivity.ts @@ -0,0 +1,94 @@ +import { Entity } from '../../../shared/domain/Entity'; +import { UniqueEntityID } from '../../../shared/domain/UniqueEntityID'; +import { Result, ok, err } from '../../../shared/core/Result'; +import { ActivityId } from './value-objects/ActivityId'; +import { ActivityType, ActivityTypeEnum } from './value-objects/ActivityType'; +import { CuratorId } from '../../cards/domain/value-objects/CuratorId'; +import { CardId } from '../../cards/domain/value-objects/CardId'; +import { CollectionId } from '../../cards/domain/value-objects/CollectionId'; + +export class ActivityValidationError extends Error { + constructor(message: string) { + super(message); + this.name = 'ActivityValidationError'; + } +} + +export interface CardCollectedMetadata { + cardId: string; + collectionIds?: string[]; +} + +export type ActivityMetadata = CardCollectedMetadata; + +interface ActivityProps { + actorId: CuratorId; // The user who performed the activity + type: ActivityType; // The type of activity + metadata: ActivityMetadata; // Additional metadata specific to the activity type + createdAt: Date; +} + +export class FeedActivity extends Entity { + get activityId(): ActivityId { + return ActivityId.create(this._id).unwrap(); + } + + get actorId(): CuratorId { + return this.props.actorId; + } + + get type(): ActivityType { + return this.props.type; + } + + get metadata(): ActivityMetadata { + return this.props.metadata; + } + + get createdAt(): Date { + return this.props.createdAt; + } + + // Type guards for metadata + get cardCollected(): boolean { + return ( + this.props.type.value === ActivityTypeEnum.CARD_ADDED_TO_LIBRARY || + this.props.type.value === ActivityTypeEnum.CARD_ADDED_TO_COLLECTION + ); + } + + private constructor(props: ActivityProps, id?: UniqueEntityID) { + super(props, id); + } + + public static createCardCollected( + actorId: CuratorId, + cardId: CardId, + collectionIds?: CollectionId[], + createdAt?: Date, + id?: UniqueEntityID, + ): Result { + if (!cardId) { + return err(new ActivityValidationError('Card ID is required')); + } + + const typeResult = ActivityType.cardCollected(); + if (typeResult.isErr()) { + return err(new ActivityValidationError(typeResult.error.message)); + } + + const metadata: CardCollectedMetadata = { + cardId: cardId.getStringValue(), + collectionIds: collectionIds?.map((id) => id.getStringValue()), + }; + + const props: ActivityProps = { + actorId, + type: typeResult.value, + metadata, + createdAt: createdAt || new Date(), + }; + + return ok(new FeedActivity(props, id)); + } +} diff --git a/src/modules/feeds/domain/IFeedRepository.ts b/src/modules/feeds/domain/IFeedRepository.ts index 226e524a..384e6c1e 100644 --- a/src/modules/feeds/domain/IFeedRepository.ts +++ b/src/modules/feeds/domain/IFeedRepository.ts @@ -1,5 +1,5 @@ import { Result } from '../../../shared/core/Result'; -import { Activity } from './Activity'; +import { FeedActivity } from './FeedActivity'; import { ActivityId } from './value-objects/ActivityId'; export interface FeedQueryOptions { @@ -9,14 +9,16 @@ export interface FeedQueryOptions { } export interface PaginatedFeedResult { - activities: Activity[]; + activities: FeedActivity[]; totalCount: number; hasMore: boolean; nextCursor?: ActivityId; // For cursor-based pagination } export interface IFeedRepository { - addActivity(activity: Activity): Promise>; - getGlobalFeed(options: FeedQueryOptions): Promise>; - findById(activityId: ActivityId): Promise>; + addActivity(activity: FeedActivity): Promise>; + getGlobalFeed( + options: FeedQueryOptions, + ): Promise>; + findById(activityId: ActivityId): Promise>; } diff --git a/src/modules/feeds/domain/services/FeedService.ts b/src/modules/feeds/domain/services/FeedService.ts index d9b0141b..498df68c 100644 --- a/src/modules/feeds/domain/services/FeedService.ts +++ b/src/modules/feeds/domain/services/FeedService.ts @@ -1,6 +1,6 @@ import { Result, ok, err } from '../../../../shared/core/Result'; import { DomainService } from '../../../../shared/domain/DomainService'; -import { Activity } from '../Activity'; +import { FeedActivity } from '../FeedActivity'; import { IFeedRepository } from '../IFeedRepository'; import { CuratorId } from '../../../cards/domain/value-objects/CuratorId'; import { CardId } from '../../../cards/domain/value-objects/CardId'; @@ -19,16 +19,9 @@ export class FeedService implements DomainService { async addCardAddedToLibraryActivity( actorId: CuratorId, cardId: CardId, - cardTitle?: string, - cardUrl?: string, - ): Promise> { + ): Promise> { try { - const activityResult = Activity.createCardAddedToLibrary( - actorId, - cardId, - cardTitle, - cardUrl, - ); + const activityResult = FeedActivity.createCardCollected(actorId, cardId); if (activityResult.isErr()) { return err(new FeedServiceError(activityResult.error.message)); @@ -38,12 +31,20 @@ export class FeedService implements DomainService { const saveResult = await this.feedRepository.addActivity(activity); if (saveResult.isErr()) { - return err(new FeedServiceError(`Failed to save activity: ${saveResult.error.message}`)); + return err( + new FeedServiceError( + `Failed to save activity: ${saveResult.error.message}`, + ), + ); } return ok(activity); } catch (error) { - return err(new FeedServiceError(`Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`)); + return err( + new FeedServiceError( + `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`, + ), + ); } } @@ -51,18 +52,12 @@ export class FeedService implements DomainService { actorId: CuratorId, cardId: CardId, collectionIds: CollectionId[], - collectionNames: string[], - cardTitle?: string, - cardUrl?: string, - ): Promise> { + ): Promise> { try { - const activityResult = Activity.createCardAddedToCollection( + const activityResult = FeedActivity.createCardCollected( actorId, cardId, collectionIds, - collectionNames, - cardTitle, - cardUrl, ); if (activityResult.isErr()) { @@ -73,12 +68,20 @@ export class FeedService implements DomainService { const saveResult = await this.feedRepository.addActivity(activity); if (saveResult.isErr()) { - return err(new FeedServiceError(`Failed to save activity: ${saveResult.error.message}`)); + return err( + new FeedServiceError( + `Failed to save activity: ${saveResult.error.message}`, + ), + ); } return ok(activity); } catch (error) { - return err(new FeedServiceError(`Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`)); + return err( + new FeedServiceError( + `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`, + ), + ); } } } -- 2.51.2