rating
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { IExternalGameRatingRepository } from '../../domain/repositories/IExternalGameRatingRepository';
|
||||
import { ExternalGameRatingProfile } from '../../domain/entities/ExternalGameRatingProfile';
|
||||
import { UserId } from '../../domain/value-objects/UserId';
|
||||
import { GameKey } from '../../domain/value-objects/GameKey';
|
||||
import { ExternalRating } from '../../domain/value-objects/ExternalRating';
|
||||
import { ExternalRatingProvenance } from '../../domain/value-objects/ExternalRatingProvenance';
|
||||
import { UpsertExternalGameRatingInput, UpsertExternalGameRatingOutput } from '../dtos/UpsertExternalGameRatingDto';
|
||||
|
||||
/**
|
||||
* Use Case: UpsertExternalGameRatingUseCase
|
||||
*
|
||||
* Upserts external game rating profile with latest data and provenance.
|
||||
* Follows CQRS Light: command side operation.
|
||||
*
|
||||
* Store/display only, no compute.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Validate input
|
||||
* 2. Check if profile exists
|
||||
* 3. Create or update profile with latest ratings
|
||||
* 4. Save to repository
|
||||
* 5. Return result
|
||||
*/
|
||||
export class UpsertExternalGameRatingUseCase {
|
||||
constructor(
|
||||
private readonly externalGameRatingRepository: IExternalGameRatingRepository
|
||||
) {}
|
||||
|
||||
async execute(input: UpsertExternalGameRatingInput): Promise<UpsertExternalGameRatingOutput> {
|
||||
try {
|
||||
// 1. Validate input
|
||||
const validationError = this.validateInput(input);
|
||||
if (validationError) {
|
||||
return {
|
||||
success: false,
|
||||
profile: {
|
||||
userId: input.userId,
|
||||
gameKey: input.gameKey,
|
||||
ratingCount: 0,
|
||||
ratingTypes: [],
|
||||
source: input.provenance.source,
|
||||
lastSyncedAt: input.provenance.lastSyncedAt,
|
||||
verified: input.provenance.verified ?? false,
|
||||
},
|
||||
action: 'created',
|
||||
errors: [validationError],
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Check if profile exists
|
||||
const existingProfile = await this.externalGameRatingRepository.findByUserIdAndGameKey(
|
||||
input.userId,
|
||||
input.gameKey
|
||||
);
|
||||
|
||||
// 3. Create or update profile
|
||||
let profile: ExternalGameRatingProfile;
|
||||
let action: 'created' | 'updated';
|
||||
|
||||
if (existingProfile) {
|
||||
// Update existing profile
|
||||
const ratingsMap = this.createRatingsMap(input.ratings, input.gameKey);
|
||||
const provenance = ExternalRatingProvenance.create({
|
||||
source: input.provenance.source,
|
||||
lastSyncedAt: new Date(input.provenance.lastSyncedAt),
|
||||
verified: input.provenance.verified ?? false,
|
||||
});
|
||||
|
||||
existingProfile.updateRatings(ratingsMap, provenance);
|
||||
profile = existingProfile;
|
||||
action = 'updated';
|
||||
} else {
|
||||
// Create new profile
|
||||
profile = this.createProfile(input);
|
||||
action = 'created';
|
||||
}
|
||||
|
||||
// 4. Save to repository
|
||||
const savedProfile = await this.externalGameRatingRepository.save(profile);
|
||||
|
||||
// 5. Return result
|
||||
const summary = savedProfile.toSummary();
|
||||
return {
|
||||
success: true,
|
||||
profile: {
|
||||
userId: summary.userId,
|
||||
gameKey: summary.gameKey,
|
||||
ratingCount: summary.ratingCount,
|
||||
ratingTypes: summary.ratingTypes,
|
||||
source: summary.source,
|
||||
lastSyncedAt: summary.lastSyncedAt.toISOString(),
|
||||
verified: summary.verified,
|
||||
},
|
||||
action,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
return {
|
||||
success: false,
|
||||
profile: {
|
||||
userId: input.userId,
|
||||
gameKey: input.gameKey,
|
||||
ratingCount: 0,
|
||||
ratingTypes: [],
|
||||
source: input.provenance.source,
|
||||
lastSyncedAt: input.provenance.lastSyncedAt,
|
||||
verified: input.provenance.verified ?? false,
|
||||
},
|
||||
action: 'created',
|
||||
errors: [errorMessage],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private validateInput(input: UpsertExternalGameRatingInput): string | null {
|
||||
if (!input.userId || input.userId.trim().length === 0) {
|
||||
return 'User ID is required';
|
||||
}
|
||||
|
||||
if (!input.gameKey || input.gameKey.trim().length === 0) {
|
||||
return 'Game key is required';
|
||||
}
|
||||
|
||||
if (!input.ratings || input.ratings.length === 0) {
|
||||
return 'Ratings are required';
|
||||
}
|
||||
|
||||
for (const rating of input.ratings) {
|
||||
if (!rating.type || rating.type.trim().length === 0) {
|
||||
return 'Rating type cannot be empty';
|
||||
}
|
||||
if (typeof rating.value !== 'number' || isNaN(rating.value)) {
|
||||
return `Rating value for type ${rating.type} must be a valid number`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!input.provenance.source || input.provenance.source.trim().length === 0) {
|
||||
return 'Provenance source is required';
|
||||
}
|
||||
|
||||
const syncDate = new Date(input.provenance.lastSyncedAt);
|
||||
if (isNaN(syncDate.getTime())) {
|
||||
return 'Provenance lastSyncedAt must be a valid date';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private createRatingsMap(
|
||||
ratingsData: Array<{ type: string; value: number }>,
|
||||
gameKeyString: string
|
||||
): Map<string, ExternalRating> {
|
||||
const gameKey = GameKey.create(gameKeyString);
|
||||
const ratingsMap = new Map<string, ExternalRating>();
|
||||
|
||||
for (const ratingData of ratingsData) {
|
||||
const rating = ExternalRating.create(
|
||||
gameKey,
|
||||
ratingData.type.trim(),
|
||||
ratingData.value
|
||||
);
|
||||
ratingsMap.set(ratingData.type.trim(), rating);
|
||||
}
|
||||
|
||||
return ratingsMap;
|
||||
}
|
||||
|
||||
private createProfile(input: UpsertExternalGameRatingInput): ExternalGameRatingProfile {
|
||||
const userId = UserId.fromString(input.userId);
|
||||
const gameKey = GameKey.create(input.gameKey);
|
||||
const ratingsMap = this.createRatingsMap(input.ratings, input.gameKey);
|
||||
const provenance = ExternalRatingProvenance.create({
|
||||
source: input.provenance.source,
|
||||
lastSyncedAt: new Date(input.provenance.lastSyncedAt),
|
||||
verified: input.provenance.verified ?? false,
|
||||
});
|
||||
|
||||
return ExternalGameRatingProfile.create({
|
||||
userId,
|
||||
gameKey,
|
||||
ratings: ratingsMap,
|
||||
provenance,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user