rating
This commit is contained in:
233
core/identity/domain/entities/ExternalGameRatingProfile.ts
Normal file
233
core/identity/domain/entities/ExternalGameRatingProfile.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { Entity } from '@core/shared/domain';
|
||||
import { UserId } from '../value-objects/UserId';
|
||||
import { GameKey } from '../value-objects/GameKey';
|
||||
import { ExternalRating } from '../value-objects/ExternalRating';
|
||||
import { ExternalRatingProvenance } from '../value-objects/ExternalRatingProvenance';
|
||||
import { IdentityDomainValidationError } from '../errors/IdentityDomainError';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user