refactor
This commit is contained in:
@@ -5,12 +5,14 @@
|
||||
* Returns metrics formatted for display to sponsors and admins.
|
||||
*/
|
||||
|
||||
import type { AsyncUseCase , Logger } from '@core/shared/application';
|
||||
import type { AsyncUseCase , Logger, UseCaseOutputPort } from '@core/shared/application';
|
||||
import type { IPageViewRepository } from '../repositories/IPageViewRepository';
|
||||
import type { IEngagementRepository } from '@core/analytics/domain/repositories/IEngagementRepository';
|
||||
import type { IAnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/IAnalyticsSnapshotRepository';
|
||||
import type { EntityType } from '../../domain/types/PageView';
|
||||
import type { SnapshotPeriod } from '../../domain/types/AnalyticsSnapshot';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
export interface GetEntityAnalyticsInput {
|
||||
entityType: EntityType;
|
||||
@@ -42,89 +44,68 @@ export interface EntityAnalyticsOutput {
|
||||
};
|
||||
}
|
||||
|
||||
export type GetEntityAnalyticsErrorCode = 'REPOSITORY_ERROR';
|
||||
|
||||
export class GetEntityAnalyticsQuery
|
||||
implements AsyncUseCase<GetEntityAnalyticsInput, EntityAnalyticsOutput> {
|
||||
implements AsyncUseCase<GetEntityAnalyticsInput, EntityAnalyticsOutput, GetEntityAnalyticsErrorCode> {
|
||||
constructor(
|
||||
private readonly pageViewRepository: IPageViewRepository,
|
||||
private readonly engagementRepository: IEngagementRepository,
|
||||
private readonly snapshotRepository: IAnalyticsSnapshotRepository,
|
||||
private readonly output: UseCaseOutputPort<Result<EntityAnalyticsOutput, ApplicationErrorCode<GetEntityAnalyticsErrorCode, { message: string }>>>,
|
||||
private readonly logger: Logger
|
||||
) {}
|
||||
|
||||
async execute(input: GetEntityAnalyticsInput): Promise<EntityAnalyticsOutput> {
|
||||
this.logger.debug(`Executing GetEntityAnalyticsQuery with input: ${JSON.stringify(input)}`);
|
||||
const period = input.period ?? 'weekly';
|
||||
const now = new Date();
|
||||
const since = input.since ?? this.getPeriodStartDate(now, period);
|
||||
this.logger.debug(`Calculated period: ${period}, now: ${now.toISOString()}, since: ${since.toISOString()}`);
|
||||
|
||||
// Get current metrics
|
||||
let totalPageViews = 0;
|
||||
async execute(input: GetEntityAnalyticsInput): Promise<Result<EntityAnalyticsOutput, ApplicationErrorCode<GetEntityAnalyticsErrorCode, { message: string }>>> {
|
||||
try {
|
||||
this.logger.debug(`Executing GetEntityAnalyticsQuery with input: ${JSON.stringify(input)}`);
|
||||
const period = input.period ?? 'weekly';
|
||||
const now = new Date();
|
||||
const since = input.since ?? this.getPeriodStartDate(now, period);
|
||||
this.logger.debug(`Calculated period: ${period}, now: ${now.toISOString()}, since: ${since.toISOString()}`);
|
||||
|
||||
// Get current metrics
|
||||
let totalPageViews = 0;
|
||||
totalPageViews = await this.pageViewRepository.countByEntityId(
|
||||
input.entityType,
|
||||
input.entityId,
|
||||
since
|
||||
);
|
||||
this.logger.debug(`Total page views for entity ${input.entityId}: ${totalPageViews}`);
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(`Error counting total page views for entity ${input.entityId}: ${err.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
let uniqueVisitors = 0;
|
||||
try {
|
||||
let uniqueVisitors = 0;
|
||||
uniqueVisitors = await this.pageViewRepository.countUniqueVisitors(
|
||||
input.entityType,
|
||||
input.entityId,
|
||||
since
|
||||
);
|
||||
this.logger.debug(`Unique visitors for entity ${input.entityId}: ${uniqueVisitors}`);
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(`Error counting unique visitors for entity ${input.entityId}: ${err.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
let sponsorClicks = 0;
|
||||
try {
|
||||
let sponsorClicks = 0;
|
||||
sponsorClicks = await this.engagementRepository.getSponsorClicksForEntity(
|
||||
input.entityId,
|
||||
since
|
||||
);
|
||||
this.logger.debug(`Sponsor clicks for entity ${input.entityId}: ${sponsorClicks}`);
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(`Error getting sponsor clicks for entity ${input.entityId}: ${err.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Calculate engagement score (weighted sum of actions)
|
||||
let engagementScore = 0;
|
||||
try {
|
||||
// Calculate engagement score (weighted sum of actions)
|
||||
let engagementScore = 0;
|
||||
engagementScore = await this.calculateEngagementScore(input.entityId, since);
|
||||
this.logger.debug(`Engagement score for entity ${input.entityId}: ${engagementScore}`);
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(`Error calculating engagement score for entity ${input.entityId}: ${err.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Determine trust indicator
|
||||
const trustIndicator = this.determineTrustIndicator(totalPageViews, uniqueVisitors, engagementScore);
|
||||
this.logger.debug(`Trust indicator for entity ${input.entityId}: ${trustIndicator}`);
|
||||
|
||||
// Calculate exposure value (for sponsor ROI)
|
||||
const exposureValue = this.calculateExposureValue(totalPageViews, uniqueVisitors, sponsorClicks);
|
||||
this.logger.debug(`Exposure value for entity ${input.entityId}: ${exposureValue}`);
|
||||
// Determine trust indicator
|
||||
const trustIndicator = this.determineTrustIndicator(totalPageViews, uniqueVisitors, engagementScore);
|
||||
this.logger.debug(`Trust indicator for entity ${input.entityId}: ${trustIndicator}`);
|
||||
|
||||
// Get previous period for trends
|
||||
const previousPeriodStart = this.getPreviousPeriodStart(since, period);
|
||||
this.logger.debug(`Previous period start: ${previousPeriodStart.toISOString()}`);
|
||||
// Calculate exposure value (for sponsor ROI)
|
||||
const exposureValue = this.calculateExposureValue(totalPageViews, uniqueVisitors, sponsorClicks);
|
||||
this.logger.debug(`Exposure value for entity ${input.entityId}: ${exposureValue}`);
|
||||
|
||||
let previousPageViews = 0;
|
||||
try {
|
||||
// Get previous period for trends
|
||||
const previousPeriodStart = this.getPreviousPeriodStart(since, period);
|
||||
this.logger.debug(`Previous period start: ${previousPeriodStart.toISOString()}`);
|
||||
|
||||
let previousPageViews = 0;
|
||||
const fullPreviousPageViews = await this.pageViewRepository.countByEntityId(
|
||||
input.entityType,
|
||||
input.entityId,
|
||||
@@ -132,14 +113,8 @@ export class GetEntityAnalyticsQuery
|
||||
);
|
||||
previousPageViews = fullPreviousPageViews - totalPageViews; // This calculates change, not just previous period's total
|
||||
this.logger.debug(`Previous period full page views: ${fullPreviousPageViews}, change: ${previousPageViews}`);
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(`Error counting previous period page views for entity ${input.entityId}: ${err.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
let previousUniqueVisitors = 0;
|
||||
try {
|
||||
let previousUniqueVisitors = 0;
|
||||
const fullPreviousUniqueVisitors = await this.pageViewRepository.countUniqueVisitors(
|
||||
input.entityType,
|
||||
input.entityId,
|
||||
@@ -148,36 +123,42 @@ export class GetEntityAnalyticsQuery
|
||||
previousUniqueVisitors = fullPreviousUniqueVisitors - uniqueVisitors; // This calculates change, not just previous period's total
|
||||
this.logger.debug(`Previous period full unique visitors: ${fullPreviousUniqueVisitors}, change: ${previousUniqueVisitors}`);
|
||||
|
||||
const resultData: EntityAnalyticsOutput = {
|
||||
entityType: input.entityType,
|
||||
entityId: input.entityId,
|
||||
summary: {
|
||||
totalPageViews,
|
||||
uniqueVisitors,
|
||||
sponsorClicks,
|
||||
engagementScore,
|
||||
trustIndicator,
|
||||
exposureValue,
|
||||
},
|
||||
trends: {
|
||||
pageViewsChange: this.calculatePercentageChange(previousPageViews, totalPageViews),
|
||||
uniqueVisitorsChange: this.calculatePercentageChange(previousUniqueVisitors, uniqueVisitors),
|
||||
engagementChange: 0, // Would need historical engagement data
|
||||
},
|
||||
period: {
|
||||
start: since,
|
||||
end: now,
|
||||
label: this.formatPeriodLabel(since, now),
|
||||
},
|
||||
};
|
||||
const result = Result.ok<EntityAnalyticsOutput, ApplicationErrorCode<GetEntityAnalyticsErrorCode, { message: string }>>(resultData);
|
||||
this.output.present(result);
|
||||
this.logger.info(`Successfully retrieved analytics for entity ${input.entityId}.`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(`Error counting previous period unique visitors for entity ${input.entityId}: ${err.message}`);
|
||||
throw error;
|
||||
const err = error as Error;
|
||||
this.logger.error(`Failed to get entity analytics for ${input.entityId}: ${err.message}`, err);
|
||||
const result = Result.err<EntityAnalyticsOutput, ApplicationErrorCode<GetEntityAnalyticsErrorCode, { message: string }>>({
|
||||
code: 'REPOSITORY_ERROR',
|
||||
details: { message: err.message ?? 'Failed to get entity analytics' },
|
||||
});
|
||||
this.output.present(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
const result: EntityAnalyticsOutput = {
|
||||
entityType: input.entityType,
|
||||
entityId: input.entityId,
|
||||
summary: {
|
||||
totalPageViews,
|
||||
uniqueVisitors,
|
||||
sponsorClicks,
|
||||
engagementScore,
|
||||
trustIndicator,
|
||||
exposureValue,
|
||||
},
|
||||
trends: {
|
||||
pageViewsChange: this.calculatePercentageChange(previousPageViews, totalPageViews),
|
||||
uniqueVisitorsChange: this.calculatePercentageChange(previousUniqueVisitors, uniqueVisitors),
|
||||
engagementChange: 0, // Would need historical engagement data
|
||||
},
|
||||
period: {
|
||||
start: since,
|
||||
end: now,
|
||||
label: this.formatPeriodLabel(since, now),
|
||||
},
|
||||
};
|
||||
this.logger.info(`Successfully retrieved analytics for entity ${input.entityId}.`);
|
||||
return result;
|
||||
}
|
||||
|
||||
private getPeriodStartDate(now: Date, period: SnapshotPeriod): Date {
|
||||
|
||||
Reference in New Issue
Block a user