import { Entity } from '@core/shared/domain/Entity'; import { IdentityDomainValidationError } from '../errors/IdentityDomainError'; import { ExternalRating } from '../value-objects/ExternalRating'; import { ExternalRatingProvenance } from '../value-objects/ExternalRatingProvenance'; import { GameKey } from '../value-objects/GameKey'; import { UserId } from '../value-objects/UserId'; export interface ExternalGameRatingProfileProps { userId: UserId; gameKey: GameKey; ratings: Map; // type -> rating provenance: ExternalRatingProvenance; } export class ExternalGameRatingProfile extends Entity { private readonly _userId: UserId; private readonly _gameKey: GameKey; private _ratings: Map; private _provenance: ExternalRatingProvenance; private constructor(props: ExternalGameRatingProfileProps) { super(props.userId); this._userId = props.userId; this._gameKey = props.gameKey; this._ratings = props.ratings; this._provenance = props.provenance; } static create(props: ExternalGameRatingProfileProps): ExternalGameRatingProfile { if (!props.userId || !props.gameKey || !props.ratings || !props.provenance) { throw new IdentityDomainValidationError('All properties are required'); } // Note: Empty ratings map is allowed for initial creation // The entity can be created with no ratings and updated later // Validate that all ratings match the gameKey for (const [type, rating] of props.ratings.entries()) { if (!rating.gameKey.equals(props.gameKey)) { throw new IdentityDomainValidationError( `Rating type ${type} has mismatched gameKey` ); } } return new ExternalGameRatingProfile(props); } static restore(props: { userId: string; gameKey: string; ratings: Array<{ type: string; gameKey: string; value: number }>; provenance: { source: string; lastSyncedAt: Date; verified?: boolean; }; }): ExternalGameRatingProfile { const userId = UserId.fromString(props.userId); const gameKey = GameKey.create(props.gameKey); const ratingsMap = new Map(); for (const ratingData of props.ratings) { const ratingGameKey = GameKey.create(ratingData.gameKey); const rating = ExternalRating.create(ratingGameKey, ratingData.type, ratingData.value); ratingsMap.set(ratingData.type, rating); } const provenance = ExternalRatingProvenance.restore(props.provenance); return new ExternalGameRatingProfile({ userId, gameKey, ratings: ratingsMap, provenance, }); } get userId(): UserId { return this._userId; } get gameKey(): GameKey { return this._gameKey; } get ratings(): ReadonlyMap { return new Map(this._ratings); } get provenance(): ExternalRatingProvenance { return this._provenance; } /** * Update ratings and provenance with latest data */ updateRatings( newRatings: Map, newProvenance: ExternalRatingProvenance ): void { // Validate all new ratings match the gameKey for (const [type, rating] of newRatings.entries()) { if (!rating.gameKey.equals(this._gameKey)) { throw new IdentityDomainValidationError( `Rating type ${type} has mismatched gameKey` ); } } this._ratings = newRatings; this._provenance = newProvenance; } /** * Get a specific rating by type */ getRatingByType(type: string): ExternalRating | undefined { return this._ratings.get(type); } /** * Get all rating types */ getRatingTypes(): string[] { return Array.from(this._ratings.keys()); } /** * Check if profile has any ratings */ hasRatings(): boolean { return this._ratings.size > 0; } /** * Mark provenance as verified */ markVerified(): void { this._provenance = this._provenance.markVerified(); } /** * Update last synced timestamp */ updateLastSyncedAt(date: Date): void { this._provenance = this._provenance.updateLastSyncedAt(date); } /** * Get summary for display */ toSummary(): { userId: string; gameKey: string; ratingCount: number; ratingTypes: string[]; source: string; lastSyncedAt: Date; verified: boolean; } { return { userId: this._userId.toString(), gameKey: this._gameKey.toString(), ratingCount: this._ratings.size, ratingTypes: this.getRatingTypes(), source: this._provenance.source, lastSyncedAt: this._provenance.lastSyncedAt, verified: this._provenance.verified, }; } /** * Serialize for storage */ toJSON(): { userId: string; gameKey: string; ratings: Array<{ type: string; gameKey: string; value: number }>; provenance: { source: string; lastSyncedAt: Date; verified: boolean; }; } { return { userId: this._userId.toString(), gameKey: this._gameKey.toString(), ratings: Array.from(this._ratings.entries()).map(([type, rating]) => ({ type, gameKey: rating.gameKey.toString(), value: rating.value, })), provenance: { source: this._provenance.source, lastSyncedAt: this._provenance.lastSyncedAt, verified: this._provenance.verified, }, }; } equals(other: ExternalGameRatingProfile): boolean { if (!(other instanceof ExternalGameRatingProfile)) { return false; } if (!this._userId.equals(other._userId)) { return false; } if (!this._gameKey.equals(other._gameKey)) { return false; } if (!this._provenance.equals(other._provenance)) { return false; } // Compare ratings maps if (this._ratings.size !== other._ratings.size) { return false; } for (const [type, rating] of this._ratings.entries()) { const otherRating = other._ratings.get(type); if (!otherRating || !rating.equals(otherRating)) { return false; } } return true; } }