wip
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
import { Module, ConsoleLogger } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AnalyticsService } from './analytics.service';
|
||||
import { AnalyticsController } from '../../presentation/analytics.controller';
|
||||
import { RecordPageViewUseCase } from './record-page-view.use-case';
|
||||
import { RecordEngagementUseCase } from './record-engagement.use-case';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/domain/repositories/IPageViewRepository';
|
||||
import { IEngagementRepository } from '@gridpilot/analytics/domain/repositories/IEngagementRepository';
|
||||
import { ILogger } from '@gridpilot/shared/logging/ILogger';
|
||||
import { InMemoryPageViewRepository } from '../../infrastructure/analytics/in-memory-page-view.repository';
|
||||
import { TypeOrmPageViewRepository } from '../../infrastructure/analytics/typeorm-page-view.repository';
|
||||
import { InMemoryEngagementRepository } from '../../infrastructure/analytics/in-memory-engagement.repository';
|
||||
import { PageViewEntity } from '../../infrastructure/analytics/typeorm-page-view.entity';
|
||||
|
||||
@Module({
|
||||
imports: [], // Removed TypeOrmModule as we are using in-memory repositories
|
||||
imports: [TypeOrmModule.forFeature([PageViewEntity])],
|
||||
controllers: [AnalyticsController],
|
||||
providers: [
|
||||
AnalyticsService,
|
||||
@@ -18,7 +17,7 @@ import { InMemoryEngagementRepository } from '../../infrastructure/analytics/in-
|
||||
RecordEngagementUseCase,
|
||||
{
|
||||
provide: 'IPageViewRepository',
|
||||
useClass: InMemoryPageViewRepository,
|
||||
useClass: TypeOrmPageViewRepository,
|
||||
},
|
||||
{
|
||||
provide: 'IEngagementRepository',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { RecordPageViewUseCase, RecordPageViewInput, RecordPageViewOutput } from './record-page-view.use-case';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/domain/repositories/IPageViewRepository';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/application/repositories/IPageViewRepository';
|
||||
import { ILogger } from '@gridpilot/shared/logging/ILogger';
|
||||
import { PageView } from '@gridpilot/analytics/domain/entities/PageView';
|
||||
import { EntityType, VisitorType } from '@gridpilot/analytics/domain/types/PageView';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PageView, EntityType, VisitorType } from '@gridpilot/analytics/domain/entities/PageView';
|
||||
import type { IPageViewRepository } from '@gridpilot/analytics/domain/repositories/IPageViewRepository';
|
||||
import type { IPageViewRepository } from '@gridpilot/analytics/application/repositories/IPageViewRepository';
|
||||
|
||||
@Injectable()
|
||||
export class InMemoryPageViewRepository implements IPageViewRepository {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { PageViewMapper } from './PageViewMapper';
|
||||
import { PageView } from '@gridpilot/analytics/domain/entities/PageView';
|
||||
import { PageViewEntity } from '../typeorm-page-view.entity';
|
||||
import { EntityType, VisitorType } from '@gridpilot/analytics/domain/types/PageView';
|
||||
|
||||
describe('PageViewMapper', () => {
|
||||
const now = new Date();
|
||||
|
||||
const pageViewProps = {
|
||||
id: 'test-id',
|
||||
entityType: EntityType.LEAGUE,
|
||||
entityId: 'entity-id',
|
||||
visitorType: VisitorType.ANONYMOUS,
|
||||
sessionId: 'session-id',
|
||||
timestamp: now,
|
||||
visitorId: 'visitor-id',
|
||||
referrer: 'fart.com',
|
||||
userAgent: 'Mozilla',
|
||||
country: 'US',
|
||||
durationMs: 1000,
|
||||
};
|
||||
|
||||
const pageViewDomain = PageView.create(pageViewProps);
|
||||
|
||||
it('should correctly map a PageView domain entity to a PageViewEntity persistence entity', () => {
|
||||
const pageViewEntity = PageViewMapper.toPersistence(pageViewDomain);
|
||||
|
||||
expect(pageViewEntity).toBeInstanceOf(PageViewEntity);
|
||||
expect(pageViewEntity.id).toEqual(pageViewDomain.id);
|
||||
expect(pageViewEntity.entityType).toEqual(pageViewDomain.entityType);
|
||||
expect(pageViewEntity.entityId).toEqual(pageViewDomain.entityId);
|
||||
expect(pageViewEntity.visitorType).toEqual(pageViewDomain.visitorType);
|
||||
expect(pageViewEntity.sessionId).toEqual(pageViewDomain.sessionId);
|
||||
expect(pageViewEntity.timestamp.toISOString()).toEqual(pageViewDomain.timestamp.toISOString());
|
||||
expect(pageViewEntity.visitorId).toEqual(pageViewDomain.visitorId);
|
||||
expect(pageViewEntity.referrer).toEqual(pageViewDomain.referrer);
|
||||
expect(pageViewEntity.userAgent).toEqual(pageViewDomain.userAgent);
|
||||
expect(pageViewEntity.country).toEqual(pageViewDomain.country);
|
||||
expect(pageViewEntity.durationMs).toEqual(pageViewDomain.durationMs);
|
||||
});
|
||||
|
||||
it('should correctly map a PageViewEntity persistence entity to a PageView domain entity', () => {
|
||||
const pageViewEntity = new PageViewEntity();
|
||||
pageViewEntity.id = pageViewProps.id;
|
||||
pageViewEntity.entityType = pageViewProps.entityType as any; // Cast as any because entityType is string in entity
|
||||
pageViewEntity.entityId = pageViewProps.entityId;
|
||||
pageViewEntity.visitorType = pageViewProps.visitorType as any;
|
||||
pageViewEntity.sessionId = pageViewProps.sessionId;
|
||||
pageViewEntity.timestamp = pageViewProps.timestamp;
|
||||
pageViewEntity.visitorId = pageViewProps.visitorId;
|
||||
pageViewEntity.referrer = pageViewProps.referrer;
|
||||
pageViewEntity.userAgent = pageViewProps.userAgent;
|
||||
pageViewEntity.country = pageViewProps.country;
|
||||
pageViewEntity.durationMs = pageViewProps.durationMs;
|
||||
|
||||
const pageView = PageViewMapper.toDomain(pageViewEntity);
|
||||
|
||||
expect(pageView).toBeInstanceOf(PageView);
|
||||
expect(pageView.id).toEqual(pageViewEntity.id);
|
||||
expect(pageView.entityType).toEqual(pageViewEntity.entityType);
|
||||
expect(pageView.entityId).toEqual(pageViewEntity.entityId);
|
||||
expect(pageView.visitorType).toEqual(pageViewEntity.visitorType);
|
||||
expect(pageView.sessionId).toEqual(pageViewEntity.sessionId);
|
||||
expect(pageView.timestamp.toISOString()).toEqual(pageViewEntity.timestamp.toISOString());
|
||||
expect(pageView.visitorId).toEqual(pageViewEntity.visitorId);
|
||||
expect(pageView.referrer).toEqual(pageViewEntity.referrer);
|
||||
expect(pageView.userAgent).toEqual(pageViewEntity.userAgent);
|
||||
expect(pageView.country).toEqual(pageViewEntity.country);
|
||||
expect(pageView.durationMs).toEqual(pageViewEntity.durationMs);
|
||||
});
|
||||
|
||||
it('should handle optional properties correctly when mapping to persistence', () => {
|
||||
const minimalProps = {
|
||||
id: 'minimal-id',
|
||||
entityType: EntityType.RACE,
|
||||
entityId: 'minimal-entity',
|
||||
visitorType: VisitorType.DRIVER,
|
||||
sessionId: 'minimal-session',
|
||||
timestamp: now,
|
||||
};
|
||||
const minimalDomain = PageView.create(minimalProps);
|
||||
const entity = PageViewMapper.toPersistence(minimalDomain);
|
||||
|
||||
expect(entity.visitorId).toBeUndefined();
|
||||
expect(entity.referrer).toBeUndefined();
|
||||
expect(entity.userAgent).toBeUndefined();
|
||||
expect(entity.country).toBeUndefined();
|
||||
expect(entity.durationMs).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle optional properties correctly when mapping to domain', () => {
|
||||
const minimalEntity = new PageViewEntity();
|
||||
minimalEntity.id = 'minimal-id-entity';
|
||||
minimalEntity.entityType = EntityType.RACE as any;
|
||||
minimalEntity.entityId = 'minimal-entity-entity';
|
||||
minimalEntity.visitorType = VisitorType.DRIVER as any;
|
||||
minimalEntity.sessionId = 'minimal-session-entity';
|
||||
minimalEntity.timestamp = now;
|
||||
|
||||
const domain = PageViewMapper.toDomain(minimalEntity);
|
||||
|
||||
expect(domain.visitorId).toBeUndefined();
|
||||
expect(domain.referrer).toBeUndefined();
|
||||
expect(domain.userAgent).toBeUndefined();
|
||||
expect(domain.country).toBeUndefined();
|
||||
expect(domain.durationMs).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { PageView } from '../../domain/entities/PageView';
|
||||
import { PageViewEntity } from '../../../../apps/api/src/infrastructure/analytics/typeorm-page-view.entity';
|
||||
import { EntityType, VisitorType } from '../../domain/types/PageView';
|
||||
import { PageViewProps } from '../../domain/types/PageView';
|
||||
|
||||
export class PageViewMapper {
|
||||
public static toDomain(entity: PageViewEntity): PageView {
|
||||
const props: Omit<PageViewProps, 'timestamp'> & { timestamp?: Date } = {
|
||||
id: entity.id,
|
||||
entityType: entity.entityType as EntityType,
|
||||
entityId: entity.entityId,
|
||||
visitorType: entity.visitorType as VisitorType,
|
||||
sessionId: entity.sessionId,
|
||||
timestamp: entity.timestamp,
|
||||
...(entity.visitorId !== undefined && entity.visitorId !== null ? { visitorId: entity.visitorId } : {}),
|
||||
...(entity.referrer !== undefined && entity.referrer !== null ? { referrer: entity.referrer } : {}),
|
||||
...(entity.userAgent !== undefined && entity.userAgent !== null ? { userAgent: entity.userAgent } : {}),
|
||||
...(entity.country !== undefined && entity.country !== null ? { country: entity.country } : {}),
|
||||
...(entity.durationMs !== undefined && entity.durationMs !== null ? { durationMs: entity.durationMs } : {}),
|
||||
};
|
||||
return PageView.create(props);
|
||||
}
|
||||
|
||||
public static toPersistence(domain: PageView): PageViewEntity {
|
||||
const entity = new PageViewEntity();
|
||||
entity.id = domain.id;
|
||||
entity.entityType = domain.entityType;
|
||||
entity.entityId = domain.entityId;
|
||||
entity.visitorType = domain.visitorType;
|
||||
entity.sessionId = domain.sessionId;
|
||||
entity.timestamp = domain.timestamp;
|
||||
if (domain.visitorId !== undefined) entity.visitorId = domain.visitorId;
|
||||
if (domain.referrer !== undefined) entity.referrer = domain.referrer;
|
||||
if (domain.userAgent !== undefined) entity.userAgent = domain.userAgent;
|
||||
if (domain.country !== undefined) entity.country = domain.country;
|
||||
if (domain.durationMs !== undefined) entity.durationMs = domain.durationMs;
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
import { EntityType, VisitorType } from '@gridpilot/analytics/domain/types/PageView';
|
||||
|
||||
@Entity('page_views')
|
||||
export class PageViewEntity {
|
||||
@PrimaryColumn({ type: 'uuid' })
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'enum', enum: EntityType })
|
||||
entityType: EntityType;
|
||||
@Column({ type: 'enum', enum: ['league', 'driver', 'team', 'race', 'sponsor'] })
|
||||
entityType: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
entityId: string;
|
||||
@@ -15,8 +13,8 @@ export class PageViewEntity {
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
visitorId?: string;
|
||||
|
||||
@Column({ type: 'enum', enum: VisitorType })
|
||||
visitorType: VisitorType;
|
||||
@Column({ type: 'enum', enum: ['anonymous', 'driver', 'sponsor'] })
|
||||
visitorType: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
sessionId: string;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TypeOrmPageViewRepository } from './typeorm-page-view.repository';
|
||||
import { PageViewEntity } from './typeorm-page-view.entity';
|
||||
import { PageView } from '@gridpilot/analytics/domain/entities/PageView';
|
||||
import { EntityType, VisitorType } from '@gridpilot/analytics/domain/types/PageView';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/domain/repositories/IPageViewRepository';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/application/repositories/IPageViewRepository';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
describe('TypeOrmPageViewRepository (Integration)', () => {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, MoreThanOrEqual, Between } from 'typeorm';
|
||||
import { PageView, EntityType } from '@gridpilot/analytics/domain/entities/PageView';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/domain/repositories/IPageViewRepository';
|
||||
import { IPageViewRepository } from '@gridpilot/analytics/application/repositories/IPageViewRepository';
|
||||
import { PageViewEntity } from './typeorm-page-view.entity';
|
||||
import { PageViewMapper } from './mappers/PageViewMapper';
|
||||
import { PageView } from '@gridpilot/analytics/domain/entities/PageView';
|
||||
import { EntityType } from '@gridpilot/analytics/domain/types/PageView';
|
||||
|
||||
@Injectable()
|
||||
export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
@@ -13,7 +15,7 @@ export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
) {}
|
||||
|
||||
async save(pageView: PageView): Promise<void> {
|
||||
const pageViewEntity = this.toPageViewEntity(pageView);
|
||||
const pageViewEntity = PageViewMapper.toPersistence(pageView);
|
||||
await this.pageViewRepository.save(pageViewEntity);
|
||||
}
|
||||
|
||||
@@ -21,7 +23,7 @@ export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
const entity = await this.pageViewRepository.findOne({
|
||||
where: { id },
|
||||
});
|
||||
return entity ? this.toPageView(entity) : null;
|
||||
return entity ? PageViewMapper.toDomain(entity) : null;
|
||||
}
|
||||
|
||||
async findByEntityId(entityType: EntityType, entityId: string): Promise<PageView[]> {
|
||||
@@ -29,7 +31,7 @@ export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
where: { entityType, entityId },
|
||||
order: { timestamp: 'DESC' },
|
||||
});
|
||||
return entities.map(this.toPageView);
|
||||
return entities.map(PageViewMapper.toDomain);
|
||||
}
|
||||
|
||||
async findByDateRange(startDate: Date, endDate: Date): Promise<PageView[]> {
|
||||
@@ -37,7 +39,7 @@ export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
where: { timestamp: Between(startDate, endDate) },
|
||||
order: { timestamp: 'DESC' },
|
||||
});
|
||||
return entities.map(this.toPageView);
|
||||
return entities.map(PageViewMapper.toDomain);
|
||||
}
|
||||
|
||||
async findBySession(sessionId: string): Promise<PageView[]> {
|
||||
@@ -45,7 +47,7 @@ export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
where: { sessionId },
|
||||
order: { timestamp: 'DESC' },
|
||||
});
|
||||
return entities.map(this.toPageView);
|
||||
return entities.map(PageViewMapper.toDomain);
|
||||
}
|
||||
|
||||
async countByEntityId(entityType: EntityType, entityId: string, since?: Date): Promise<number> {
|
||||
@@ -69,36 +71,4 @@ export class TypeOrmPageViewRepository implements IPageViewRepository {
|
||||
const result = await query.getRawOne();
|
||||
return parseInt(result.count, 10) || 0;
|
||||
}
|
||||
|
||||
public toPageViewEntity(pageView: PageView): PageViewEntity {
|
||||
const entity = new PageViewEntity();
|
||||
entity.id = pageView.id;
|
||||
entity.entityType = pageView.entityType;
|
||||
entity.entityId = pageView.entityId;
|
||||
entity.visitorId = pageView.visitorId;
|
||||
entity.visitorType = pageView.visitorType;
|
||||
entity.sessionId = pageView.sessionId;
|
||||
entity.referrer = pageView.referrer;
|
||||
entity.userAgent = pageView.userAgent;
|
||||
entity.country = pageView.country;
|
||||
entity.timestamp = pageView.timestamp;
|
||||
entity.durationMs = pageView.durationMs;
|
||||
return entity;
|
||||
}
|
||||
|
||||
private toPageView(entity: PageViewEntity): PageView {
|
||||
return PageView.create({
|
||||
id: entity.id,
|
||||
entityType: entity.entityType,
|
||||
entityId: entity.entityId,
|
||||
visitorType: entity.visitorType,
|
||||
sessionId: entity.sessionId,
|
||||
timestamp: entity.timestamp,
|
||||
visitorId: entity.visitorId,
|
||||
referrer: entity.referrer,
|
||||
userAgent: entity.userAgent,
|
||||
country: entity.country,
|
||||
durationMs: entity.durationMs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
/**
|
||||
* Repository Interface: IPageViewRepository
|
||||
*
|
||||
* Defines persistence operations for PageView entities.
|
||||
*/
|
||||
|
||||
import type { PageView, EntityType } from '../entities/PageView';
|
||||
import { PageView } from '../../domain/entities/PageView';
|
||||
import { EntityType } from '../../domain/types/PageView';
|
||||
|
||||
export interface IPageViewRepository {
|
||||
save(pageView: PageView): Promise<void>;
|
||||
@@ -14,4 +9,4 @@ export interface IPageViewRepository {
|
||||
findBySession(sessionId: string): Promise<PageView[]>;
|
||||
countByEntityId(entityType: EntityType, entityId: string, since?: Date): Promise<number>;
|
||||
countUniqueVisitors(entityType: EntityType, entityId: string, since?: Date): Promise<number>;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": false,
|
||||
"declaration": true,
|
||||
"declarationMap": false
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
@@ -8,7 +8,8 @@
|
||||
},
|
||||
"workspaces": [
|
||||
"core/*",
|
||||
"apps/*"
|
||||
"apps/*",
|
||||
"testing/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "echo 'Development server placeholder - to be configured'",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@gridpilot/testing-support",
|
||||
"name": "@gridpilot/testing",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
9
testing/tsconfig.json
Normal file
9
testing/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"declarationMap": false
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user