233 lines
6.0 KiB
TypeScript
233 lines
6.0 KiB
TypeScript
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<string, ExternalRating>; // type -> rating
|
|
provenance: ExternalRatingProvenance;
|
|
}
|
|
|
|
export class ExternalGameRatingProfile extends Entity<UserId> {
|
|
private readonly _userId: UserId;
|
|
private readonly _gameKey: GameKey;
|
|
private _ratings: Map<string, ExternalRating>;
|
|
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<string, ExternalRating>();
|
|
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<string, ExternalRating> {
|
|
return new Map(this._ratings);
|
|
}
|
|
|
|
get provenance(): ExternalRatingProvenance {
|
|
return this._provenance;
|
|
}
|
|
|
|
/**
|
|
* Update ratings and provenance with latest data
|
|
*/
|
|
updateRatings(
|
|
newRatings: Map<string, ExternalRating>,
|
|
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;
|
|
}
|
|
} |