module creation

This commit is contained in:
2025-12-15 21:44:06 +01:00
parent b834f88bbd
commit 7c7267da72
88 changed files with 12119 additions and 4241 deletions

View File

@@ -0,0 +1,129 @@
import { IAuthRepository } from '@gridpilot/core/identity/domain/repositories/IAuthRepository';
import { IUserRepository, StoredUser } from '@gridpilot/core/identity/domain/repositories/IUserRepository';
import { IPasswordHashingService } from '@gridpilot/core/identity/domain/services/PasswordHashingService';
import { User } from '@gridpilot/core/identity/domain/entities/User';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
import { EmailAddress } from '@gridpilot/core/identity/domain/value-objects/EmailAddress';
import { randomUUID } from 'crypto';
export class InMemoryAuthRepository implements IAuthRepository {
constructor(
private readonly userRepository: IUserRepository,
private readonly passwordHashingService: IPasswordHashingService,
private readonly logger: ILogger,
) {}
async findByEmail(email: EmailAddress): Promise<User | null> {
this.logger.debug(`[InMemoryAuthRepository] Finding user by email: ${email.value}`);
const storedUser = await this.userRepository.findByEmail(email.value);
if (!storedUser) {
return null;
}
return User.fromStored(storedUser);
}
async save(user: User): Promise<void> {
this.logger.debug(`[InMemoryAuthRepository] Saving user with email: ${user.getEmail()}`);
const userId = user.getId().value;
const userEmail = user.getEmail() ?? '';
const userDisplayName = user.getDisplayName();
const userPasswordHash = user.getPasswordHash()?.value ?? '';
const storedUser: StoredUser = {
id: userId,
email: userEmail,
passwordHash: userPasswordHash,
displayName: userDisplayName,
salt: '', // In-memory doesn't use actual salt, but Stub has to provide.
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
createdAt: new Date(),
};
// Check if user exists to decide between create or update
const existingStoredUser = await this.userRepository.findById(userId);
if (existingStoredUser) {
// For update, ensure createdAt is preserved from existing user
const updatedStoredUser: StoredUser = {
...storedUser,
createdAt: existingStoredUser.createdAt,
};
await this.userRepository.update(updatedStoredUser);
} else {
await this.userRepository.create(storedUser);
}
}
async create(user: User, passwordPlain: string): Promise<User> {
this.logger.debug(`[InMemoryAuthRepository] Creating user with email: ${user.getEmail()}`);
const passwordHashValue = await this.passwordHashingService.hash(passwordPlain);
// Ensure email is not undefined before using
const userEmail = user.getEmail() ?? '';
const userDisplayName = user.getDisplayName();
const userId = user.getId().value;
const storedUser: StoredUser = {
id: userId,
email: userEmail,
passwordHash: passwordHashValue,
displayName: userDisplayName,
salt: '', // Salt is handled by PasswordHashingService, but StoredUser requires it.
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
createdAt: new Date(),
};
const createdStoredUser = await this.userRepository.create(storedUser);
return User.fromStored(createdStoredUser);
}
async update(user: User): Promise<User> {
this.logger.debug(`[InMemoryAuthRepository] Updating user with email: ${user.getEmail()}`);
// Ensure values are not undefined before using
const userId = user.getId().value;
const userEmail = user.getEmail() ?? '';
const userPasswordHash = user.getPasswordHash()?.value ?? '';
const userDisplayName = user.getDisplayName();
const storedUser: StoredUser = {
id: userId,
email: userEmail,
passwordHash: userPasswordHash,
displayName: userDisplayName,
salt: '', // Salt is handled by PasswordHashingService, but StoredUser requires it.
primaryDriverId: user.getPrimaryDriverId() ?? undefined,
createdAt: new Date(), // Assuming creation date is maintained or updated within UserRepository
};
const updatedStoredUser = await this.userRepository.update(storedUser);
return User.fromStored(updatedStoredUser);
}
async delete(userId: string): Promise<boolean> {
// Deletion needs to be implemented in InMemoryUserRepository and exposed via IUserRepository
// For now, it's not supported.
this.logger.warn(`[InMemoryAuthRepository] Delete operation not implemented for user: ${userId}`);
return false;
}
async userExists(email: string): Promise<boolean> {
this.logger.debug(`[InMemoryAuthRepository] Checking if user exists for email: ${email}`);
return this.userRepository.emailExists(email);
}
async verifyPassword(email: string, passwordPlain: string): Promise<User | null> {
this.logger.debug(`[InMemoryAuthRepository] Verifying password for user with email: ${email}`);
const user = await this.findByEmail(EmailAddress.create(email)); // Use EmailAddress value object
if (!user) {
this.logger.warn(`[InMemoryAuthRepository] User not found for email: ${email}`);
return null;
}
const isPasswordValid = await this.passwordHashingService.verify(
passwordPlain,
user.getPasswordHash()?.value ?? '', // Access value and provide fallback
);
if (!isPasswordValid) {
this.logger.warn(`[InMemoryAuthRepository] Invalid password for user with email: ${email}`);
return null;
}
return user;
}
}

View File

@@ -0,0 +1,22 @@
/**
* In-Memory Password Hashing Service
*
* Provides basic password hashing and comparison for demo/development purposes.
* NOT FOR PRODUCTION USE - uses a simple string reversal as "hashing".
*/
import type { IPasswordHashingService } from '@gridpilot/core/identity/domain/services/PasswordHashingService';
export class InMemoryPasswordHashingService implements IPasswordHashingService {
async hash(plain: string): Promise<string> {
// In a real application, use a robust hashing library like bcrypt or Argon2.
// For demo, we'll just reverse the password and add a salt-like prefix.
const salt = 'demo_salt_';
return Promise.resolve(salt + plain.split('').reverse().join(''));
}
async verify(plain: string, hash: string): Promise<boolean> {
const hashedPlain = await this.hash(plain);
return Promise.resolve(hashedPlain === hash);
}
}

View File

@@ -1,59 +1,47 @@
import { cookies } from 'next/headers';
import { randomUUID } from 'crypto';
import type { AuthenticatedUserDTO } from '../../application/dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '../../application/dto/AuthSessionDTO';
import type { IdentitySessionPort } from '../../application/ports/IdentitySessionPort';
/**
* Adapter: CookieIdentitySessionAdapter
*
* Manages user session using cookies. This is a placeholder implementation.
*/
const SESSION_COOKIE = 'gp_demo_session';
function parseCookieValue(raw: string | undefined): AuthSessionDTO | null {
if (!raw) return null;
try {
const parsed = JSON.parse(raw) as AuthSessionDTO;
if (!parsed.expiresAt || Date.now() > parsed.expiresAt) {
return null;
}
return parsed;
} catch {
return null;
}
}
function serializeSession(session: AuthSessionDTO): string {
return JSON.stringify(session);
}
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import type { AuthenticatedUserDTO } from '@gridpilot/core/identity/application/dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '@gridpilot/core/identity/application/dto/AuthSessionDTO';
import type { IdentitySessionPort } from '@gridpilot/core/identity/application/ports/IdentitySessionPort';
export class CookieIdentitySessionAdapter implements IdentitySessionPort {
private currentSession: AuthSessionDTO | null = null;
constructor(private readonly logger: ILogger) {
this.logger.info('CookieIdentitySessionAdapter initialized.');
// In a real application, you would load the session from a cookie here
// For demo, we'll start with no session.
}
async getCurrentSession(): Promise<AuthSessionDTO | null> {
const store = await cookies();
const raw = store.get(SESSION_COOKIE)?.value;
return parseCookieValue(raw);
this.logger.debug('[CookieIdentitySessionAdapter] Getting current session.');
return Promise.resolve(this.currentSession);
}
async createSession(user: AuthenticatedUserDTO): Promise<AuthSessionDTO> {
const now = Date.now();
const expiresAt = now + 24 * 60 * 60 * 1000;
const session: AuthSessionDTO = {
user,
issuedAt: now,
expiresAt,
token: randomUUID(),
this.logger.debug(`[CookieIdentitySessionAdapter] Creating session for user: ${user.id}`);
const newSession: AuthSessionDTO = {
user: user,
issuedAt: Date.now(),
expiresAt: Date.now() + 3600 * 1000, // 1 hour expiration
token: `mock-token-${user.id}-${Date.now()}`,
};
const store = await cookies();
store.set(SESSION_COOKIE, serializeSession(session), {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
});
return session;
this.currentSession = newSession;
// In a real app, you'd set a secure, HTTP-only cookie here.
this.logger.info(`[CookieIdentitySessionAdapter] Session created for user ${user.id}.`);
return Promise.resolve(newSession);
}
async clearSession(): Promise<void> {
const store = await cookies();
store.delete(SESSION_COOKIE);
this.logger.debug('[CookieIdentitySessionAdapter] Clearing session.');
this.currentSession = null;
// In a real app, you'd clear the session cookie here.
this.logger.info('[CookieIdentitySessionAdapter] Session cleared.');
return Promise.resolve();
}
}
}

View File

@@ -1,78 +1,85 @@
import type {
IAvatarGenerationRepository,
} from '@gridpilot/media';
import {
AvatarGenerationRequest,
type AvatarGenerationRequestProps,
} from '@gridpilot/media';
import { IAvatarGenerationRepository } from '@gridpilot/core/media/domain/repositories/IAvatarGenerationRepository';
import { AvatarGenerationRequest } from '@gridpilot/core/media/domain/entities/AvatarGenerationRequest';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
/**
* In-memory implementation of IAvatarGenerationRepository.
*
* For demo/development purposes. In production, this would use a database.
*/
export class InMemoryAvatarGenerationRepository implements IAvatarGenerationRepository {
private readonly requests = new Map<string, AvatarGenerationRequestProps>();
private readonly logger: ILogger;
private requests: Map<string, AvatarGenerationRequest> = new Map(); // Key: requestId
private userRequests: Map<string, AvatarGenerationRequest[]> = new Map(); // Key: userId
constructor(logger: ILogger) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialRequests: AvatarGenerationRequest[] = []) {
this.logger.info('InMemoryAvatarGenerationRepository initialized.');
for (const req of initialRequests) {
this.requests.set(req.id, req);
if (!this.userRequests.has(req.userId)) {
this.userRequests.set(req.userId, []);
}
this.userRequests.get(req.userId)?.push(req);
this.logger.debug(`Seeded avatar generation request: ${req.id} for user ${req.userId}.`);
}
}
async save(request: AvatarGenerationRequest): Promise<void> {
this.logger.debug(`Saving avatar generation request with ID: ${request.id}`);
this.requests.set(request.id, request.toProps());
this.logger.info(`Avatar generation request with ID: ${request.id} saved successfully.`);
this.logger.debug(`[InMemoryAvatarGenerationRepository] Saving avatar generation request: ${request.id} for user ${request.userId}.`);
this.requests.set(request.id, request);
if (!this.userRequests.has(request.userId)) {
this.userRequests.set(request.userId, []);
}
const existingRequests = this.userRequests.get(request.userId)!;
// Remove old version if exists and add new version (update-like behavior)
const index = existingRequests.findIndex(r => r.id === request.id);
if (index > -1) {
existingRequests[index] = request;
} else {
existingRequests.push(request);
}
this.logger.info(`Avatar generation request ${request.id} for user ${request.userId} saved successfully.`);
return Promise.resolve();
}
async findById(id: string): Promise<AvatarGenerationRequest | null> {
this.logger.debug(`Finding avatar generation request by ID: ${id}`);
const props = this.requests.get(id);
if (!props) {
this.logger.info(`Avatar generation request with ID: ${id} not found.`);
return null;
this.logger.debug(`[InMemoryAvatarGenerationRepository] Finding request by ID: ${id}`);
const request = this.requests.get(id) ?? null;
if (request) {
this.logger.info(`Found request by ID: ${id}.`);
} else {
this.logger.warn(`Request with ID ${id} not found.`);
}
this.logger.info(`Avatar generation request with ID: ${id} found.`);
return AvatarGenerationRequest.reconstitute(props);
return Promise.resolve(request);
}
async findByUserId(userId: string): Promise<AvatarGenerationRequest[]> {
this.logger.debug(`Finding avatar generation requests by user ID: ${userId}`);
const results: AvatarGenerationRequest[] = [];
for (const props of Array.from(this.requests.values())) {
if (props.userId === userId) {
results.push(AvatarGenerationRequest.reconstitute(props));
}
}
this.logger.info(`${results.length} avatar generation requests found for user ID: ${userId}.`);
return results.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
this.logger.debug(`[InMemoryAvatarGenerationRepository] Finding requests for user: ${userId}`);
const requests = this.userRequests.get(userId) ?? [];
this.logger.info(`Found ${requests.length} requests for user ${userId}.`);
return Promise.resolve(requests);
}
async findLatestByUserId(userId: string): Promise<AvatarGenerationRequest | null> {
this.logger.debug(`Finding latest avatar generation request for user ID: ${userId}`);
const userRequests = await this.findByUserId(userId);
if (userRequests.length === 0) {
this.logger.info(`No avatar generation requests found for user ID: ${userId}.`);
return null;
this.logger.debug(`[InMemoryAvatarGenerationRepository] Finding latest request for user: ${userId}`);
const requests = (this.userRequests.get(userId) ?? [])
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); // Sort by most recent
const latest = requests.length > 0 ? requests[0]! : null; // Use non-null assertion as `requests.length > 0` is checked
if (latest) {
this.logger.info(`Found latest request for user ${userId}: ${latest.id}.`);
} else {
this.logger.warn(`No latest request found for user: ${userId}.`);
}
const latest = userRequests[0];
if (!latest) {
this.logger.info(`No latest avatar generation request found for user ID: ${userId}.`);
return null;
}
this.logger.info(`Latest avatar generation request found for user ID: ${userId}, ID: ${latest.id}.`);
return latest;
return Promise.resolve(latest);
}
async delete(id: string): Promise<void> {
this.logger.debug(`Deleting avatar generation request with ID: ${id}`);
const deleted = this.requests.delete(id);
if (deleted) {
this.logger.info(`Avatar generation request with ID: ${id} deleted successfully.`);
this.logger.debug(`[InMemoryAvatarGenerationRepository] Deleting request with ID: ${id}.`);
const requestToDelete = this.requests.get(id);
if (requestToDelete) {
this.requests.delete(id);
const userRequests = this.userRequests.get(requestToDelete.userId);
if (userRequests) {
this.userRequests.set(requestToDelete.userId, userRequests.filter(req => req.id !== id));
}
this.logger.info(`Request ${id} deleted successfully.`);
} else {
this.logger.warn(`Attempted to delete non-existent avatar generation request with ID: ${id}.`);
this.logger.warn(`Request with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
}
}

View File

@@ -0,0 +1,19 @@
import type { FaceValidationPort, FaceValidationResult } from '@gridpilot/core/media/application/ports/FaceValidationPort';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryFaceValidationAdapter implements FaceValidationPort {
constructor(private readonly logger: ILogger) {
this.logger.info('InMemoryFaceValidationAdapter initialized.');
}
async validateFacePhoto(imageData: string | Buffer): Promise<FaceValidationResult> {
this.logger.debug('[InMemoryFaceValidationAdapter] Validating face photo (mock).');
// Simulate a successful validation for any input for demo purposes
return Promise.resolve({
isValid: true,
hasFace: true,
faceCount: 1,
confidence: 0.95,
});
}
}

View File

@@ -0,0 +1,28 @@
import type { IImageServicePort } from '@gridpilot/racing/application/ports/IImageServicePort';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryImageServiceAdapter implements IImageServicePort {
constructor(private readonly logger: ILogger) {
this.logger.info('InMemoryImageServiceAdapter initialized.');
}
getDriverAvatar(driverId: string): string {
this.logger.debug(`[InMemoryImageServiceAdapter] Getting avatar for driver: ${driverId}`);
return `https://cdn.example.com/avatars/${driverId}.png`; // Mock URL
}
getTeamLogo(teamId: string): string {
this.logger.debug(`[InMemoryImageServiceAdapter] Getting logo for team: ${teamId}`);
return `https://cdn.example.com/logos/team-${teamId}.png`; // Mock URL
}
getLeagueCover(leagueId: string): string {
this.logger.debug(`[InMemoryImageServiceAdapter] Getting cover for league: ${leagueId}`);
return `https://cdn.example.com/covers/league-${leagueId}.png`; // Mock URL
}
getLeagueLogo(leagueId: string): string {
this.logger.debug(`[InMemoryImageServiceAdapter] Getting logo for league: ${leagueId}`);
return `https://cdn.example.com/logos/league-${leagueId}.png`; // Mock URL
}
}

View File

@@ -1,84 +1,54 @@
/**
* In-Memory Implementation: InMemoryNotificationPreferenceRepository
*
* Provides an in-memory storage implementation for notification preferences.
*/
import { NotificationPreference } from '../../domain/entities/NotificationPreference';
import type { INotificationPreferenceRepository } from '../../domain/repositories/INotificationPreferenceRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { INotificationPreferenceRepository } from '@gridpilot/core/notifications/domain/repositories/INotificationPreferenceRepository';
import { NotificationPreference } from '@gridpilot/core/notifications/domain/entities/NotificationPreference';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryNotificationPreferenceRepository implements INotificationPreferenceRepository {
private preferences: Map<string, NotificationPreference> = new Map();
private readonly logger: ILogger;
constructor(logger: ILogger, initialPreferences: NotificationPreference[] = []) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialPreferences: NotificationPreference[] = []) {
this.logger.info('InMemoryNotificationPreferenceRepository initialized.');
initialPreferences.forEach(pref => {
this.preferences.set(pref.driverId, pref);
this.logger.debug(`Seeded preference for driver: ${pref.driverId}`);
});
for (const pref of initialPreferences) {
this.preferences.set(pref.id, pref);
this.logger.debug(`Seeded preference: ${pref.id}.`);
}
}
async findByDriverId(driverId: string): Promise<NotificationPreference | null> {
this.logger.debug(`Finding notification preference for driver: ${driverId}`);
try {
const preference = this.preferences.get(driverId) || null;
if (preference) {
this.logger.info(`Found preference for driver: ${driverId}`);
} else {
this.logger.warn(`Preference not found for driver: ${driverId}`);
}
return preference;
} catch (error) {
this.logger.error(`Error finding preference for driver ${driverId}:`, error);
throw error;
this.logger.debug(`[InMemoryNotificationPreferenceRepository] Finding preferences for driver: ${driverId}`);
const preference = this.preferences.get(driverId) ?? null;
if (preference) {
this.logger.info(`Found preferences for driver: ${driverId}.`);
} else {
this.logger.warn(`No preferences found for driver: ${driverId}.`);
}
return Promise.resolve(preference);
}
async save(preference: NotificationPreference): Promise<void> {
this.logger.debug(`Saving notification preference for driver: ${preference.driverId}`);
try {
this.preferences.set(preference.driverId, preference);
this.logger.info(`Preference for driver ${preference.driverId} saved successfully.`);
} catch (error) {
this.logger.error(`Error saving preference for driver ${preference.driverId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryNotificationPreferenceRepository] Saving preferences for driver: ${preference.driverId}.`);
this.preferences.set(preference.id, preference);
this.logger.info(`Preferences for driver ${preference.driverId} saved successfully.`);
return Promise.resolve();
}
async delete(driverId: string): Promise<void> {
this.logger.debug(`Deleting notification preference for driver: ${driverId}`);
try {
if (this.preferences.delete(driverId)) {
this.logger.info(`Preference for driver ${driverId} deleted successfully.`);
} else {
this.logger.warn(`Preference for driver ${driverId} not found for deletion.`);
}
} catch (error) {
this.logger.error(`Error deleting preference for driver ${driverId}:`, error);
throw error;
this.logger.debug(`[InMemoryNotificationPreferenceRepository] Deleting preferences for driver: ${driverId}.`);
if (this.preferences.delete(driverId)) {
this.logger.info(`Preferences for driver ${driverId} deleted successfully.`);
} else {
this.logger.warn(`No preferences found for driver ${driverId} to delete.`);
}
return Promise.resolve();
}
async getOrCreateDefault(driverId: string): Promise<NotificationPreference> {
this.logger.debug(`Getting or creating default notification preference for driver: ${driverId}`);
try {
const existing = this.preferences.get(driverId);
if (existing) {
this.logger.debug(`Existing preference found for driver: ${driverId}.`);
return existing;
}
this.logger.info(`Creating default preference for driver: ${driverId}.`);
const defaultPreference = NotificationPreference.createDefault(driverId);
this.preferences.set(driverId, defaultPreference);
this.logger.info(`Default preference created and saved for driver: ${driverId}.`);
return defaultPreference;
} catch (error) {
this.logger.error(`Error getting or creating default preference for driver ${driverId}:`, error);
throw error;
this.logger.debug(`[InMemoryNotificationPreferenceRepository] Getting or creating default preferences for driver: ${driverId}.`);
let preference = await this.findByDriverId(driverId);
if (!preference) {
this.logger.info(`Creating default preferences for new driver: ${driverId}.`);
preference = NotificationPreference.createDefault(driverId);
await this.save(preference);
}
return preference;
}
}
}

View File

@@ -1,163 +1,99 @@
/**
* Infrastructure Adapter: InMemoryDriverRepository
*
* In-memory implementation of IDriverRepository.
* Stores data in Map structure with UUID generation.
*/
import { v4 as uuidv4 } from 'uuid';
import { IDriverRepository } from '@gridpilot/racing/domain/repositories/IDriverRepository';
import { Driver } from '@gridpilot/racing/domain/entities/Driver';
import type { IDriverRepository } from '@gridpilot/racing/domain/repositories/IDriverRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryDriverRepository implements IDriverRepository {
private drivers: Map<string, Driver>;
private readonly logger: ILogger;
private drivers: Map<string, Driver> = new Map();
private iracingIdIndex: Map<string, string> = new Map(); // iracingId -> driverId
constructor(logger: ILogger, seedData?: Driver[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialDrivers: Driver[] = []) {
this.logger.info('InMemoryDriverRepository initialized.');
this.drivers = new Map();
if (seedData) {
seedData.forEach(driver => {
this.drivers.set(driver.id, driver);
this.logger.debug(`Seeded driver: ${driver.id}.`);
});
for (const driver of initialDrivers) {
this.drivers.set(driver.id, driver);
this.iracingIdIndex.set(driver.iracingId, driver.id);
this.logger.debug(`Seeded driver: ${driver.id} (iRacing ID: ${driver.iracingId}).`);
}
}
async findById(id: string): Promise<Driver | null> {
this.logger.debug(`Finding driver by id: ${id}`);
try {
const driver = this.drivers.get(id) ?? null;
if (driver) {
this.logger.info(`Found driver: ${id}.`);
} else {
this.logger.warn(`Driver with id ${id} not found.`);
}
return driver;
} catch (error) {
this.logger.error(`Error finding driver by id ${id}:`, error);
throw error;
this.logger.debug(`[InMemoryDriverRepository] Finding driver by ID: ${id}`);
const driver = this.drivers.get(id) ?? null;
if (driver) {
this.logger.info(`Found driver by ID: ${id}.`);
} else {
this.logger.warn(`Driver with ID ${id} not found.`);
}
return Promise.resolve(driver);
}
async findByIRacingId(iracingId: string): Promise<Driver | null> {
this.logger.debug(`Finding driver by iRacing id: ${iracingId}`);
try {
const driver = Array.from(this.drivers.values()).find(
d => d.iracingId === iracingId
);
if (driver) {
this.logger.info(`Found driver with iRacing id: ${iracingId}.`);
} else {
this.logger.warn(`Driver with iRacing id ${iracingId} not found.`);
}
return driver ?? null;
} catch (error) {
this.logger.error(`Error finding driver by iRacing id ${iracingId}:`, error);
throw error;
this.logger.debug(`[InMemoryDriverRepository] Finding driver by iRacing ID: ${iracingId}`);
const driverId = this.iracingIdIndex.get(iracingId);
if (!driverId) {
this.logger.warn(`Driver with iRacing ID ${iracingId} not found.`);
return Promise.resolve(null);
}
return this.findById(driverId);
}
async findAll(): Promise<Driver[]> {
this.logger.debug('Finding all drivers.');
try {
const drivers = Array.from(this.drivers.values());
this.logger.info(`Found ${drivers.length} drivers.`);
return drivers;
} catch (error) {
this.logger.error('Error finding all drivers:', error);
throw error;
}
this.logger.debug('[InMemoryDriverRepository] Finding all drivers.');
return Promise.resolve(Array.from(this.drivers.values()));
}
async create(driver: Driver): Promise<Driver> {
this.logger.debug(`Creating driver: ${driver.id}`);
try {
if (await this.exists(driver.id)) {
this.logger.warn(`Driver with ID ${driver.id} already exists.`);
throw new Error(`Driver with ID ${driver.id} already exists`);
}
if (await this.existsByIRacingId(driver.iracingId)) {
this.logger.warn(`Driver with iRacing ID ${driver.iracingId} already exists.`);
throw new Error(`Driver with iRacing ID ${driver.iracingId} already exists`);
}
this.drivers.set(driver.id, driver);
this.logger.info(`Driver ${driver.id} created successfully.`);
return driver;
} catch (error) {
this.logger.error(`Error creating driver ${driver.id}:`, error);
throw error;
this.logger.debug(`[InMemoryDriverRepository] Creating driver: ${driver.id} (iRacing ID: ${driver.iracingId})`);
if (this.drivers.has(driver.id)) {
this.logger.warn(`Driver with ID ${driver.id} already exists.`);
throw new Error('Driver already exists');
}
if (this.iracingIdIndex.has(driver.iracingId)) {
this.logger.warn(`Driver with iRacing ID ${driver.iracingId} already exists.`);
throw new Error('iRacing ID already registered');
}
this.drivers.set(driver.id, driver);
this.iracingIdIndex.set(driver.iracingId, driver.id);
this.logger.info(`Driver ${driver.id} created successfully.`);
return Promise.resolve(driver);
}
async update(driver: Driver): Promise<Driver> {
this.logger.debug(`Updating driver: ${driver.id}`);
try {
if (!await this.exists(driver.id)) {
this.logger.warn(`Driver with ID ${driver.id} not found for update.`);
throw new Error(`Driver with ID ${driver.id} not found`);
}
this.drivers.set(driver.id, driver);
this.logger.info(`Driver ${driver.id} updated successfully.`);
return driver;
} catch (error) {
this.logger.error(`Error updating driver ${driver.id}:`, error);
throw error;
this.logger.debug(`[InMemoryDriverRepository] Updating driver: ${driver.id} (iRacing ID: ${driver.iracingId})`);
if (!this.drivers.has(driver.id)) {
this.logger.warn(`Driver with ID ${driver.id} not found for update.`);
throw new Error('Driver not found');
}
this.drivers.set(driver.id, driver);
// Re-index iRacing ID if it changed
const existingDriver = this.drivers.get(driver.id);
if (existingDriver && existingDriver.iracingId !== driver.iracingId) {
this.iracingIdIndex.delete(existingDriver.iracingId);
this.iracingIdIndex.set(driver.iracingId, driver.id);
}
this.logger.info(`Driver ${driver.id} updated successfully.`);
return Promise.resolve(driver);
}
async delete(id: string): Promise<void> {
this.logger.debug(`Deleting driver: ${id}`);
try {
if (!await this.exists(id)) {
this.logger.warn(`Driver with ID ${id} not found for deletion.`);
throw new Error(`Driver with ID ${id} not found`);
}
this.logger.debug(`[InMemoryDriverRepository] Deleting driver with ID: ${id}`);
const driver = this.drivers.get(id);
if (driver) {
this.drivers.delete(id);
this.iracingIdIndex.delete(driver.iracingId);
this.logger.info(`Driver ${id} deleted successfully.`);
} catch (error) {
this.logger.error(`Error deleting driver ${id}:`, error);
throw error;
} else {
this.logger.warn(`Driver with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
async exists(id: string): Promise<boolean> {
this.logger.debug(`Checking existence of driver with id: ${id}`);
try {
const exists = this.drivers.has(id);
this.logger.debug(`Driver ${id} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of driver with id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemoryDriverRepository] Checking existence of driver with ID: ${id}`);
return Promise.resolve(this.drivers.has(id));
}
async existsByIRacingId(iracingId: string): Promise<boolean> {
this.logger.debug(`Checking existence of driver with iRacing id: ${iracingId}`);
try {
const exists = Array.from(this.drivers.values()).some(
d => d.iracingId === iracingId
);
this.logger.debug(`Driver with iRacing id ${iracingId} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of driver with iRacing id ${iracingId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryDriverRepository] Checking existence of driver with iRacing ID: ${iracingId}`);
return Promise.resolve(this.iracingIdIndex.has(iracingId));
}
/**
* Utility method to generate a new UUID
*/
static generateId(): string {
return uuidv4();
}
}
}

View File

@@ -1,97 +1,31 @@
/**
* Infrastructure Adapter: InMemoryGameRepository
*
* In-memory implementation of IGameRepository.
*/
import { Game } from '../../domain/entities/Game';
import type { IGameRepository } from '../../domain/repositories/IGameRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { IGameRepository } from '@gridpilot/racing/domain/repositories/IGameRepository';
import { Game } from '@gridpilot/racing/domain/entities/Game';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryGameRepository implements IGameRepository {
private games: Map<string, Game>;
private readonly logger: ILogger;
private games: Map<string, Game> = new Map();
constructor(logger: ILogger, seedData?: Game[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialGames: Game[] = []) {
this.logger.info('InMemoryGameRepository initialized.');
this.games = new Map();
if (seedData) {
seedData.forEach(game => {
this.games.set(game.id, game);
this.logger.debug(`Seeded game: ${game.id}.`);
});
} else {
// Default seed data for common sim racing games
const defaultGames = [
Game.create({ id: 'iracing', name: 'iRacing' }),
Game.create({ id: 'acc', name: 'Assetto Corsa Competizione' }),
Game.create({ id: 'ac', name: 'Assetto Corsa' }),
Game.create({ id: 'rf2', name: 'rFactor 2' }),
Game.create({ id: 'ams2', name: 'Automobilista 2' }),
Game.create({ id: 'lmu', name: 'Le Mans Ultimate' }),
];
defaultGames.forEach(game => {
this.games.set(game.id, game);
this.logger.debug(`Seeded default game: ${game.id}.`);
});
for (const game of initialGames) {
this.games.set(game.id, game);
this.logger.debug(`Seeded game: ${game.id} (${game.name}).`);
}
}
async findById(id: string): Promise<Game | null> {
this.logger.debug(`Finding game by id: ${id}`);
try {
const game = this.games.get(id) ?? null;
if (game) {
this.logger.info(`Found game: ${id}.`);
} else {
this.logger.warn(`Game with id ${id} not found.`);
}
return game;
} catch (error) {
this.logger.error(`Error finding game by id ${id}:`, error);
throw error;
this.logger.debug(`[InMemoryGameRepository] Finding game by ID: ${id}`);
const game = this.games.get(id) ?? null;
if (game) {
this.logger.info(`Found game by ID: ${id}.`);
} else {
this.logger.warn(`Game with ID ${id} not found.`);
}
return Promise.resolve(game);
}
async findAll(): Promise<Game[]> {
this.logger.debug('Finding all games.');
try {
const games = Array.from(this.games.values()).sort((a, b) => a.name.localeCompare(b.name));
this.logger.info(`Found ${games.length} games.`);
return games;
} catch (error) {
this.logger.error('Error finding all games:', error);
throw error;
}
this.logger.debug('[InMemoryGameRepository] Finding all games.');
return Promise.resolve(Array.from(this.games.values()));
}
/**
* Utility method to add a game
*/
async create(game: Game): Promise<Game> {
this.logger.debug(`Creating game: ${game.id}`);
try {
if (this.games.has(game.id)) {
this.logger.warn(`Game with ID ${game.id} already exists.`);
throw new Error(`Game with ID ${game.id} already exists`);
}
this.games.set(game.id, game);
this.logger.info(`Game ${game.id} created successfully.`);
return game;
} catch (error) {
this.logger.error(`Error creating game ${game.id}:`, error);
throw error;
}
}
/**
* Test helper to clear data
*/
clear(): void {
this.logger.debug('Clearing all games.');
this.games.clear();
this.logger.info('All games cleared.');
}
}
}

View File

@@ -1,179 +1,102 @@
/**
* Infrastructure Adapter: InMemoryLeagueMembershipRepository
*
* In-memory implementation of ILeagueMembershipRepository.
* Stores memberships and join requests in maps keyed by league.
*/
import type {
LeagueMembership,
JoinRequest,
} from '@gridpilot/racing/domain/entities/LeagueMembership';
import type { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ILeagueMembershipRepository } from '@gridpilot/racing/domain/repositories/ILeagueMembershipRepository';
import { LeagueMembership, JoinRequest } from '@gridpilot/racing/domain/entities/LeagueMembership';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryLeagueMembershipRepository implements ILeagueMembershipRepository {
private membershipsByLeague: Map<string, LeagueMembership[]>;
private joinRequestsByLeague: Map<string, JoinRequest[]>;
private readonly logger: ILogger;
private memberships: Map<string, LeagueMembership> = new Map(); // Key: `${leagueId}:${driverId}`
private joinRequests: Map<string, JoinRequest> = new Map(); // Key: requestId
constructor(logger: ILogger, seedMemberships?: LeagueMembership[], seedJoinRequests?: JoinRequest[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialMemberships: LeagueMembership[] = [], initialJoinRequests: JoinRequest[] = []) {
this.logger.info('InMemoryLeagueMembershipRepository initialized.');
this.membershipsByLeague = new Map();
this.joinRequestsByLeague = new Map();
if (seedMemberships) {
seedMemberships.forEach((membership) => {
const list = this.membershipsByLeague.get(membership.leagueId) ?? [];
list.push(membership);
this.membershipsByLeague.set(membership.leagueId, list);
this.logger.debug(`Seeded membership for league ${membership.leagueId}, driver ${membership.driverId}.`);
});
for (const membership of initialMemberships) {
this.memberships.set(`${membership.leagueId}:${membership.driverId}`, membership);
this.logger.debug(`Seeded membership: ${membership.id}.`);
}
if (seedJoinRequests) {
seedJoinRequests.forEach((request) => {
const list = this.joinRequestsByLeague.get(request.leagueId) ?? [];
list.push(request);
this.joinRequestsByLeague.set(request.leagueId, list);
this.logger.debug(`Seeded join request for league ${request.leagueId}, driver ${request.driverId}.`);
});
for (const req of initialJoinRequests) {
this.joinRequests.set(req.id, req);
this.logger.debug(`Seeded join request: ${req.id}.`);
}
}
async getMembership(leagueId: string, driverId: string): Promise<LeagueMembership | null> {
this.logger.debug(`Getting membership for league: ${leagueId}, driver: ${driverId}`);
try {
const list = this.membershipsByLeague.get(leagueId);
if (!list) {
this.logger.warn(`No membership list found for league: ${leagueId}.`);
return null;
}
const membership = list.find((m) => m.driverId === driverId) ?? null;
if (membership) {
this.logger.info(`Found membership for league: ${leagueId}, driver: ${driverId}.`);
} else {
this.logger.warn(`Membership not found for league: ${leagueId}, driver: ${driverId}.`);
}
return membership;
} catch (error) {
this.logger.error(`Error getting membership for league ${leagueId}, driver ${driverId}:`, error);
throw error;
this.logger.debug(`[InMemoryLeagueMembershipRepository] Getting membership for league ${leagueId}, driver ${driverId}.`);
const key = `${leagueId}:${driverId}`;
const membership = this.memberships.get(key) ?? null;
if (membership) {
this.logger.info(`Found membership for league ${leagueId}, driver ${driverId}.`);
} else {
this.logger.warn(`No membership found for league ${leagueId}, driver ${driverId}.`);
}
return Promise.resolve(membership);
}
async findActiveByLeagueIdAndDriverId(leagueId: string, driverId: string): Promise<LeagueMembership | null> {
this.logger.debug(`[InMemoryLeagueMembershipRepository] Finding active membership for league ${leagueId}, driver ${driverId}.`);
const membership = await this.getMembership(leagueId, driverId);
return Promise.resolve(membership && membership.status === 'active' ? membership : null);
}
async findAllByLeagueId(leagueId: string): Promise<LeagueMembership[]> {
this.logger.debug(`[InMemoryLeagueMembershipRepository] Finding all memberships for league ${leagueId}.`);
const filteredMemberships = Array.from(this.memberships.values()).filter(mem => mem.leagueId === leagueId);
this.logger.info(`Found ${filteredMemberships.length} memberships for league ${leagueId}.`);
return Promise.resolve(filteredMemberships);
}
async findAllByDriverId(driverId: string): Promise<LeagueMembership[]> {
this.logger.debug(`[InMemoryLeagueMembershipRepository] Finding all memberships for driver ${driverId}.`);
const memberships = Array.from(this.memberships.values()).filter(mem => mem.driverId === driverId);
this.logger.info(`Found ${memberships.length} memberships for driver ${driverId}.`);
return Promise.resolve(memberships);
}
async getLeagueMembers(leagueId: string): Promise<LeagueMembership[]> {
this.logger.debug(`Getting league members for league: ${leagueId}`);
try {
const members = [...(this.membershipsByLeague.get(leagueId) ?? [])];
this.logger.info(`Found ${members.length} members for league: ${leagueId}.`);
return members;
} catch (error) {
this.logger.error(`Error getting league members for league ${leagueId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryLeagueMembershipRepository] Getting active members for league ${leagueId}.`);
const members = Array.from(this.memberships.values()).filter(mem => mem.leagueId === leagueId && mem.status === 'active');
this.logger.info(`Found ${members.length} active members for league ${leagueId}.`);
return Promise.resolve(members);
}
async getJoinRequests(leagueId: string): Promise<JoinRequest[]> {
this.logger.debug(`Getting join requests for league: ${leagueId}`);
try {
const requests = [...(this.joinRequestsByLeague.get(leagueId) ?? [])];
this.logger.info(`Found ${requests.length} join requests for league: ${leagueId}.`);
return requests;
} catch (error) {
this.logger.error(`Error getting join requests for league ${leagueId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryLeagueMembershipRepository] Getting join requests for league ${leagueId}.`);
const requests = Array.from(this.joinRequests.values()).filter(req => req.leagueId === leagueId);
this.logger.info(`Found ${requests.length} join requests for league ${leagueId}.`);
return Promise.resolve(requests);
}
async saveMembership(membership: LeagueMembership): Promise<LeagueMembership> {
this.logger.debug(`Saving membership for league: ${membership.leagueId}, driver: ${membership.driverId}`);
try {
const list = this.membershipsByLeague.get(membership.leagueId) ?? [];
const existingIndex = list.findIndex(
(m) => m.leagueId === membership.leagueId && m.driverId === membership.driverId,
);
if (existingIndex >= 0) {
list[existingIndex] = membership;
this.logger.info(`Updated existing membership for league: ${membership.leagueId}, driver: ${membership.driverId}.`);
} else {
list.push(membership);
this.logger.info(`Created new membership for league: ${membership.leagueId}, driver: ${membership.driverId}.`);
}
this.membershipsByLeague.set(membership.leagueId, list);
return membership;
} catch (error) {
this.logger.error(`Error saving membership for league ${membership.leagueId}, driver ${membership.driverId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryLeagueMembershipRepository] Saving membership for ${membership.id}.`);
const key = `${membership.leagueId}:${membership.driverId}`;
this.memberships.set(key, membership);
this.logger.info(`Membership ${membership.id} saved successfully.`);
return Promise.resolve(membership);
}
async removeMembership(leagueId: string, driverId: string): Promise<void> {
this.logger.debug(`Removing membership for league: ${leagueId}, driver: ${driverId}`);
try {
const list = this.membershipsByLeague.get(leagueId);
if (!list) {
this.logger.warn(`No membership list found for league: ${leagueId}. Cannot remove.`);
return;
}
const next = list.filter((m) => m.driverId !== driverId);
if (next.length < list.length) {
this.membershipsByLeague.set(leagueId, next);
this.logger.info(`Removed membership for league: ${leagueId}, driver: ${driverId}.`);
} else {
this.logger.warn(`Membership not found for league: ${leagueId}, driver: ${driverId}. Cannot remove.`);
}
} catch (error) {
this.logger.error(`Error removing membership for league ${leagueId}, driver ${driverId}:`, error);
throw error;
this.logger.debug(`[InMemoryLeagueMembershipRepository] Removing membership for league ${leagueId}, driver ${driverId}.`);
const key = `${leagueId}:${driverId}`;
if (this.memberships.delete(key)) {
this.logger.info(`Membership for league ${leagueId}, driver ${driverId} removed successfully.`);
} else {
this.logger.warn(`Membership for league ${leagueId}, driver ${driverId} not found for removal.`);
}
return Promise.resolve();
}
async saveJoinRequest(request: JoinRequest): Promise<JoinRequest> {
this.logger.debug(`Saving join request for league: ${request.leagueId}, driver: ${request.driverId}, id: ${request.id}`);
try {
const list = this.joinRequestsByLeague.get(request.leagueId) ?? [];
const existingIndex = list.findIndex((r) => r.id === request.id);
if (existingIndex >= 0) {
list[existingIndex] = request;
this.logger.info(`Updated existing join request: ${request.id}.`);
} else {
list.push(request);
this.logger.info(`Created new join request: ${request.id}.`);
}
this.joinRequestsByLeague.set(request.leagueId, list);
return request;
} catch (error) {
this.logger.error(`Error saving join request ${request.id}:`, error);
throw error;
}
this.logger.debug(`[InMemoryLeagueMembershipRepository] Saving join request for ${request.id}.`);
this.joinRequests.set(request.id, request);
this.logger.info(`Join request ${request.id} saved successfully.`);
return Promise.resolve(request);
}
async removeJoinRequest(requestId: string): Promise<void> {
this.logger.debug(`Removing join request with ID: ${requestId}`);
try {
let removed = false;
for (const [leagueId, requests] of this.joinRequestsByLeague.entries()) {
const next = requests.filter((r) => r.id !== requestId);
if (next.length !== requests.length) {
this.joinRequestsByLeague.set(leagueId, next);
removed = true;
this.logger.info(`Removed join request ${requestId} from league ${leagueId}.`);
break;
}
}
if (!removed) {
this.logger.warn(`Join request with ID ${requestId} not found for removal.`);
}
} catch (error) {
this.logger.error(`Error removing join request ${requestId}:`, error);
throw error;
this.logger.debug(`[InMemoryLeagueMembershipRepository] Removing join request: ${requestId}.`);
if (this.joinRequests.delete(requestId)) {
this.logger.info(`Join request ${requestId} removed successfully.`);
} else {
this.logger.warn(`Join request ${requestId} not found for removal.`);
}
return Promise.resolve();
}
}
}

View File

@@ -1,155 +1,84 @@
/**
* Infrastructure Adapter: InMemoryLeagueRepository
*
* In-memory implementation of ILeagueRepository.
* Stores data in Map structure with UUID generation.
*/
import { v4 as uuidv4 } from 'uuid';
import { League } from '@gridpilot/racing/domain/entities/League';
import type { ILeagueRepository } from '@gridpilot/racing/domain/repositories/ILeagueRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ILeagueRepository } from '@gridpilot/core/racing/domain/repositories/ILeagueRepository';
import { League } from '@gridpilot/core/racing/domain/entities/League';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryLeagueRepository implements ILeagueRepository {
private leagues: Map<string, League>;
private readonly logger: ILogger;
private leagues: Map<string, League> = new Map();
constructor(logger: ILogger, seedData?: League[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialLeagues: League[] = []) {
this.logger.info('InMemoryLeagueRepository initialized.');
this.leagues = new Map();
if (seedData) {
seedData.forEach(league => {
this.leagues.set(league.id, league);
this.logger.debug(`Seeded league: ${league.id}.`);
});
for (const league of initialLeagues) {
this.leagues.set(league.id, league);
this.logger.debug(`Seeded league: ${league.id} (${league.name}).`);
}
}
async findById(id: string): Promise<League | null> {
this.logger.debug(`Finding league by id: ${id}`);
try {
const league = this.leagues.get(id) ?? null;
if (league) {
this.logger.info(`Found league: ${id}.`);
} else {
this.logger.warn(`League with id ${id} not found.`);
}
return league;
} catch (error) {
this.logger.error(`Error finding league by id ${id}:`, error);
throw error;
}
}
async findAll(): Promise<League[]> {
this.logger.debug('Finding all leagues.');
try {
const leagues = Array.from(this.leagues.values());
this.logger.info(`Found ${leagues.length} leagues.`);
return leagues;
} catch (error) {
this.logger.error('Error finding all leagues:', error);
throw error;
this.logger.debug(`[InMemoryLeagueRepository] Finding league by ID: ${id}`);
const league = this.leagues.get(id) ?? null;
if (league) {
this.logger.info(`Found league by ID: ${id}.`);
} else {
this.logger.warn(`League with ID ${id} not found.`);
}
return Promise.resolve(league);
}
async findByOwnerId(ownerId: string): Promise<League[]> {
this.logger.debug(`Finding leagues by owner id: ${ownerId}`);
try {
const leagues = Array.from(this.leagues.values()).filter(
league => league.ownerId === ownerId
);
this.logger.info(`Found ${leagues.length} leagues for owner id: ${ownerId}.`);
return leagues;
} catch (error) {
this.logger.error(`Error finding leagues by owner id ${ownerId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryLeagueRepository] Finding leagues by owner ID: ${ownerId}`);
const ownedLeagues = Array.from(this.leagues.values()).filter(league => league.ownerId === ownerId);
this.logger.info(`Found ${ownedLeagues.length} leagues for owner ID: ${ownerId}.`);
return Promise.resolve(ownedLeagues);
}
async searchByName(name: string): Promise<League[]> {
this.logger.debug(`[InMemoryLeagueRepository] Searching leagues by name: ${name}`);
const matchingLeagues = Array.from(this.leagues.values()).filter(league =>
league.name.toLowerCase().includes(name.toLowerCase()),
);
this.logger.info(`Found ${matchingLeagues.length} matching leagues for name search: ${name}.`);
return Promise.resolve(matchingLeagues);
}
async findAll(): Promise<League[]> {
this.logger.debug('[InMemoryLeagueRepository] Finding all leagues.');
return Promise.resolve(Array.from(this.leagues.values()));
}
async create(league: League): Promise<League> {
this.logger.debug(`Creating league: ${league.id}`);
try {
if (await this.exists(league.id)) {
this.logger.warn(`League with ID ${league.id} already exists.`);
throw new Error(`League with ID ${league.id} already exists`);
}
this.leagues.set(league.id, league);
this.logger.info(`League ${league.id} created successfully.`);
return league;
} catch (error) {
this.logger.error(`Error creating league ${league.id}:`, error);
throw error;
this.logger.debug(`[InMemoryLeagueRepository] Creating league: ${league.id} (${league.name})`);
if (this.leagues.has(league.id)) {
this.logger.warn(`League with ID ${league.id} already exists.`);
throw new Error('League already exists');
}
this.leagues.set(league.id, league);
this.logger.info(`League ${league.id} created successfully.`);
return Promise.resolve(league);
}
async update(league: League): Promise<League> {
this.logger.debug(`Updating league: ${league.id}`);
try {
if (!await this.exists(league.id)) {
this.logger.warn(`League with ID ${league.id} not found for update.`);
throw new Error(`League with ID ${league.id} not found`);
}
this.leagues.set(league.id, league);
this.logger.info(`League ${league.id} updated successfully.`);
return league;
} catch (error) {
this.logger.error(`Error updating league ${league.id}:`, error);
throw error;
this.logger.debug(`[InMemoryLeagueRepository] Updating league: ${league.id} (${league.name})`);
if (!this.leagues.has(league.id)) {
this.logger.warn(`League with ID ${league.id} not found for update.`);
throw new Error('League not found');
}
this.leagues.set(league.id, league);
this.logger.info(`League ${league.id} updated successfully.`);
return Promise.resolve(league);
}
async delete(id: string): Promise<void> {
this.logger.debug(`Deleting league: ${id}`);
try {
if (!await this.exists(id)) {
this.logger.warn(`League with ID ${id} not found for deletion.`);
throw new Error(`League with ID ${id} not found`);
}
this.leagues.delete(id);
this.logger.debug(`[InMemoryLeagueRepository] Deleting league with ID: ${id}`);
if (this.leagues.delete(id)) {
this.logger.info(`League ${id} deleted successfully.`);
} catch (error) {
this.logger.error(`Error deleting league ${id}:`, error);
throw error;
} else {
this.logger.warn(`League with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
async exists(id: string): Promise<boolean> {
this.logger.debug(`Checking existence of league with id: ${id}`);
try {
const exists = this.leagues.has(id);
this.logger.debug(`League ${id} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of league with id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemoryLeagueRepository] Checking existence of league with ID: ${id}`);
return Promise.resolve(this.leagues.has(id));
}
async searchByName(query: string): Promise<League[]> {
this.logger.debug(`Searching leagues by name query: ${query}`);
try {
const normalizedQuery = query.toLowerCase();
const leagues = Array.from(this.leagues.values()).filter(league =>
league.name.toLowerCase().includes(normalizedQuery)
);
this.logger.info(`Found ${leagues.length} leagues matching search query: ${query}.`);
return leagues;
} catch (error) {
this.logger.error(`Error searching leagues by name query ${query}:`, error);
throw error;
}
}
/**
* Utility method to generate a new UUID
*/
static generateId(): string {
return uuidv4();
}
}
}

View File

@@ -0,0 +1,33 @@
import { ILeagueScoringConfigRepository } from '@gridpilot/racing/domain/repositories/ILeagueScoringConfigRepository';
import { LeagueScoringConfig } from '@gridpilot/racing/domain/entities/LeagueScoringConfig';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryLeagueScoringConfigRepository implements ILeagueScoringConfigRepository {
private configs: Map<string, LeagueScoringConfig> = new Map(); // Key: seasonId
constructor(private readonly logger: ILogger, initialConfigs: LeagueScoringConfig[] = []) {
this.logger.info('InMemoryLeagueScoringConfigRepository initialized.');
for (const config of initialConfigs) {
this.configs.set(config.seasonId, config);
this.logger.debug(`Seeded league scoring config for season: ${config.seasonId}.`);
}
}
async findBySeasonId(seasonId: string): Promise<LeagueScoringConfig | null> {
this.logger.debug(`[InMemoryLeagueScoringConfigRepository] Finding config for season: ${seasonId}.`);
const config = this.configs.get(seasonId) ?? null;
if (config) {
this.logger.info(`Found config for season: ${seasonId}.`);
} else {
this.logger.warn(`No config found for season: ${seasonId}.`);
}
return Promise.resolve(config);
}
async save(config: LeagueScoringConfig): Promise<LeagueScoringConfig> {
this.logger.debug(`[InMemoryLeagueScoringConfigRepository] Saving config for season: ${config.seasonId}.`);
this.configs.set(config.seasonId, config);
this.logger.info(`Config for season ${config.seasonId} saved successfully.`);
return Promise.resolve(config);
}
}

View File

@@ -0,0 +1,30 @@
import { ILeagueStandingsRepository, RawStanding } from '@gridpilot/core/league/application/ports/ILeagueStandingsRepository';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryLeagueStandingsRepository implements ILeagueStandingsRepository {
private standings: Map<string, RawStanding[]> = new Map(); // Key: leagueId
constructor(private readonly logger: ILogger, initialStandings: Record<string, RawStanding[]> = {}) {
this.logger.info('InMemoryLeagueStandingsRepository initialized.');
for (const leagueId in initialStandings) {
// Ensure initialStandings[leagueId] is not undefined before setting
if (initialStandings[leagueId] !== undefined) {
this.standings.set(leagueId, initialStandings[leagueId]);
this.logger.debug(`Seeded standings for league: ${leagueId}.`);
}
}
}
async getLeagueStandings(leagueId: string): Promise<RawStanding[]> {
this.logger.debug(`[InMemoryLeagueStandingsRepository] Getting standings for league: ${leagueId}.`);
const leagueStandings = this.standings.get(leagueId) ?? [];
this.logger.info(`Found ${leagueStandings.length} standings for league: ${leagueId}.`);
return Promise.resolve(leagueStandings);
}
// Helper method for seeding/updating if needed by other in-memory repos
public setLeagueStandings(leagueId: string, standings: RawStanding[]): void {
this.standings.set(leagueId, standings);
this.logger.debug(`[InMemoryLeagueStandingsRepository] Set standings for league: ${leagueId}.`);
}
}

View File

@@ -1,151 +1,88 @@
/**
* In-Memory Implementation: InMemoryProtestRepository
*
* Provides an in-memory storage implementation for protests.
*/
import type { Protest } from '../../domain/entities/Protest';
import type { IProtestRepository } from '../../domain/repositories/IProtestRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { IProtestRepository } from '@gridpilot/racing/domain/repositories/IProtestRepository';
import { Protest, ProtestStatus } from '@gridpilot/racing/domain/entities/Protest';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryProtestRepository implements IProtestRepository {
private protests: Map<string, Protest> = new Map();
private readonly logger: ILogger;
constructor(logger: ILogger, initialProtests: Protest[] = []) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialProtests: Protest[] = []) {
this.logger.info('InMemoryProtestRepository initialized.');
initialProtests.forEach(protest => {
for (const protest of initialProtests) {
this.protests.set(protest.id, protest);
this.logger.debug(`Seeded protest: ${protest.id}`);
});
this.logger.debug(`Seeded protest: ${protest.id}.`);
}
}
async findById(id: string): Promise<Protest | null> {
this.logger.debug(`Finding protest by id: ${id}`);
try {
const protest = this.protests.get(id) || null;
if (protest) {
this.logger.info(`Found protest with id: ${id}.`);
} else {
this.logger.warn(`Protest with id ${id} not found.`);
}
return protest;
} catch (error) {
this.logger.error(`Error finding protest by id ${id}:`, error);
throw error;
this.logger.debug(`[InMemoryProtestRepository] Finding protest by ID: ${id}`);
const protest = this.protests.get(id) ?? null;
if (protest) {
this.logger.info(`Found protest by ID: ${id}.`);
} else {
this.logger.warn(`Protest with ID ${id} not found.`);
}
return Promise.resolve(protest);
}
async findByRaceId(raceId: string): Promise<Protest[]> {
this.logger.debug(`Finding protests by race id: ${raceId}`);
try {
const protests = Array.from(this.protests.values()).filter(
protest => protest.raceId === raceId
);
this.logger.info(`Found ${protests.length} protests for race id: ${raceId}.`);
return protests;
} catch (error) {
this.logger.error(`Error finding protests by race id ${raceId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryProtestRepository] Finding protests for race: ${raceId}.`);
const protests = Array.from(this.protests.values()).filter(p => p.raceId === raceId);
this.logger.info(`Found ${protests.length} protests for race ${raceId}.`);
return Promise.resolve(protests);
}
async findByProtestingDriverId(driverId: string): Promise<Protest[]> {
this.logger.debug(`Finding protests by protesting driver id: ${driverId}`);
try {
const protests = Array.from(this.protests.values()).filter(
protest => protest.protestingDriverId === driverId
);
this.logger.info(`Found ${protests.length} protests by protesting driver id: ${driverId}.`);
return protests;
} catch (error) {
this.logger.error(`Error finding protests by protesting driver id ${driverId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryProtestRepository] Finding protests by protesting driver ID: ${driverId}.`);
const protests = Array.from(this.protests.values()).filter(p => p.protestingDriverId === driverId);
this.logger.info(`Found ${protests.length} protests by protesting driver ${driverId}.`);
return Promise.resolve(protests);
}
async findByAccusedDriverId(driverId: string): Promise<Protest[]> {
this.logger.debug(`Finding protests by accused driver id: ${driverId}`);
try {
const protests = Array.from(this.protests.values()).filter(
protest => protest.accusedDriverId === driverId
);
this.logger.info(`Found ${protests.length} protests by accused driver id: ${driverId}.`);
return protests;
} catch (error) {
this.logger.error(`Error finding protests by accused driver id ${driverId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryProtestRepository] Finding protests by accused driver ID: ${driverId}.`);
const protests = Array.from(this.protests.values()).filter(p => p.accusedDriverId === driverId);
this.logger.info(`Found ${protests.length} protests by accused driver ${driverId}.`);
return Promise.resolve(protests);
}
async findPending(): Promise<Protest[]> {
this.logger.debug('Finding pending protests.');
try {
const protests = Array.from(this.protests.values()).filter(
protest => protest.isPending()
);
this.logger.info(`Found ${protests.length} pending protests.`);
return protests;
} catch (error) {
this.logger.error('Error finding pending protests:', error);
throw error;
}
this.logger.debug('[InMemoryProtestRepository] Finding all pending protests.');
const pendingProtests = Array.from(this.protests.values()).filter(p => p.status === 'pending');
this.logger.info(`Found ${pendingProtests.length} pending protests.`);
return Promise.resolve(pendingProtests);
}
async findUnderReviewBy(stewardId: string): Promise<Protest[]> {
this.logger.debug(`Finding protests under review by steward: ${stewardId}`);
try {
const protests = Array.from(this.protests.values()).filter(
protest => protest.reviewedBy === stewardId && protest.isUnderReview()
);
this.logger.info(`Found ${protests.length} protests under review by steward: ${stewardId}.`);
return protests;
} catch (error) {
this.logger.error(`Error finding protests under review by steward ${stewardId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryProtestRepository] Finding protests under review by steward: ${stewardId}.`);
const underReviewProtests = Array.from(this.protests.values()).filter(p => p.reviewedBy === stewardId && p.status === 'under_review');
this.logger.info(`Found ${underReviewProtests.length} protests under review by steward ${stewardId}.`);
return Promise.resolve(underReviewProtests);
}
async create(protest: Protest): Promise<void> {
this.logger.debug(`Creating protest: ${protest.id}`);
try {
if (this.protests.has(protest.id)) {
this.logger.warn(`Protest with ID ${protest.id} already exists.`);
throw new Error(`Protest with ID ${protest.id} already exists`);
}
this.protests.set(protest.id, protest);
this.logger.info(`Protest ${protest.id} created successfully.`);
} catch (error) {
this.logger.error(`Error creating protest ${protest.id}:`, error);
throw error;
this.logger.debug(`[InMemoryProtestRepository] Creating protest: ${protest.id}.`);
if (this.protests.has(protest.id)) {
this.logger.warn(`Protest with ID ${protest.id} already exists.`);
throw new Error('Protest already exists');
}
this.protests.set(protest.id, protest);
this.logger.info(`Protest ${protest.id} created successfully.`);
return Promise.resolve();
}
async update(protest: Protest): Promise<void> {
this.logger.debug(`Updating protest: ${protest.id}`);
try {
if (!this.protests.has(protest.id)) {
this.logger.warn(`Protest with ID ${protest.id} not found for update.`);
throw new Error(`Protest with ID ${protest.id} not found`);
}
this.protests.set(protest.id, protest);
this.logger.info(`Protest ${protest.id} updated successfully.`);
} catch (error) {
this.logger.error(`Error updating protest ${protest.id}:`, error);
throw error;
this.logger.debug(`[InMemoryProtestRepository] Updating protest: ${protest.id}.`);
if (!this.protests.has(protest.id)) {
this.logger.warn(`Protest with ID ${protest.id} not found for update.`);
throw new Error('Protest not found');
}
this.protests.set(protest.id, protest);
this.logger.info(`Protest ${protest.id} updated successfully.`);
return Promise.resolve();
}
async exists(id: string): Promise<boolean> {
this.logger.debug(`Checking existence of protest with id: ${id}`);
try {
const exists = this.protests.has(id);
this.logger.debug(`Protest ${id} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of protest with id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemoryProtestRepository] Checking existence of protest with ID: ${id}.`);
return Promise.resolve(this.protests.has(id));
}
}
}

View File

@@ -1,178 +1,90 @@
/**
* Infrastructure Adapter: InMemoryRaceRegistrationRepository
*
* In-memory implementation of IRaceRegistrationRepository.
* Stores race registrations in Maps keyed by raceId and driverId.
*/
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import type { RaceRegistration } from '@gridpilot/racing/domain/entities/RaceRegistration';
import type { IRaceRegistrationRepository } from '@gridpilot/racing/domain/repositories/IRaceRegistrationRepository';
type RaceRegistrationSeed = Pick<RaceRegistration, 'raceId' | 'driverId' | 'registeredAt'>;
import { IRaceRegistrationRepository } from '@gridpilot/racing/domain/repositories/IRaceRegistrationRepository';
import { RaceRegistration } from '@gridpilot/racing/domain/entities/RaceRegistration';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryRaceRegistrationRepository implements IRaceRegistrationRepository {
private registrationsByRace: Map<string, Set<string>>;
private registrationsByDriver: Map<string, Set<string>>;
private readonly logger: ILogger;
private registrations: Map<string, RaceRegistration> = new Map(); // Key: `${raceId}:${driverId}`
constructor(logger: ILogger, seedRegistrations?: RaceRegistrationSeed[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialRegistrations: RaceRegistration[] = []) {
this.logger.info('InMemoryRaceRegistrationRepository initialized.');
this.registrationsByRace = new Map();
this.registrationsByDriver = new Map();
if (seedRegistrations) {
this.logger.debug('Seeding with initial registrations', { count: seedRegistrations.length });
seedRegistrations.forEach((registration) => {
this.addToIndexes(registration.raceId, registration.driverId, registration.registeredAt);
});
for (const reg of initialRegistrations) {
this.registrations.set(reg.id, reg);
this.logger.debug(`Seeded registration: ${reg.id}.`);
}
}
private addToIndexes(raceId: string, driverId: string, _registeredAt: Date): void {
this.logger.debug('Attempting to add race registration to indexes', { raceId, driverId });
let raceSet = this.registrationsByRace.get(raceId);
if (!raceSet) {
raceSet = new Set();
this.registrationsByRace.set(raceId, raceSet);
this.logger.debug('Created new race set as none existed', { raceId });
}
raceSet.add(driverId);
this.logger.debug('Added driver to race set', { raceId, driverId });
let driverSet = this.registrationsByDriver.get(driverId);
if (!driverSet) {
driverSet = new Set();
this.registrationsByDriver.set(driverId, driverSet);
this.logger.debug('Created new driver set as none existed', { driverId });
}
driverSet.add(raceId);
this.logger.debug('Added race to driver set', { raceId, driverId });
this.logger.info('Successfully added race registration to indexes', { raceId, driverId });
}
private removeFromIndexes(raceId: string, driverId: string): void {
this.logger.debug('Attempting to remove race registration from indexes', { raceId, driverId });
const raceSet = this.registrationsByRace.get(raceId);
if (raceSet) {
raceSet.delete(driverId);
this.logger.debug('Removed driver from race set', { raceId, driverId });
if (raceSet.size === 0) {
this.registrationsByRace.delete(raceId);
this.logger.debug('Deleted race set as it is now empty', { raceId });
}
} else {
this.logger.warn('Race set not found during removal, potential inconsistency', { raceId });
}
const driverSet = this.registrationsByDriver.get(driverId);
if (driverSet) {
driverSet.delete(raceId);
this.logger.debug('Removed race from driver set', { raceId, driverId });
if (driverSet.size === 0) {
this.registrationsByDriver.delete(driverId);
this.logger.debug('Deleted driver set as it is now empty', { driverId });
}
} else {
this.logger.warn('Driver set not found during removal, potential inconsistency', { driverId });
}
this.logger.info('Successfully removed race registration from indexes', { raceId, driverId });
}
async isRegistered(raceId: string, driverId: string): Promise<boolean> {
this.logger.info('Checking if driver is registered for race', { raceId, driverId });
const raceSet = this.registrationsByRace.get(raceId);
if (!raceSet) {
this.logger.debug('Race set not found, driver not registered', { raceId, driverId });
return false;
}
const isRegistered = raceSet.has(driverId);
this.logger.debug('Registration status result', { raceId, driverId, isRegistered });
return isRegistered;
this.logger.debug(`[InMemoryRaceRegistrationRepository] Checking if driver ${driverId} is registered for race ${raceId}.`);
const key = `${raceId}:${driverId}`;
return Promise.resolve(this.registrations.has(key));
}
async getRegisteredDrivers(raceId: string): Promise<string[]> {
this.logger.info('Attempting to fetch registered drivers for race', { raceId });
const raceSet = this.registrationsByRace.get(raceId);
if (!raceSet) {
this.logger.debug('No registered drivers found for race', { raceId });
return [];
this.logger.debug(`[InMemoryRaceRegistrationRepository] Getting registered drivers for race ${raceId}.`);
const driverIds: string[] = [];
for (const registration of this.registrations.values()) {
if (registration.raceId === raceId) {
driverIds.push(registration.driverId);
}
}
const drivers = Array.from(raceSet.values());
this.logger.debug('Found registered drivers for race', { raceId, count: drivers.length });
this.logger.info('Successfully fetched registered drivers for race', { raceId, count: drivers.length });
return drivers;
this.logger.info(`Found ${driverIds.length} registered drivers for race ${raceId}.`);
return Promise.resolve(driverIds);
}
async getRegistrationCount(raceId: string): Promise<number> {
this.logger.info('Attempting to get registration count for race', { raceId });
const raceSet = this.registrationsByRace.get(raceId);
const count = raceSet ? raceSet.size : 0;
this.logger.debug('Registration count for race', { raceId, count });
this.logger.info('Returning registration count for race', { raceId, count });
return count;
this.logger.debug(`[InMemoryRaceRegistrationRepository] Getting registration count for race ${raceId}.`);
const count = Array.from(this.registrations.values()).filter(reg => reg.raceId === raceId).length;
this.logger.info(`Registration count for race ${raceId}: ${count}.`);
return Promise.resolve(count);
}
async register(registration: RaceRegistration): Promise<void> {
this.logger.info('Attempting to register driver for race', { raceId: registration.raceId, driverId: registration.driverId });
const alreadyRegistered = await this.isRegistered(registration.raceId, registration.driverId);
if (alreadyRegistered) {
this.logger.warn('Driver already registered for race, registration aborted', { raceId: registration.raceId, driverId: registration.driverId });
throw new Error('Already registered for this race');
this.logger.debug(`[InMemoryRaceRegistrationRepository] Registering driver ${registration.driverId} for race ${registration.raceId}.`);
if (await this.isRegistered(registration.raceId, registration.driverId)) {
this.logger.warn(`Driver ${registration.driverId} already registered for race ${registration.raceId}.`);
throw new Error('Driver already registered for this race');
}
this.addToIndexes(registration.raceId, registration.driverId, registration.registeredAt);
this.logger.info('Driver successfully registered for race', { raceId: registration.raceId, driverId: registration.driverId });
this.registrations.set(registration.id, registration);
this.logger.info(`Driver ${registration.driverId} registered for race ${registration.raceId}.`);
return Promise.resolve();
}
async withdraw(raceId: string, driverId: string): Promise<void> {
this.logger.info('Attempting to withdraw driver from race', { raceId, driverId });
const alreadyRegistered = await this.isRegistered(raceId, driverId);
if (!alreadyRegistered) {
this.logger.warn('Driver not registered for race, withdrawal aborted', { raceId, driverId });
throw new Error('Not registered for this race');
this.logger.debug(`[InMemoryRaceRegistrationRepository] Withdrawing driver ${driverId} from race ${raceId}.`);
const key = `${raceId}:${driverId}`;
if (!this.registrations.has(key)) {
this.logger.warn(`Driver ${driverId} not registered for race ${raceId}. No withdrawal needed.`);
throw new Error('Driver not registered for this race');
}
this.removeFromIndexes(raceId, driverId);
this.logger.info('Driver successfully withdrew from race', { raceId, driverId });
this.registrations.delete(key);
this.logger.info(`Driver ${driverId} withdrawn from race ${raceId}.`);
return Promise.resolve();
}
async getDriverRegistrations(driverId: string): Promise<string[]> {
this.logger.info('Attempting to fetch registrations for driver', { driverId });
const driverSet = this.registrationsByDriver.get(driverId);
if (!driverSet) {
this.logger.debug('No registrations found for driver', { driverId });
return [];
this.logger.debug(`[InMemoryRaceRegistrationRepository] Getting registrations for driver: ${driverId}.`);
const raceIds: string[] = [];
for (const registration of this.registrations.values()) {
if (registration.driverId === driverId) {
raceIds.push(registration.raceId);
}
}
const registrations = Array.from(driverSet.values());
this.logger.debug('Found registrations for driver', { driverId, count: registrations.length });
this.logger.info('Successfully fetched registrations for driver', { driverId, count: registrations.length });
return registrations;
this.logger.info(`Found ${raceIds.length} registrations for driver ${driverId}.`);
return Promise.resolve(raceIds);
}
async clearRaceRegistrations(raceId: string): Promise<void> {
this.logger.info('Attempting to clear all registrations for race', { raceId });
const raceSet = this.registrationsByRace.get(raceId);
if (!raceSet) {
this.logger.debug('No registrations to clear for race (race set not found)', { raceId });
return;
}
this.logger.debug('Found registrations to clear', { raceId, count: raceSet.size });
for (const driverId of raceSet.values()) {
const driverSet = this.registrationsByDriver.get(driverId);
if (driverSet) {
driverSet.delete(raceId);
if (driverSet.size === 0) {
this.registrationsByDriver.delete(driverId);
this.logger.debug('Deleted driver set as it is now empty during race clear', { raceId, driverId });
}
} else {
this.logger.warn('Driver set not found during race clear, potential inconsistency', { raceId, driverId });
this.logger.debug(`[InMemoryRaceRegistrationRepository] Clearing all registrations for race: ${raceId}.`);
const registrationsToDelete: string[] = [];
for (const registration of this.registrations.values()) {
if (registration.raceId === raceId) {
registrationsToDelete.push(registration.id);
}
this.logger.debug('Removed race from driver set during race clear', { raceId, driverId });
}
this.registrationsByRace.delete(raceId);
this.logger.info('Successfully cleared all registrations for race', { raceId });
for (const id of registrationsToDelete) {
this.registrations.delete(id);
}
this.logger.info(`Cleared ${registrationsToDelete.length} registrations for race ${raceId}.`);
return Promise.resolve();
}
}
}

View File

@@ -1,207 +1,110 @@
/**
* Infrastructure Adapter: InMemoryRaceRepository
*
* In-memory implementation of IRaceRepository.
* Stores data in Map structure with UUID generation.
*/
import { v4 as uuidv4 } from 'uuid';
import { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRaceRepository';
import { Race, RaceStatus } from '@gridpilot/racing/domain/entities/Race';
import type { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRaceRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryRaceRepository implements IRaceRepository {
private races: Map<string, Race>;
private readonly logger: ILogger;
private races: Map<string, Race> = new Map();
constructor(logger: ILogger, seedData?: Race[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialRaces: Race[] = []) {
this.logger.info('InMemoryRaceRepository initialized.');
this.races = new Map();
if (seedData) {
seedData.forEach(race => {
this.races.set(race.id, race);
this.logger.debug(`Seeded race: ${race.id}.`);
});
for (const race of initialRaces) {
this.races.set(race.id, race);
this.logger.debug(`Seeded race: ${race.id} (${race.track}).`);
}
}
async findById(id: string): Promise<Race | null> {
this.logger.debug(`Finding race by id: ${id}`);
try {
const race = this.races.get(id) ?? null;
if (race) {
this.logger.info(`Found race: ${id}.`);
} else {
this.logger.warn(`Race with id ${id} not found.`);
}
return race;
} catch (error) {
this.logger.error(`Error finding race by id ${id}:`, error);
throw error;
this.logger.debug(`[InMemoryRaceRepository] Finding race by ID: ${id}`);
const race = this.races.get(id) ?? null;
if (race) {
this.logger.info(`Found race by ID: ${id}.`);
} else {
this.logger.warn(`Race with ID ${id} not found.`);
}
return Promise.resolve(race);
}
async findAll(): Promise<Race[]> {
this.logger.debug('Finding all races.');
try {
const races = Array.from(this.races.values());
this.logger.info(`Found ${races.length} races.`);
return races;
} catch (error) {
this.logger.error('Error finding all races:', error);
throw error;
}
this.logger.debug('[InMemoryRaceRepository] Finding all races.');
return Promise.resolve(Array.from(this.races.values()));
}
async findByLeagueId(leagueId: string): Promise<Race[]> {
this.logger.debug(`Finding races by league id: ${leagueId}`);
try {
const races = Array.from(this.races.values())
.filter(race => race.leagueId === leagueId)
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
this.logger.info(`Found ${races.length} races for league id: ${leagueId}.`);
return races;
} catch (error) {
this.logger.error(`Error finding races by league id ${leagueId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryRaceRepository] Finding races by league ID: ${leagueId}`);
const races = Array.from(this.races.values()).filter(race => race.leagueId === leagueId);
this.logger.info(`Found ${races.length} races for league ID: ${leagueId}.`);
return Promise.resolve(races);
}
async findUpcomingByLeagueId(leagueId: string): Promise<Race[]> {
this.logger.debug(`Finding upcoming races by league id: ${leagueId}`);
try {
const now = new Date();
const races = Array.from(this.races.values())
.filter(race =>
race.leagueId === leagueId &&
race.status === 'scheduled' &&
race.scheduledAt > now
)
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
this.logger.info(`Found ${races.length} upcoming races for league id: ${leagueId}.`);
return races;
} catch (error) {
this.logger.error(`Error finding upcoming races by league id ${leagueId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryRaceRepository] Finding upcoming races by league ID: ${leagueId}`);
const now = new Date();
const upcomingRaces = Array.from(this.races.values()).filter(race =>
race.leagueId === leagueId && race.status === 'scheduled' && race.scheduledAt > now
);
this.logger.info(`Found ${upcomingRaces.length} upcoming races for league ID: ${leagueId}.`);
return Promise.resolve(upcomingRaces);
}
async findCompletedByLeagueId(leagueId: string): Promise<Race[]> {
this.logger.debug(`Finding completed races by league id: ${leagueId}`);
try {
const races = Array.from(this.races.values())
.filter(race =>
race.leagueId === leagueId &&
race.status === 'completed'
)
.sort((a, b) => b.scheduledAt.getTime() - a.scheduledAt.getTime());
this.logger.info(`Found ${races.length} completed races for league id: ${leagueId}.`);
return races;
} catch (error) {
this.logger.error(`Error finding completed races by league id ${leagueId}:`, error);
throw error;
}
this.logger.debug(`[InMemoryRaceRepository] Finding completed races by league ID: ${leagueId}`);
const completedRaces = Array.from(this.races.values()).filter(race =>
race.leagueId === leagueId && race.status === 'completed'
);
this.logger.info(`Found ${completedRaces.length} completed races for league ID: ${leagueId}.`);
return Promise.resolve(completedRaces);
}
async findByStatus(status: RaceStatus): Promise<Race[]> {
this.logger.debug(`Finding races by status: ${status}`);
try {
const races = Array.from(this.races.values())
.filter(race => race.status === status)
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
this.logger.info(`Found ${races.length} races with status: ${status}.`);
return races;
} catch (error) {
this.logger.error(`Error finding races by status ${status}:`, error);
throw error;
}
this.logger.debug(`[InMemoryRaceRepository] Finding races by status: ${status}.`);
const races = Array.from(this.races.values()).filter(race => race.status === status);
this.logger.info(`Found ${races.length} races with status: ${status}.`);
return Promise.resolve(races);
}
async findByDateRange(startDate: Date, endDate: Date): Promise<Race[]> {
this.logger.debug(`Finding races by date range: ${startDate.toISOString()} - ${endDate.toISOString()}`);
try {
const races = Array.from(this.races.values())
.filter(race =>
race.scheduledAt >= startDate &&
race.scheduledAt <= endDate
)
.sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
this.logger.info(`Found ${races.length} races in date range.`);
return races;
} catch (error) {
this.logger.error(`Error finding races by date range:`, error);
throw error;
}
this.logger.debug(`[InMemoryRaceRepository] Finding races by date range: ${startDate.toISOString()} - ${endDate.toISOString()}.`);
const races = Array.from(this.races.values()).filter(race =>
race.scheduledAt >= startDate && race.scheduledAt <= endDate
);
this.logger.info(`Found ${races.length} races within date range.`);
return Promise.resolve(races);
}
async create(race: Race): Promise<Race> {
this.logger.debug(`Creating race: ${race.id}`);
try {
if (await this.exists(race.id)) {
this.logger.warn(`Race with ID ${race.id} already exists.`);
throw new Error(`Race with ID ${race.id} already exists`);
}
this.races.set(race.id, race);
this.logger.info(`Race ${race.id} created successfully.`);
return race;
} catch (error) {
this.logger.error(`Error creating race ${race.id}:`, error);
throw error;
this.logger.debug(`[InMemoryRaceRepository] Creating race: ${race.id} (${race.track}).`);
if (this.races.has(race.id)) {
this.logger.warn(`Race with ID ${race.id} already exists.`);
throw new Error('Race already exists');
}
this.races.set(race.id, race);
this.logger.info(`Race ${race.id} created successfully.`);
return Promise.resolve(race);
}
async update(race: Race): Promise<Race> {
this.logger.debug(`Updating race: ${race.id}`);
try {
if (!await this.exists(race.id)) {
this.logger.warn(`Race with ID ${race.id} not found for update.`);
throw new Error(`Race with ID ${race.id} not found`);
}
this.races.set(race.id, race);
this.logger.info(`Race ${race.id} updated successfully.`);
return race;
} catch (error) {
this.logger.error(`Error updating race ${race.id}:`, error);
throw error;
this.logger.debug(`[InMemoryRaceRepository] Updating race: ${race.id} (${race.track}).`);
if (!this.races.has(race.id)) {
this.logger.warn(`Race with ID ${race.id} not found for update.`);
throw new Error('Race not found');
}
this.races.set(race.id, race);
this.logger.info(`Race ${race.id} updated successfully.`);
return Promise.resolve(race);
}
async delete(id: string): Promise<void> {
this.logger.debug(`Deleting race: ${id}`);
try {
if (!await this.exists(id)) {
this.logger.warn(`Race with ID ${id} not found for deletion.`);
throw new Error(`Race with ID ${id} not found`);
}
this.races.delete(id);
this.logger.debug(`[InMemoryRaceRepository] Deleting race with ID: ${id}.`);
if (this.races.delete(id)) {
this.logger.info(`Race ${id} deleted successfully.`);
} catch (error) {
this.logger.error(`Error deleting race ${id}:`, error);
throw error;
} else {
this.logger.warn(`Race with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
async exists(id: string): Promise<boolean> {
this.logger.debug(`Checking existence of race with id: ${id}`);
try {
const exists = this.races.has(id);
this.logger.debug(`Race ${id} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of race with id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemoryRaceRepository] Checking existence of race with ID: ${id}.`);
return Promise.resolve(this.races.has(id));
}
/**
* Utility method to generate a new UUID
*/
static generateId(): string {
return uuidv4();
}
}
}

View File

@@ -0,0 +1,88 @@
import { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository';
import { Season } from '@gridpilot/racing/domain/entities/Season';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemorySeasonRepository implements ISeasonRepository {
private seasons: Map<string, Season> = new Map(); // Key: seasonId
constructor(private readonly logger: ILogger, initialSeasons: Season[] = []) {
this.logger.info('InMemorySeasonRepository initialized.');
for (const season of initialSeasons) {
this.seasons.set(season.id, season);
this.logger.debug(`Seeded season: ${season.id} (${season.name}).`);
}
}
async findById(id: string): Promise<Season | null> {
this.logger.debug(`[InMemorySeasonRepository] Finding season by ID: ${id}`);
const season = this.seasons.get(id) ?? null;
if (season) {
this.logger.info(`Found season by ID: ${id}.`);
} else {
this.logger.warn(`Season with ID ${id} not found.`);
}
return Promise.resolve(season);
}
async findByLeagueId(leagueId: string): Promise<Season[]> {
this.logger.debug(`[InMemorySeasonRepository] Finding seasons by league ID: ${leagueId}`);
const seasons = Array.from(this.seasons.values()).filter(season => season.leagueId === leagueId);
this.logger.info(`Found ${seasons.length} seasons for league ID: ${leagueId}.`);
return Promise.resolve(seasons);
}
async create(season: Season): Promise<Season> {
this.logger.debug(`[InMemorySeasonRepository] Creating season: ${season.id} (${season.name})`);
if (this.seasons.has(season.id)) {
this.logger.warn(`Season with ID ${season.id} already exists.`);
throw new Error('Season already exists');
}
this.seasons.set(season.id, season);
this.logger.info(`Season ${season.id} created successfully.`);
return Promise.resolve(season);
}
async add(season: Season): Promise<void> {
this.logger.debug(`[InMemorySeasonRepository] Adding season: ${season.id} (${season.name})`);
if (this.seasons.has(season.id)) {
this.logger.warn(`Season with ID ${season.id} already exists (add method).`);
throw new Error('Season already exists');
}
this.seasons.set(season.id, season);
this.logger.info(`Season ${season.id} added successfully.`);
return Promise.resolve();
}
async update(season: Season): Promise<void> {
this.logger.debug(`[InMemorySeasonRepository] Updating season: ${season.id} (${season.name})`);
if (!this.seasons.has(season.id)) {
this.logger.warn(`Season with ID ${season.id} not found for update.`);
throw new Error('Season not found');
}
this.seasons.set(season.id, season);
this.logger.info(`Season ${season.id} updated successfully.`);
return Promise.resolve();
}
async delete(id: string): Promise<void> {
this.logger.debug(`[InMemorySeasonRepository] Deleting season with ID: ${id}`);
if (this.seasons.delete(id)) {
this.logger.info(`Season ${id} deleted successfully.`);
} else {
this.logger.warn(`Season with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
async listByLeague(leagueId: string): Promise<Season[]> {
this.logger.debug(`[InMemorySeasonRepository] Listing seasons by league ID: ${leagueId}`);
const seasons = Array.from(this.seasons.values()).filter(season => season.leagueId === leagueId);
return Promise.resolve(seasons);
}
async listActiveByLeague(leagueId: string): Promise<Season[]> {
this.logger.debug(`[InMemorySeasonRepository] Listing active seasons by league ID: ${leagueId}`);
const activeSeasons = Array.from(this.seasons.values()).filter(season => season.leagueId === leagueId && season.status === 'active');
return Promise.resolve(activeSeasons);
}
}

View File

@@ -1,149 +1,98 @@
/**
* In-Memory Implementation: ISponsorRepository
*
* Mock repository for testing and development
*/
import type { Sponsor } from '../../domain/entities/Sponsor';
import type { ISponsorRepository } from '../../domain/repositories/ISponsorRepository';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ISponsorRepository } from '@gridpilot/core/racing/domain/repositories/ISponsorRepository';
import { Sponsor } from '@gridpilot/core/racing/domain/entities/Sponsor';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemorySponsorRepository implements ISponsorRepository {
private sponsors: Map<string, Sponsor> = new Map();
private readonly logger: ILogger;
private emailIndex: Map<string, string> = new Map(); // contactEmail -> sponsorId
constructor(logger: ILogger, seedData?: Sponsor[]) {
this.logger = logger;
constructor(private readonly logger: ILogger, initialSponsors: Sponsor[] = []) {
this.logger.info('InMemorySponsorRepository initialized.');
if (seedData) {
this.seed(seedData);
for (const sponsor of initialSponsors) {
this.sponsors.set(sponsor.id, sponsor);
this.emailIndex.set(sponsor.contactEmail.toLowerCase(), sponsor.id);
this.logger.debug(`Seeded sponsor: ${sponsor.id} (${sponsor.name}).`);
}
}
async findById(id: string): Promise<Sponsor | null> {
this.logger.debug(`Finding sponsor by id: ${id}`);
try {
const sponsor = this.sponsors.get(id) ?? null;
if (sponsor) {
this.logger.info(`Found sponsor: ${id}.`);
} else {
this.logger.warn(`Sponsor with id ${id} not found.`);
}
return sponsor;
} catch (error) {
this.logger.error(`Error finding sponsor by id ${id}:`, error);
throw error;
this.logger.debug(`[InMemorySponsorRepository] Finding sponsor by ID: ${id}`);
const sponsor = this.sponsors.get(id) ?? null;
if (sponsor) {
this.logger.info(`Found sponsor by ID: ${id}.`);
} else {
this.logger.warn(`Sponsor with ID ${id} not found.`);
}
return Promise.resolve(sponsor);
}
async findAll(): Promise<Sponsor[]> {
this.logger.debug('Finding all sponsors.');
try {
const sponsors = Array.from(this.sponsors.values());
this.logger.info(`Found ${sponsors.length} sponsors.`);
return sponsors;
} catch (error) {
this.logger.error('Error finding all sponsors:', error);
throw error;
}
this.logger.debug('[InMemorySponsorRepository] Finding all sponsors.');
return Promise.resolve(Array.from(this.sponsors.values()));
}
async findByEmail(email: string): Promise<Sponsor | null> {
this.logger.debug(`Finding sponsor by email: ${email}`);
try {
for (const sponsor of this.sponsors.values()) {
if (sponsor.contactEmail === email) {
this.logger.info(`Found sponsor with email: ${email}.`);
return sponsor;
}
}
this.logger.debug(`[InMemorySponsorRepository] Finding sponsor by email: ${email}`);
const sponsorId = this.emailIndex.get(email.toLowerCase());
if (!sponsorId) {
this.logger.warn(`Sponsor with email ${email} not found.`);
return null;
} catch (error) {
this.logger.error(`Error finding sponsor by email ${email}:`, error);
throw error;
return Promise.resolve(null);
}
return this.findById(sponsorId);
}
async create(sponsor: Sponsor): Promise<Sponsor> {
this.logger.debug(`Creating sponsor: ${sponsor.id}`);
try {
if (this.sponsors.has(sponsor.id)) {
this.logger.warn(`Sponsor with ID ${sponsor.id} already exists.`);
throw new Error('Sponsor with this ID already exists');
}
this.sponsors.set(sponsor.id, sponsor);
this.logger.info(`Sponsor ${sponsor.id} created successfully.`);
return sponsor;
} catch (error) {
this.logger.error(`Error creating sponsor ${sponsor.id}:`, error);
throw error;
this.logger.debug(`[InMemorySponsorRepository] Creating sponsor: ${sponsor.id} (${sponsor.name})`);
if (this.sponsors.has(sponsor.id)) {
this.logger.warn(`Sponsor with ID ${sponsor.id} already exists.`);
throw new Error('Sponsor already exists');
}
if (this.emailIndex.has(sponsor.contactEmail.toLowerCase())) {
this.logger.warn(`Sponsor with email ${sponsor.contactEmail} already exists.`);
throw new Error('Sponsor with this email already exists');
}
this.sponsors.set(sponsor.id, sponsor);
this.emailIndex.set(sponsor.contactEmail.toLowerCase(), sponsor.id);
this.logger.info(`Sponsor ${sponsor.id} (${sponsor.name}) created successfully.`);
return Promise.resolve(sponsor);
}
async update(sponsor: Sponsor): Promise<Sponsor> {
this.logger.debug(`Updating sponsor: ${sponsor.id}`);
try {
if (!this.sponsors.has(sponsor.id)) {
this.logger.warn(`Sponsor with ID ${sponsor.id} not found for update.`);
throw new Error('Sponsor not found');
}
this.sponsors.set(sponsor.id, sponsor);
this.logger.info(`Sponsor ${sponsor.id} updated successfully.`);
return sponsor;
} catch (error) {
this.logger.error(`Error updating sponsor ${sponsor.id}:`, error);
throw error;
this.logger.debug(`[InMemorySponsorRepository] Updating sponsor: ${sponsor.id} (${sponsor.name})`);
if (!this.sponsors.has(sponsor.id)) {
this.logger.warn(`Sponsor with ID ${sponsor.id} not found for update.`);
throw new Error('Sponsor not found');
}
const existingSponsor = this.sponsors.get(sponsor.id);
// If email changed, update index
if (existingSponsor && existingSponsor.contactEmail.toLowerCase() !== sponsor.contactEmail.toLowerCase()) {
if (this.emailIndex.has(sponsor.contactEmail.toLowerCase()) && this.emailIndex.get(sponsor.contactEmail.toLowerCase()) !== sponsor.id) {
this.logger.warn(`Cannot update sponsor ${sponsor.id} to email ${sponsor.contactEmail} as it's already taken.`);
throw new Error('Sponsor with this email already exists');
}
this.emailIndex.delete(existingSponsor.contactEmail.toLowerCase());
this.emailIndex.set(sponsor.contactEmail.toLowerCase(), sponsor.id);
}
this.sponsors.set(sponsor.id, sponsor);
this.logger.info(`Sponsor ${sponsor.id} (${sponsor.name}) updated successfully.`);
return Promise.resolve(sponsor);
}
async delete(id: string): Promise<void> {
this.logger.debug(`Deleting sponsor: ${id}`);
try {
if (this.sponsors.delete(id)) {
this.logger.info(`Sponsor ${id} deleted successfully.`);
} else {
this.logger.warn(`Sponsor with id ${id} not found for deletion.`);
}
} catch (error) {
this.logger.error(`Error deleting sponsor ${id}:`, error);
throw error;
this.logger.debug(`[InMemorySponsorRepository] Deleting sponsor with ID: ${id}`);
const sponsor = this.sponsors.get(id);
if (sponsor) {
this.sponsors.delete(id);
this.emailIndex.delete(sponsor.contactEmail.toLowerCase());
this.logger.info(`Sponsor ${id} deleted successfully.`);
} else {
this.logger.warn(`Sponsor with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
async exists(id: string): Promise<boolean> {
this.logger.debug(`Checking existence of sponsor with id: ${id}`);
try {
const exists = this.sponsors.has(id);
this.logger.debug(`Sponsor ${id} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of sponsor with id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorRepository] Checking existence of sponsor with ID: ${id}`);
return Promise.resolve(this.sponsors.has(id));
}
/**
* Seed initial data
*/
seed(sponsors: Sponsor[]): void {
this.logger.debug(`Seeding ${sponsors.length} sponsors.`);
try {
for (const sponsor of sponsors) {
this.sponsors.set(sponsor.id, sponsor);
this.logger.debug(`Seeded sponsor: ${sponsor.id}.`);
}
this.logger.info(`Successfully seeded ${sponsors.length} sponsors.`);
} catch (error) {
this.logger.error(`Error seeding sponsors:`, error);
throw error;
}
}
// Test helper
clear(): void {
this.logger.debug('Clearing all sponsors.');
this.sponsors.clear();
this.logger.info('All sponsors cleared.');
}
}
}

View File

@@ -1,232 +1,112 @@
/**
* InMemory implementation of ISponsorshipRequestRepository
*/
import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository';
import {
SponsorshipRequest,
type SponsorableEntityType,
type SponsorshipRequestStatus
} from '../../domain/entities/SponsorshipRequest';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
import { ISponsorshipRequestRepository } from '@gridpilot/racing/domain/repositories/ISponsorshipRequestRepository';
import { SponsorshipRequest, SponsorableEntityType, SponsorshipRequestStatus } from '@gridpilot/racing/domain/entities/SponsorshipRequest';
import { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemorySponsorshipRequestRepository implements ISponsorshipRequestRepository {
private requests: Map<string, SponsorshipRequest> = new Map();
private readonly logger: ILogger;
constructor(logger: ILogger, seedData?: SponsorshipRequest[]) {
this.logger = logger;
this.logger.info('InMemorySponsorshipRequestRepository initialized.');
if (seedData) {
this.seed(seedData);
constructor(private readonly logger: ILogger, initialRequests: SponsorshipRequest[] = []) {
this.logger.info('InMemorySponsorshipRequestRepository initialized.');
for (const req of initialRequests) {
this.requests.set(req.id, req);
this.logger.debug(`Seeded sponsorship request: ${req.id}.`);
}
}
async findById(id: string): Promise<SponsorshipRequest | null> {
this.logger.debug(`Finding sponsorship request by id: ${id}`);
try {
const request = this.requests.get(id) ?? null;
if (request) {
this.logger.info(`Found sponsorship request: ${id}.`);
} else {
this.logger.warn(`Sponsorship request with id ${id} not found.`);
}
return request;
} catch (error) {
this.logger.error(`Error finding sponsorship request by id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Finding request by ID: ${id}`);
const request = this.requests.get(id) ?? null;
if (request) {
this.logger.info(`Found request by ID: ${id}.`);
} else {
this.logger.warn(`Request with ID ${id} not found.`);
}
return Promise.resolve(request);
}
async findByEntity(entityType: SponsorableEntityType, entityId: string): Promise<SponsorshipRequest[]> {
this.logger.debug(`Finding sponsorship requests by entity: ${entityType}, ${entityId}`);
try {
const requests = Array.from(this.requests.values()).filter(
request => request.entityType === entityType && request.entityId === entityId
);
this.logger.info(`Found ${requests.length} sponsorship requests for entity: ${entityType}, ${entityId}.`);
return requests;
} catch (error) {
this.logger.error(`Error finding sponsorship requests by entity ${entityType}, ${entityId}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Finding requests for entity ${entityType}:${entityId}.`);
const requests = Array.from(this.requests.values()).filter(req => req.entityType === entityType && req.entityId === entityId);
this.logger.info(`Found ${requests.length} requests for entity ${entityType}:${entityId}.`);
return Promise.resolve(requests);
}
async findPendingByEntity(entityType: SponsorableEntityType, entityId: string): Promise<SponsorshipRequest[]> {
this.logger.debug(`Finding pending sponsorship requests by entity: ${entityType}, ${entityId}`);
try {
const requests = Array.from(this.requests.values()).filter(
request =>
request.entityType === entityType &&
request.entityId === entityId &&
request.status === 'pending'
);
this.logger.info(`Found ${requests.length} pending sponsorship requests for entity: ${entityType}, ${entityId}.`);
return requests;
} catch (error) {
this.logger.error(`Error finding pending sponsorship requests by entity ${entityType}, ${entityId}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Finding pending requests for entity ${entityType}:${entityId}.`);
const requests = Array.from(this.requests.values()).filter(req => req.entityType === entityType && req.entityId === entityId && req.status === 'pending');
this.logger.info(`Found ${requests.length} pending requests for entity ${entityType}:${entityId}.`);
return Promise.resolve(requests);
}
async findBySponsorId(sponsorId: string): Promise<SponsorshipRequest[]> {
this.logger.debug(`Finding sponsorship requests by sponsor id: ${sponsorId}`);
try {
const requests = Array.from(this.requests.values()).filter(
request => request.sponsorId === sponsorId
);
this.logger.info(`Found ${requests.length} sponsorship requests for sponsor id: ${sponsorId}.`);
return requests;
} catch (error) {
this.logger.error(`Error finding sponsorship requests by sponsor id ${sponsorId}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Finding requests by sponsor ID: ${sponsorId}.`);
const requests = Array.from(this.requests.values()).filter(req => req.sponsorId === sponsorId);
this.logger.info(`Found ${requests.length} requests by sponsor ID: ${sponsorId}.`);
return Promise.resolve(requests);
}
async findByStatus(status: SponsorshipRequestStatus): Promise<SponsorshipRequest[]> {
this.logger.debug(`Finding sponsorship requests by status: ${status}`);
try {
const requests = Array.from(this.requests.values()).filter(
request => request.status === status
);
this.logger.info(`Found ${requests.length} sponsorship requests with status: ${status}.`);
return requests;
} catch (error) {
this.logger.error(`Error finding sponsorship requests by status ${status}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Finding requests by status: ${status}.`);
const requests = Array.from(this.requests.values()).filter(req => req.status === status);
this.logger.info(`Found ${requests.length} requests with status: ${status}.`);
return Promise.resolve(requests);
}
async findBySponsorIdAndStatus(sponsorId: string, status: SponsorshipRequestStatus): Promise<SponsorshipRequest[]> {
this.logger.debug(`Finding sponsorship requests by sponsor id: ${sponsorId} and status: ${status}`);
try {
const requests = Array.from(this.requests.values()).filter(
request => request.sponsorId === sponsorId && request.status === status
);
this.logger.info(`Found ${requests.length} sponsorship requests for sponsor id: ${sponsorId}, status: ${status}.`);
return requests;
} catch (error) {
this.logger.error(`Error finding sponsorship requests by sponsor id ${sponsorId}, status ${status}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Finding requests by sponsor ID ${sponsorId} and status ${status}.`);
const requests = Array.from(this.requests.values()).filter(req => req.sponsorId === sponsorId && req.status === status);
this.logger.info(`Found ${requests.length} requests by sponsor ID ${sponsorId} and status ${status}.`);
return Promise.resolve(requests);
}
async hasPendingRequest(sponsorId: string, entityType: SponsorableEntityType, entityId: string): Promise<boolean> {
this.logger.debug(`Checking for pending request from sponsor: ${sponsorId} for entity: ${entityType}, ${entityId}`);
try {
const has = Array.from(this.requests.values()).some(
request =>
request.sponsorId === sponsorId &&
request.entityType === entityType &&
request.entityId === entityId &&
request.status === 'pending'
);
this.logger.debug(`Pending request exists: ${has}.`);
return has;
} catch (error) {
this.logger.error(`Error checking for pending request from sponsor ${sponsorId} for entity ${entityType}, ${entityId}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Checking for pending request for sponsor ${sponsorId}, entity ${entityType}:${entityId}.`);
const exists = Array.from(this.requests.values()).some(req => req.sponsorId === sponsorId && req.entityType === entityType && req.entityId === entityId && req.status === 'pending');
this.logger.info(`Pending request for sponsor ${sponsorId}, entity ${entityType}:${entityId} exists: ${exists}.`);
return Promise.resolve(exists);
}
async countPendingByEntity(entityType: SponsorableEntityType, entityId: string): Promise<number> {
this.logger.debug(`Counting pending requests for entity: ${entityType}, ${entityId}`);
try {
const count = Array.from(this.requests.values()).filter(
request =>
request.entityType === entityType &&
request.entityId === entityId &&
request.status === 'pending'
).length;
this.logger.info(`Counted ${count} pending requests for entity: ${entityType}, ${entityId}.`);
return count;
} catch (error) {
this.logger.error(`Error counting pending requests for entity ${entityType}, ${entityId}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Counting pending requests for entity ${entityType}:${entityId}.`);
const count = Array.from(this.requests.values()).filter(req => req.entityType === entityType && req.entityId === entityId && req.status === 'pending').length;
this.logger.info(`Count of pending requests for entity ${entityType}:${entityId}: ${count}.`);
return Promise.resolve(count);
}
async create(request: SponsorshipRequest): Promise<SponsorshipRequest> {
this.logger.debug(`Creating sponsorship request: ${request.id}`);
try {
if (this.requests.has(request.id)) {
this.logger.warn(`SponsorshipRequest with ID ${request.id} already exists.`);
throw new Error(`SponsorshipRequest with ID ${request.id} already exists`);
}
this.requests.set(request.id, request);
this.logger.info(`SponsorshipRequest ${request.id} created successfully.`);
return request;
} catch (error) {
this.logger.error(`Error creating sponsorship request ${request.id}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Creating request: ${request.id}.`);
if (this.requests.has(request.id)) {
this.logger.warn(`Request with ID ${request.id} already exists.`);
throw new Error('Sponsorship request already exists');
}
this.requests.set(request.id, request);
this.logger.info(`Sponsorship request ${request.id} created successfully.`);
return Promise.resolve(request);
}
async update(request: SponsorshipRequest): Promise<SponsorshipRequest> {
this.logger.debug(`Updating sponsorship request: ${request.id}`);
try {
if (!this.requests.has(request.id)) {
this.logger.warn(`SponsorshipRequest ${request.id} not found for update.`);
throw new Error(`SponsorshipRequest ${request.id} not found`);
}
this.requests.set(request.id, request);
this.logger.info(`SponsorshipRequest ${request.id} updated successfully.`);
return request;
} catch (error) {
this.logger.error(`Error updating sponsorship request ${request.id}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Updating request: ${request.id}.`);
if (!this.requests.has(request.id)) {
this.logger.warn(`Request with ID ${request.id} not found for update.`);
throw new Error('Sponsorship request not found');
}
this.requests.set(request.id, request);
this.logger.info(`Sponsorship request ${request.id} updated successfully.`);
return Promise.resolve(request);
}
async delete(id: string): Promise<void> {
this.logger.debug(`Deleting sponsorship request: ${id}`);
try {
if (this.requests.delete(id)) {
this.logger.info(`SponsorshipRequest ${id} deleted successfully.`);
} else {
this.logger.warn(`SponsorshipRequest with id ${id} not found for deletion.`);
}
} catch (error) {
this.logger.error(`Error deleting sponsorship request ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Deleting request with ID: ${id}.`);
if (this.requests.delete(id)) {
this.logger.info(`Sponsorship request ${id} deleted successfully.`);
} else {
this.logger.warn(`Request with ID ${id} not found for deletion.`);
}
return Promise.resolve();
}
async exists(id: string): Promise<boolean> {
this.logger.debug(`Checking existence of sponsorship request with id: ${id}`);
try {
const exists = this.requests.has(id);
this.logger.debug(`Sponsorship request ${id} exists: ${exists}.`);
return exists;
} catch (error) {
this.logger.error(`Error checking existence of sponsorship request with id ${id}:`, error);
throw error;
}
this.logger.debug(`[InMemorySponsorshipRequestRepository] Checking existence of request with ID: ${id}.`);
return Promise.resolve(this.requests.has(id));
}
/**
* Seed initial data
*/
seed(requests: SponsorshipRequest[]): void {
this.logger.debug(`Seeding ${requests.length} sponsorship requests.`);
try {
for (const request of requests) {
this.requests.set(request.id, request);
this.logger.debug(`Seeded sponsorship request: ${request.id}.`);
}
this.logger.info(`Successfully seeded ${requests.length} sponsorship requests.`);
} catch (error) {
this.logger.error(`Error seeding sponsorship requests:`, error);
throw error;
}
}
/**
* Clear all data (for testing)
*/
clear(): void {
this.logger.debug('Clearing all sponsorship requests.');
this.requests.clear();
this.logger.info('All sponsorship requests cleared.');
}
}
}

View File

@@ -0,0 +1,32 @@
import type { DriverRatingProvider } from '@gridpilot/racing/application/ports/DriverRatingProvider';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryDriverRatingProvider implements DriverRatingProvider {
constructor(private readonly logger: ILogger) {
this.logger.info('InMemoryDriverRatingProvider initialized.');
}
getRating(driverId: string): number | null {
this.logger.debug(`[InMemoryDriverRatingProvider] Getting rating for driver: ${driverId}`);
// Mock data for demonstration purposes
if (driverId === 'driver-1') {
return 2500;
}
if (driverId === 'driver-2') {
return 2400;
}
return null;
}
getRatings(driverIds: string[]): Map<string, number> {
this.logger.debug(`[InMemoryDriverRatingProvider] Getting ratings for drivers: ${driverIds.join(', ')}`);
const ratingsMap = new Map<string, number>();
for (const driverId of driverIds) {
const rating = this.getRating(driverId);
if (rating !== null) {
ratingsMap.set(driverId, rating);
}
}
return ratingsMap;
}
}

View File

@@ -0,0 +1,33 @@
import type { IDriverStatsService, DriverStats } from '@gridpilot/racing/domain/services/IDriverStatsService';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryDriverStatsService implements IDriverStatsService {
constructor(private readonly logger: ILogger) {
this.logger.info('InMemoryDriverStatsService initialized.');
}
getDriverStats(driverId: string): DriverStats | null {
this.logger.debug(`[InMemoryDriverStatsService] Getting stats for driver: ${driverId}`);
// Mock data for demonstration purposes
if (driverId === 'driver-1') {
return {
rating: 2500,
wins: 10,
podiums: 15,
totalRaces: 50,
overallRank: 1,
};
}
if (driverId === 'driver-2') {
return {
rating: 2400,
wins: 8,
podiums: 12,
totalRaces: 45,
overallRank: 2,
};
}
return null;
}
}

View File

@@ -0,0 +1,20 @@
import type { IRankingService, DriverRanking } from '@gridpilot/racing/domain/services/IRankingService';
import type { ILogger } from '@gridpilot/shared/logging/ILogger';
export class InMemoryRankingService implements IRankingService {
constructor(private readonly logger: ILogger) {
this.logger.info('InMemoryRankingService initialized.');
}
getAllDriverRankings(): DriverRanking[] {
this.logger.debug('[InMemoryRankingService] Getting all driver rankings.');
// Mock data for demonstration purposes
const mockRankings: DriverRanking[] = [
{ driverId: 'driver-1', rating: 2500, overallRank: 1 },
{ driverId: 'driver-2', rating: 2400, overallRank: 2 },
{ driverId: 'driver-3', rating: 2300, overallRank: 3 },
];
return mockRankings;
}
}

22
adapters/tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "../", // Base URL is the project root
"paths": {
"@gridpilot/core/*": ["core/*"],
"@gridpilot/shared/*": ["core/shared/*"],
"@gridpilot/identity/application/dto/*": ["core/identity/application/dto/*"],
"@gridpilot/identity/application/ports/*": ["core/identity/application/ports/*"],
"@gridpilot/identity/domain/repositories/*": ["core/identity/domain/repositories/*"],
"@gridpilot/identity/domain/services/*": ["core/identity/domain/services/*"],
"@gridpilot/racing/domain/repositories/*": ["core/racing/domain/repositories/*"],
"@gridpilot/racing/domain/entities/*": ["core/racing/domain/entities/*"],
"@gridpilot/racing/application/ports/*": ["core/racing/application/ports/*"]
},
"composite": true,
"outDir": "./dist",
"rootDir": "../" // Root directory is the project root
},
"include": ["**/*", "../core/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}