website refactor

This commit is contained in:
2026-01-16 21:57:44 +01:00
parent 83a9092c50
commit 27f5a52e04
30 changed files with 166 additions and 161 deletions

View File

@@ -4,7 +4,8 @@
* In-memory implementation of IAnalyticsSnapshotRepository for development/testing. * In-memory implementation of IAnalyticsSnapshotRepository for development/testing.
*/ */
import { AnalyticsSnapshot, SnapshotPeriod } from '@core/analytics/application/repositories/PageViewRepository'; import { AnalyticsSnapshot, type SnapshotEntityType, type SnapshotPeriod } from '@core/analytics/domain/entities/AnalyticsSnapshot';
import type { AnalyticsSnapshotRepository } from '@core/analytics/domain/repositories/AnalyticsSnapshotRepository';
import { Logger } from '@core/shared/domain/Logger'; import { Logger } from '@core/shared/domain/Logger';
export class InMemoryAnalyticsSnapshotRepository implements AnalyticsSnapshotRepository { export class InMemoryAnalyticsSnapshotRepository implements AnalyticsSnapshotRepository {

View File

@@ -6,7 +6,7 @@
import type { PageViewRepository } from '@core/analytics/application/repositories/PageViewRepository'; import type { PageViewRepository } from '@core/analytics/application/repositories/PageViewRepository';
import { PageView, type EntityType } from '@core/analytics/domain/entities/PageView'; import { PageView, type EntityType } from '@core/analytics/domain/entities/PageView';
import { Logger } from '@core/shared/domain'; import { Logger } from '@core/shared/domain/Logger';
export class InMemoryPageViewRepository implements PageViewRepository { export class InMemoryPageViewRepository implements PageViewRepository {
private pageViews: Map<string, PageView> = new Map(); private pageViews: Map<string, PageView> = new Map();

View File

@@ -1,6 +1,10 @@
import { AdminUser } from '@core/admin/domain/entities/AdminUser'; import { AdminUser } from '@core/admin/domain/entities/AdminUser';
import { Email } from '@core/admin/domain/value-objects/Email'; import { Email } from '@core/admin/domain/value-objects/Email';
import type { AdminUserRepository } from '@core/admin/domain/repositories/AdminUserRepository';
import { User } from '@core/identity/domain/entities/User'; import { User } from '@core/identity/domain/entities/User';
import type { AuthRepository } from '@core/identity/domain/repositories/AuthRepository';
import type { UserRepository } from '@core/identity/domain/repositories/UserRepository';
import type { PasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress'; import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress';
import { PasswordHash } from '@core/identity/domain/value-objects/PasswordHash'; import { PasswordHash } from '@core/identity/domain/value-objects/PasswordHash';
import { UserId } from '@core/identity/domain/value-objects/UserId'; import { UserId } from '@core/identity/domain/value-objects/UserId';
@@ -84,9 +88,9 @@ export class SeedDemoUsers {
constructor( constructor(
private readonly logger: Logger, private readonly logger: Logger,
private readonly authRepository: IAuthRepository, private readonly authRepository: AuthRepository,
private readonly passwordHashingService: IPasswordHashingService, private readonly passwordHashingService: PasswordHashingService,
private readonly adminUserRepository: IAdminUserRepository, private readonly adminUserRepository: AdminUserRepository,
) {} ) {}
private getApiPersistence(): 'postgres' | 'inmemory' { private getApiPersistence(): 'postgres' | 'inmemory' {

View File

@@ -4,40 +4,65 @@ import { Result } from '@core/racing/domain/entities/result/Result';
import type { Season } from '@core/racing/domain/entities/season/Season'; import type { Season } from '@core/racing/domain/entities/season/Season';
import { Standing } from '@core/racing/domain/entities/Standing'; import { Standing } from '@core/racing/domain/entities/Standing';
import { Team } from '@core/racing/domain/entities/Team'; import { Team } from '@core/racing/domain/entities/Team';
import type { TeamStats } from '@core/racing/domain/repositories/TeamStatsRepository'; import type { DriverRepository } from '@core/racing/domain/repositories/DriverRepository';
import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository';
import type { SeasonRepository } from '@core/racing/domain/repositories/SeasonRepository';
import type { LeagueScoringConfigRepository } from '@core/racing/domain/repositories/LeagueScoringConfigRepository';
import type { SeasonSponsorshipRepository } from '@core/racing/domain/repositories/SeasonSponsorshipRepository';
import type { SponsorshipRequestRepository } from '@core/racing/domain/repositories/SponsorshipRequestRepository';
import type { LeagueWalletRepository } from '@core/racing/domain/repositories/LeagueWalletRepository';
import type { TransactionRepository } from '@core/racing/domain/repositories/TransactionRepository';
import type { ProtestRepository } from '@core/racing/domain/repositories/ProtestRepository';
import type { PenaltyRepository } from '@core/racing/domain/repositories/PenaltyRepository';
import type { RaceRepository } from '@core/racing/domain/repositories/RaceRepository';
import type { ResultRepository } from '@core/racing/domain/repositories/ResultRepository';
import type { StandingRepository } from '@core/racing/domain/repositories/StandingRepository';
import type { LeagueMembershipRepository } from '@core/racing/domain/repositories/LeagueMembershipRepository';
import type { RaceRegistrationRepository } from '@core/racing/domain/repositories/RaceRegistrationRepository';
import type { TeamRepository } from '@core/racing/domain/repositories/TeamRepository';
import type { TeamMembershipRepository } from '@core/racing/domain/repositories/TeamMembershipRepository';
import type { SponsorRepository } from '@core/racing/domain/repositories/SponsorRepository';
import type { FeedRepository } from '@core/social/domain/repositories/FeedRepository';
import type { SocialGraphRepository } from '@core/social/domain/repositories/SocialGraphRepository';
import type { DriverStatsRepository } from '@core/racing/domain/repositories/DriverStatsRepository';
import type { TeamStatsRepository, TeamStats } from '@core/racing/domain/repositories/TeamStatsRepository';
import type { MediaRepository } from '@core/racing/domain/repositories/MediaRepository';
import type { AuthRepository } from '@core/identity/domain/repositories/AuthRepository';
import type { PasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
import type { AdminUserRepository } from '@core/admin/domain/repositories/AdminUserRepository';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
import { getLeagueScoringPresetById } from './LeagueScoringPresets'; import { getLeagueScoringPresetById } from './LeagueScoringPresets';
import { createRacingSeed } from './racing/RacingSeed'; import { createRacingSeed } from './racing/RacingSeed';
import { seedId } from './racing/SeedIdHelper'; import { seedId } from './racing/SeedIdHelper';
export type RacingSeedDependencies = { export type RacingSeedDependencies = {
driverRepository: IDriverRepository; driverRepository: DriverRepository;
leagueRepository: ILeagueRepository; leagueRepository: LeagueRepository;
seasonRepository: ISeasonRepository; seasonRepository: SeasonRepository;
leagueScoringConfigRepository: ILeagueScoringConfigRepository; leagueScoringConfigRepository: LeagueScoringConfigRepository;
seasonSponsorshipRepository: ISeasonSponsorshipRepository; seasonSponsorshipRepository: SeasonSponsorshipRepository;
sponsorshipRequestRepository: ISponsorshipRequestRepository; sponsorshipRequestRepository: SponsorshipRequestRepository;
leagueWalletRepository: ILeagueWalletRepository; leagueWalletRepository: LeagueWalletRepository;
transactionRepository: ITransactionRepository; transactionRepository: TransactionRepository;
protestRepository: IProtestRepository; protestRepository: ProtestRepository;
penaltyRepository: IPenaltyRepository; penaltyRepository: PenaltyRepository;
raceRepository: IRaceRepository; raceRepository: RaceRepository;
resultRepository: IResultRepository; resultRepository: ResultRepository;
standingRepository: IStandingRepository; standingRepository: StandingRepository;
leagueMembershipRepository: ILeagueMembershipRepository; leagueMembershipRepository: LeagueMembershipRepository;
raceRegistrationRepository: IRaceRegistrationRepository; raceRegistrationRepository: RaceRegistrationRepository;
teamRepository: ITeamRepository; teamRepository: TeamRepository;
teamMembershipRepository: ITeamMembershipRepository; teamMembershipRepository: TeamMembershipRepository;
sponsorRepository: ISponsorRepository; sponsorRepository: SponsorRepository;
feedRepository: IFeedRepository; feedRepository: FeedRepository;
socialGraphRepository: ISocialGraphRepository; socialGraphRepository: SocialGraphRepository;
driverStatsRepository: IDriverStatsRepository; driverStatsRepository: DriverStatsRepository;
teamStatsRepository: ITeamStatsRepository; teamStatsRepository: TeamStatsRepository;
mediaRepository: IMediaRepository; mediaRepository: MediaRepository;
// Identity dependencies for demo user seed // Identity dependencies for demo user seed
authRepository: IAuthRepository; authRepository: AuthRepository;
passwordHashingService: IPasswordHashingService; passwordHashingService: PasswordHashingService;
adminUserRepository: IAdminUserRepository; adminUserRepository: AdminUserRepository;
}; };
export class SeedRacingData { export class SeedRacingData {
@@ -280,7 +305,7 @@ export class SeedRacingData {
// ignore duplicates // ignore duplicates
} }
const seedableFeed = this.seedDeps.feedRepository as unknown as { seed?: (input: unknown) => void }; const seedableFeed = this.seedDeps.feedRepository as unknown as { seed?: (input: any) => void };
if (typeof seedableFeed.seed === 'function') { if (typeof seedableFeed.seed === 'function') {
seedableFeed.seed({ seedableFeed.seed({
drivers: seed.drivers, drivers: seed.drivers,
@@ -289,7 +314,7 @@ export class SeedRacingData {
}); });
} }
const seedableSocial = this.seedDeps.socialGraphRepository as unknown as { seed?: (input: unknown) => void }; const seedableSocial = this.seedDeps.socialGraphRepository as unknown as { seed?: (input: any) => void };
if (typeof seedableSocial.seed === 'function') { if (typeof seedableSocial.seed === 'function') {
seedableSocial.seed({ seedableSocial.seed({
drivers: seed.drivers, drivers: seed.drivers,
@@ -317,8 +342,8 @@ export class SeedRacingData {
this.logger.info(`[Bootstrap] Computing stats for ${drivers.length} drivers from ${standings.length} standings and ${results.length} results`); this.logger.info(`[Bootstrap] Computing stats for ${drivers.length} drivers from ${standings.length} standings and ${results.length} results`);
for (const driver of drivers) { for (const driver of drivers) {
const driverResults = results.filter(r => r.driverId.toString() === driver.id); const driverResults = results.filter((r: Result) => r.driverId.toString() === driver.id);
const driverStandings = standings.filter(s => s.driverId.toString() === driver.id); const driverStandings = standings.filter((s: Standing) => s.driverId.toString() === driver.id);
if (driverResults.length === 0) continue; if (driverResults.length === 0) continue;
@@ -398,13 +423,13 @@ export class SeedRacingData {
for (const team of teams) { for (const team of teams) {
// Get team members using the correct method // Get team members using the correct method
const teamMemberships = await this.seedDeps.teamMembershipRepository.getTeamMembers(team.id); const teamMemberships = await this.seedDeps.teamMembershipRepository.getTeamMembers(team.id);
const teamMemberIds = teamMemberships.map(m => m.driverId.toString()); const teamMemberIds = teamMemberships.map((m: any) => m.driverId.toString());
// Get results for team members // Get results for team members
const teamResults = results.filter(r => teamMemberIds.includes(r.driverId.toString())); const teamResults = results.filter((r: Result) => teamMemberIds.includes(r.driverId.toString()));
// Get team drivers for name resolution // Get team drivers for name resolution
const teamDrivers = drivers.filter(d => teamMemberIds.includes(d.id)); const teamDrivers = drivers.filter((d: Driver) => teamMemberIds.includes(d.id));
const stats = this.calculateTeamStats(team, teamResults, teamDrivers); const stats = this.calculateTeamStats(team, teamResults, teamDrivers);
await this.seedDeps.teamStatsRepository.saveTeamStats(team.id, stats); await this.seedDeps.teamStatsRepository.saveTeamStats(team.id, stats);

View File

@@ -1,37 +0,0 @@
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Enforce adapter naming conventions',
category: 'Architecture',
recommended: true,
},
fixable: null,
schema: [],
messages: {
invalidNaming: 'Adapter classes should end with "Adapter", "Repository", "Service", "Factory" or "Entity". Found: {{name}}',
},
},
create(context) {
return {
ClassDeclaration(node) {
const filename = context.getFilename();
if (!filename.includes('adapters/')) return;
const name = node.id.name;
const isValidName = /(.+)(Adapter|Factory|Repository|Service|Entity|Mapper|Schema|Guard|Module|Controller)$/.test(name);
if (!isValidName) {
context.report({
node,
messageId: 'invalidNaming',
data: {
name,
},
});
}
},
};
},
};

View File

@@ -1,17 +1,14 @@
const noIndexFiles = require('./no-index-files'); const noIndexFiles = require('./no-index-files');
const adapterNaming = require('./adapter-naming');
module.exports = { module.exports = {
rules: { rules: {
'no-index-files': noIndexFiles, 'no-index-files': noIndexFiles,
'adapter-naming': adapterNaming,
}, },
configs: { configs: {
recommended: { recommended: {
plugins: ['gridpilot-adapters-rules'], plugins: ['gridpilot-adapters-rules'],
rules: { rules: {
'gridpilot-adapters-rules/no-index-files': 'error', 'gridpilot-adapters-rules/no-index-files': 'error',
'gridpilot-adapters-rules/adapter-naming': 'error',
}, },
}, },
}, },

View File

@@ -5,11 +5,10 @@
*/ */
import { ADMIN_ACHIEVEMENTS, COMMUNITY_ACHIEVEMENTS, DRIVER_ACHIEVEMENTS, STEWARD_ACHIEVEMENTS } from "@core/identity/domain/AchievementConstants"; import { ADMIN_ACHIEVEMENTS, COMMUNITY_ACHIEVEMENTS, DRIVER_ACHIEVEMENTS, STEWARD_ACHIEVEMENTS } from "@core/identity/domain/AchievementConstants";
import { Achievement } from "@core/identity/domain/entities/Achievement"; import { Achievement, type AchievementCategory } from "@core/identity/domain/entities/Achievement";
import { UserAchievement } from "@core/identity/domain/entities/UserAchievement"; import { UserAchievement } from "@core/identity/domain/entities/UserAchievement";
import { AchievementRepository } from "@core/identity/domain/repositories/AchievementRepository"; import { AchievementRepository } from "@core/identity/domain/repositories/AchievementRepository";
import { AchievementCategory } from "@core/identity/domain/types/AchievementTypes"; import type { Logger } from "@core/shared/domain/Logger";
import { Logger } from "@core/shared/domain";
export class InMemoryAchievementRepository implements AchievementRepository { export class InMemoryAchievementRepository implements AchievementRepository {
private achievements: Map<string, Achievement> = new Map(); private achievements: Map<string, Achievement> = new Map();

View File

@@ -1,14 +1,14 @@
import { User } from '@core/identity/domain/entities/User'; import { User } from '@core/identity/domain/entities/User';
import { AuthRepository } from '@core/identity/domain/repositories/AuthRepository'; import type { AuthRepository } from '@core/identity/domain/repositories/AuthRepository';
import { StoredUser } from '@core/identity/domain/repositories/UserRepository'; import type { UserRepository, StoredUser } from '@core/identity/domain/repositories/UserRepository';
import type { PasswordHashingService } from '@core/identity/domain/services/PasswordHashingService';
import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress'; import { EmailAddress } from '@core/identity/domain/value-objects/EmailAddress';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryAuthRepository implements AuthRepository { export class InMemoryAuthRepository implements AuthRepository {
constructor( constructor(
private readonly userRepository: IUserRepository, private readonly userRepository: UserRepository,
private readonly passwordHashingService: IPasswordHashingService, private readonly passwordHashingService: PasswordHashingService,
private readonly logger: Logger, private readonly logger: Logger,
) {} ) {}

View File

@@ -1,5 +1,5 @@
import { MagicLinkRepository, PasswordResetRequest } from '@core/identity/domain/repositories/MagicLinkRepository'; import type { MagicLinkRepository, PasswordResetRequest } from '@core/identity/domain/repositories/MagicLinkRepository';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
export class InMemoryMagicLinkRepository implements MagicLinkRepository { export class InMemoryMagicLinkRepository implements MagicLinkRepository {

View File

@@ -4,8 +4,10 @@
* In-memory implementation of ISponsorAccountRepository for development/testing. * In-memory implementation of ISponsorAccountRepository for development/testing.
*/ */
import { SponsorAccount, SponsorAccountRepository, UserId } from '@core/identity'; import { SponsorAccount } from '@core/identity/domain/entities/SponsorAccount';
import { Logger } from '@core/shared/domain'; import type { SponsorAccountRepository } from '@core/identity/domain/repositories/SponsorAccountRepository';
import { UserId } from '@core/identity/domain/value-objects/UserId';
import type { Logger } from '@core/shared/domain/Logger';
export class InMemorySponsorAccountRepository implements SponsorAccountRepository { export class InMemorySponsorAccountRepository implements SponsorAccountRepository {
private accounts: Map<string, SponsorAccount> = new Map(); private accounts: Map<string, SponsorAccount> = new Map();

View File

@@ -4,8 +4,9 @@
* In-memory implementation of IUserRatingRepository * In-memory implementation of IUserRatingRepository
*/ */
import { UserRating, UserRatingRepository } from '@core/identity'; import { UserRating } from '@core/identity/domain/value-objects/UserRating';
import { Logger } from '@core/shared/domain'; import type { UserRatingRepository } from '@core/identity/domain/repositories/UserRatingRepository';
import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryUserRatingRepository implements UserRatingRepository { export class InMemoryUserRatingRepository implements UserRatingRepository {
private ratings: Map<string, UserRating> = new Map(); private ratings: Map<string, UserRating> = new Map();

View File

@@ -5,7 +5,7 @@
*/ */
import type { StoredUser, UserRepository } from '@core/identity/domain/repositories/UserRepository'; import type { StoredUser, UserRepository } from '@core/identity/domain/repositories/UserRepository';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryUserRepository implements UserRepository { export class InMemoryUserRepository implements UserRepository {
private users: Map<string, StoredUser> = new Map(); private users: Map<string, StoredUser> = new Map();

View File

@@ -1,5 +1,5 @@
import { MagicLinkRepository, PasswordResetRequest } from '@core/identity/domain/repositories/MagicLinkRepository'; import type { MagicLinkRepository, PasswordResetRequest } from '@core/identity/domain/repositories/MagicLinkRepository';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result'; import { Result } from '@core/shared/domain/Result';
import type { DataSource } from 'typeorm'; import type { DataSource } from 'typeorm';
import { PasswordResetRequestOrmEntity } from '../entities/PasswordResetRequestOrmEntity'; import { PasswordResetRequestOrmEntity } from '../entities/PasswordResetRequestOrmEntity';

View File

@@ -1,4 +1,4 @@
import { ErrorReporter } from "@core/shared/domain"; import type { ErrorReporter } from "@core/shared/application/ErrorReporter";
export class ConsoleErrorReporter implements ErrorReporter { export class ConsoleErrorReporter implements ErrorReporter {
report(error: Error, context?: unknown): void { report(error: Error, context?: unknown): void {

View File

@@ -1,4 +1,4 @@
import { Logger } from "@core/shared/domain"; import type { Logger } from "@core/shared/domain/Logger";
export class ConsoleLogger implements Logger { export class ConsoleLogger implements Logger {
private formatMessage(level: string, message: string, context?: unknown): string { private formatMessage(level: string, message: string, context?: unknown): string {

View File

@@ -1,6 +1,6 @@
import { AvatarGenerationRequest } from '@core/media/domain/entities/AvatarGenerationRequest'; import { AvatarGenerationRequest } from '@core/media/domain/entities/AvatarGenerationRequest';
import { AvatarGenerationRepository } from '@core/media/domain/repositories/AvatarGenerationRepository'; import type { AvatarGenerationRepository } from '@core/media/domain/repositories/AvatarGenerationRepository';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryAvatarGenerationRepository implements AvatarGenerationRepository { export class InMemoryAvatarGenerationRepository implements AvatarGenerationRepository {
private requests: Map<string, AvatarGenerationRequest> = new Map(); // Key: requestId private requests: Map<string, AvatarGenerationRequest> = new Map(); // Key: requestId

View File

@@ -32,7 +32,7 @@ export class InAppNotificationAdapter implements NotificationGateway {
return { return {
success: true, success: true,
channel: this.channel, channel: this.channel,
externalId: notification.id, externalId: notification.id.value,
attemptedAt: new Date(), attemptedAt: new Date(),
}; };
} }

View File

@@ -1,6 +1,6 @@
import { NotificationPreference } from '@core/notifications/domain/entities/NotificationPreference'; import { NotificationPreference } from '@core/notifications/domain/entities/NotificationPreference';
import { NotificationPreferenceRepository } from '@core/notifications/domain/repositories/NotificationPreferenceRepository'; import type { NotificationPreferenceRepository } from '@core/notifications/domain/repositories/NotificationPreferenceRepository';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryNotificationPreferenceRepository implements NotificationPreferenceRepository { export class InMemoryNotificationPreferenceRepository implements NotificationPreferenceRepository {
private preferences: Map<string, NotificationPreference> = new Map(); private preferences: Map<string, NotificationPreference> = new Map();

View File

@@ -17,8 +17,8 @@ export class InMemoryNotificationRepository implements NotificationRepository {
this.logger = logger; this.logger = logger;
this.logger.info('InMemoryNotificationRepository initialized.'); this.logger.info('InMemoryNotificationRepository initialized.');
initialNotifications.forEach(notification => { initialNotifications.forEach(notification => {
this.notifications.set(notification.id, notification); this.notifications.set(notification.id.value, notification);
this.logger.debug(`Seeded notification: ${notification.id}`); this.logger.debug(`Seeded notification: ${notification.id.value}`);
}); });
} }
@@ -95,31 +95,31 @@ export class InMemoryNotificationRepository implements NotificationRepository {
} }
async create(notification: Notification): Promise<void> { async create(notification: Notification): Promise<void> {
this.logger.debug(`Creating notification: ${notification.id}`); this.logger.debug(`Creating notification: ${notification.id.value}`);
try { try {
if (this.notifications.has(notification.id)) { if (this.notifications.has(notification.id.value)) {
this.logger.warn(`Notification with ID ${notification.id} already exists. Throwing error.`); this.logger.warn(`Notification with ID ${notification.id.value} already exists. Throwing error.`);
throw new Error(`Notification with ID ${notification.id} already exists`); throw new Error(`Notification with ID ${notification.id.value} already exists`);
} }
this.notifications.set(notification.id, notification); this.notifications.set(notification.id.value, notification);
this.logger.info(`Notification ${notification.id} created successfully.`); this.logger.info(`Notification ${notification.id.value} created successfully.`);
} catch (error) { } catch (error) {
this.logger.error(`Error creating notification ${notification.id}:`, error instanceof Error ? error : new Error(String(error))); this.logger.error(`Error creating notification ${notification.id.value}:`, error instanceof Error ? error : new Error(String(error)));
throw error; throw error;
} }
} }
async update(notification: Notification): Promise<void> { async update(notification: Notification): Promise<void> {
this.logger.debug(`Updating notification: ${notification.id}`); this.logger.debug(`Updating notification: ${notification.id.value}`);
try { try {
if (!this.notifications.has(notification.id)) { if (!this.notifications.has(notification.id.value)) {
this.logger.warn(`Notification with ID ${notification.id} not found for update. Throwing error.`); this.logger.warn(`Notification with ID ${notification.id.value} not found for update. Throwing error.`);
throw new Error(`Notification with ID ${notification.id} not found`); throw new Error(`Notification with ID ${notification.id.value} not found`);
} }
this.notifications.set(notification.id, notification); this.notifications.set(notification.id.value, notification);
this.logger.info(`Notification ${notification.id} updated successfully.`); this.logger.info(`Notification ${notification.id.value} updated successfully.`);
} catch (error) { } catch (error) {
this.logger.error(`Error updating notification ${notification.id}:`, error instanceof Error ? error : new Error(String(error))); this.logger.error(`Error updating notification ${notification.id.value}:`, error instanceof Error ? error : new Error(String(error)));
throw error; throw error;
} }
} }
@@ -143,7 +143,7 @@ export class InMemoryNotificationRepository implements NotificationRepository {
try { try {
const toDelete = Array.from(this.notifications.values()) const toDelete = Array.from(this.notifications.values())
.filter(n => n.recipientId === recipientId) .filter(n => n.recipientId === recipientId)
.map(n => n.id); .map(n => n.id.value);
toDelete.forEach(id => this.notifications.delete(id)); toDelete.forEach(id => this.notifications.delete(id));
this.logger.info(`Deleted ${toDelete.length} notifications for recipient ID: ${recipientId}.`); this.logger.info(`Deleted ${toDelete.length} notifications for recipient ID: ${recipientId}.`);
@@ -163,7 +163,7 @@ export class InMemoryNotificationRepository implements NotificationRepository {
toUpdate.forEach(n => { toUpdate.forEach(n => {
const updated = n.markAsRead(); const updated = n.markAsRead();
this.notifications.set(updated.id, updated); this.notifications.set(updated.id.value, updated);
}); });
this.logger.info(`Marked ${toUpdate.length} notifications as read for recipient ID: ${recipientId}.`); this.logger.info(`Marked ${toUpdate.length} notifications as read for recipient ID: ${recipientId}.`);
} catch (error) { } catch (error) {

View File

@@ -1,5 +1,5 @@
import { MagicLinkNotificationInput, MagicLinkNotificationPort } from '@core/identity/domain/ports/MagicLinkNotificationPort'; import type { MagicLinkNotificationInput, MagicLinkNotificationPort } from '@core/identity/domain/ports/MagicLinkNotificationPort';
import { Logger } from '@core/shared/domain'; import type { Logger } from '@core/shared/domain/Logger';
/** /**
* Console adapter for magic link notifications * Console adapter for magic link notifications

View File

@@ -1,6 +1,8 @@
import type { NotificationGatewayRegistry } from '@core/notifications/application/ports/NotificationGateway'; import type { NotificationGatewayRegistry } from '@core/notifications/application/ports/NotificationGateway';
import type { NotificationService, SendNotificationCommand } from '@core/notifications/application/ports/NotificationService'; import type { NotificationService, SendNotificationCommand } from '@core/notifications/application/ports/NotificationService';
import { SendNotificationUseCase } from '@core/notifications/application/use-cases/SendNotificationUseCase'; import { SendNotificationUseCase } from '@core/notifications/application/use-cases/SendNotificationUseCase';
import type { NotificationRepository } from '@core/notifications/domain/repositories/NotificationRepository';
import type { NotificationPreferenceRepository } from '@core/notifications/domain/repositories/NotificationPreferenceRepository';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
export class NotificationServiceAdapter implements NotificationService { export class NotificationServiceAdapter implements NotificationService {
@@ -8,8 +10,8 @@ export class NotificationServiceAdapter implements NotificationService {
private readonly logger: Logger; private readonly logger: Logger;
constructor( constructor(
notificationRepository: INotificationRepository, notificationRepository: NotificationRepository,
preferenceRepository: INotificationPreferenceRepository, preferenceRepository: NotificationPreferenceRepository,
gatewayRegistry: NotificationGatewayRegistry, gatewayRegistry: NotificationGatewayRegistry,
logger: Logger, logger: Logger,
) { ) {

View File

@@ -4,7 +4,7 @@
import type { MemberPayment } from '@core/payments/domain/entities/MemberPayment'; import type { MemberPayment } from '@core/payments/domain/entities/MemberPayment';
import type { MembershipFee } from '@core/payments/domain/entities/MembershipFee'; import type { MembershipFee } from '@core/payments/domain/entities/MembershipFee';
import type { MembershipFeeRepository } from '@core/payments/domain/repositories/MembershipFeeRepository'; import type { MembershipFeeRepository, MemberPaymentRepository } from '@core/payments/domain/repositories/MembershipFeeRepository';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
const membershipFees: Map<string, MembershipFee> = new Map(); const membershipFees: Map<string, MembershipFee> = new Map();
@@ -51,7 +51,7 @@ export class InMemoryMemberPaymentRepository implements MemberPaymentRepository
) || null; ) || null;
} }
async findByLeagueIdAndDriverId(leagueId: string, driverId: string, membershipFeeRepo: IMembershipFeeRepository): Promise<MemberPayment[]> { async findByLeagueIdAndDriverId(leagueId: string, driverId: string, membershipFeeRepo: MembershipFeeRepository): Promise<MemberPayment[]> {
this.logger.debug('[InMemoryMemberPaymentRepository] findByLeagueIdAndDriverId', { leagueId, driverId }); this.logger.debug('[InMemoryMemberPaymentRepository] findByLeagueIdAndDriverId', { leagueId, driverId });
const results: MemberPayment[] = []; const results: MemberPayment[] = [];
for (const payment of memberPayments.values()) { for (const payment of memberPayments.values()) {

View File

@@ -3,7 +3,7 @@
*/ */
import type { Transaction, Wallet } from '@core/payments/domain/entities/Wallet'; import type { Transaction, Wallet } from '@core/payments/domain/entities/Wallet';
import type { WalletRepository } from '@core/payments/domain/repositories/WalletRepository'; import type { WalletRepository, TransactionRepository } from '@core/payments/domain/repositories/WalletRepository';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
const wallets: Map<string, Wallet> = new Map(); const wallets: Map<string, Wallet> = new Map();

View File

@@ -2,7 +2,7 @@ import type { DataSource } from 'typeorm';
import type { MemberPayment } from '@core/payments/domain/entities/MemberPayment'; import type { MemberPayment } from '@core/payments/domain/entities/MemberPayment';
import type { MembershipFee } from '@core/payments/domain/entities/MembershipFee'; import type { MembershipFee } from '@core/payments/domain/entities/MembershipFee';
import type { IMembershipFeeRepository, MemberPaymentRepository } from '@core/payments/domain/repositories/MembershipFeeRepository'; import type { MembershipFeeRepository, MemberPaymentRepository } from '@core/payments/domain/repositories/MembershipFeeRepository';
import { PaymentsMemberPaymentOrmEntity } from '../entities/PaymentsMemberPaymentOrmEntity'; import { PaymentsMemberPaymentOrmEntity } from '../entities/PaymentsMemberPaymentOrmEntity';
import { PaymentsMembershipFeeOrmEntity } from '../entities/PaymentsMembershipFeeOrmEntity'; import { PaymentsMembershipFeeOrmEntity } from '../entities/PaymentsMembershipFeeOrmEntity';
@@ -61,7 +61,7 @@ export class TypeOrmMemberPaymentRepository implements MemberPaymentRepository {
async findByLeagueIdAndDriverId( async findByLeagueIdAndDriverId(
leagueId: string, leagueId: string,
driverId: string, driverId: string,
membershipFeeRepo: IMembershipFeeRepository, membershipFeeRepo: MembershipFeeRepository,
): Promise<MemberPayment[]> { ): Promise<MemberPayment[]> {
const fee = await membershipFeeRepo.findByLeagueId(leagueId); const fee = await membershipFeeRepo.findByLeagueId(leagueId);
if (!fee) { if (!fee) {

View File

@@ -1,7 +1,7 @@
import type { DataSource } from 'typeorm'; import type { DataSource } from 'typeorm';
import type { Transaction, Wallet } from '@core/payments/domain/entities/Wallet'; import type { Transaction, Wallet } from '@core/payments/domain/entities/Wallet';
import type { TransactionRepository } from '@core/payments/domain/repositories/WalletRepository'; import type { WalletRepository, TransactionRepository } from '@core/payments/domain/repositories/WalletRepository';
import { PaymentsTransactionOrmEntity } from '../entities/PaymentsTransactionOrmEntity'; import { PaymentsTransactionOrmEntity } from '../entities/PaymentsTransactionOrmEntity';
import { PaymentsWalletOrmEntity } from '../entities/PaymentsWalletOrmEntity'; import { PaymentsWalletOrmEntity } from '../entities/PaymentsWalletOrmEntity';

View File

@@ -16,8 +16,8 @@ export class InMemoryPenaltyRepository implements PenaltyRepository {
this.logger = logger; this.logger = logger;
this.logger.info('InMemoryPenaltyRepository initialized.'); this.logger.info('InMemoryPenaltyRepository initialized.');
initialPenalties.forEach(penalty => { initialPenalties.forEach(penalty => {
this.penalties.set(penalty.id, penalty); this.penalties.set(penalty.id.toString(), penalty);
this.logger.debug(`Seeded penalty: ${penalty.id}`); this.logger.debug(`Seeded penalty: ${penalty.id.toString()}`);
}); });
} }
@@ -108,31 +108,31 @@ export class InMemoryPenaltyRepository implements PenaltyRepository {
} }
async create(penalty: Penalty): Promise<void> { async create(penalty: Penalty): Promise<void> {
this.logger.debug(`Creating penalty: ${penalty.id}`); this.logger.debug(`Creating penalty: ${penalty.id.toString()}`);
try { try {
if (this.penalties.has(penalty.id)) { if (this.penalties.has(penalty.id.toString())) {
this.logger.warn(`Penalty with ID ${penalty.id} already exists.`); this.logger.warn(`Penalty with ID ${penalty.id.toString()} already exists.`);
throw new Error(`Penalty with ID ${penalty.id} already exists`); throw new Error(`Penalty with ID ${penalty.id.toString()} already exists`);
} }
this.penalties.set(penalty.id, penalty); this.penalties.set(penalty.id.toString(), penalty);
this.logger.info(`Penalty ${penalty.id} created successfully.`); this.logger.info(`Penalty ${penalty.id.toString()} created successfully.`);
} catch (error) { } catch (error) {
this.logger.error(`Error creating penalty ${penalty.id}:`, error instanceof Error ? error : new Error(String(error))); this.logger.error(`Error creating penalty ${penalty.id.toString()}:`, error instanceof Error ? error : new Error(String(error)));
throw error; throw error;
} }
} }
async update(penalty: Penalty): Promise<void> { async update(penalty: Penalty): Promise<void> {
this.logger.debug(`Updating penalty: ${penalty.id}`); this.logger.debug(`Updating penalty: ${penalty.id.toString()}`);
try { try {
if (!this.penalties.has(penalty.id)) { if (!this.penalties.has(penalty.id.toString())) {
this.logger.warn(`Penalty with ID ${penalty.id} not found for update.`); this.logger.warn(`Penalty with ID ${penalty.id.toString()} not found for update.`);
throw new Error(`Penalty with ID ${penalty.id} not found`); throw new Error(`Penalty with ID ${penalty.id.toString()} not found`);
} }
this.penalties.set(penalty.id, penalty); this.penalties.set(penalty.id.toString(), penalty);
this.logger.info(`Penalty ${penalty.id} updated successfully.`); this.logger.info(`Penalty ${penalty.id.toString()} updated successfully.`);
} catch (error) { } catch (error) {
this.logger.error(`Error updating penalty ${penalty.id}:`, error instanceof Error ? error : new Error(String(error))); this.logger.error(`Error updating penalty ${penalty.id.toString()}:`, error instanceof Error ? error : new Error(String(error)));
throw error; throw error;
} }
} }

View File

@@ -7,15 +7,16 @@
import { Result } from '@core/racing/domain/entities/result/Result'; import { Result } from '@core/racing/domain/entities/result/Result';
import type { ResultRepository } from '@core/racing/domain/repositories/ResultRepository'; import type { ResultRepository } from '@core/racing/domain/repositories/ResultRepository';
import type { RaceRepository } from '@core/racing/domain/repositories/RaceRepository';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
export class InMemoryResultRepository implements ResultRepository { export class InMemoryResultRepository implements ResultRepository {
private results: Map<string, Result>; private results: Map<string, Result>;
private raceRepository: IRaceRepository | null; private raceRepository: RaceRepository | null;
private readonly logger: Logger; private readonly logger: Logger;
constructor(logger: Logger, raceRepository?: IRaceRepository | null) { constructor(logger: Logger, raceRepository?: RaceRepository | null) {
this.logger = logger; this.logger = logger;
this.logger.info('[InMemoryResultRepository] Initialized.'); this.logger.info('[InMemoryResultRepository] Initialized.');
this.results = new Map(); this.results = new Map();
@@ -86,7 +87,7 @@ export class InMemoryResultRepository implements ResultRepository {
} }
const leagueRaces = await this.raceRepository.findByLeagueId(leagueId); const leagueRaces = await this.raceRepository.findByLeagueId(leagueId);
const leagueRaceIds = new Set(leagueRaces.map(race => race.id)); const leagueRaceIds = new Set(leagueRaces.map((race: any) => race.id));
this.logger.debug(`[InMemoryResultRepository] Found ${leagueRaces.length} races in league ${leagueId}.`); this.logger.debug(`[InMemoryResultRepository] Found ${leagueRaces.length} races in league ${leagueId}.`);
const results = Array.from(this.results.values()) const results = Array.from(this.results.values())

View File

@@ -7,22 +7,25 @@
import { Standing } from '@core/racing/domain/entities/Standing'; import { Standing } from '@core/racing/domain/entities/Standing';
import type { StandingRepository } from '@core/racing/domain/repositories/StandingRepository'; import type { StandingRepository } from '@core/racing/domain/repositories/StandingRepository';
import type { ResultRepository } from '@core/racing/domain/repositories/ResultRepository';
import type { RaceRepository } from '@core/racing/domain/repositories/RaceRepository';
import type { LeagueRepository } from '@core/racing/domain/repositories/LeagueRepository';
import type { Logger } from '@core/shared/domain/Logger'; import type { Logger } from '@core/shared/domain/Logger';
export class InMemoryStandingRepository implements StandingRepository { export class InMemoryStandingRepository implements StandingRepository {
private standings: Map<string, Standing>; private standings: Map<string, Standing>;
private resultRepository: IResultRepository | null; private resultRepository: ResultRepository | null;
private raceRepository: IRaceRepository | null; private raceRepository: RaceRepository | null;
private leagueRepository: ILeagueRepository | null; private leagueRepository: LeagueRepository | null;
private readonly logger: Logger; private readonly logger: Logger;
private readonly pointsSystems: Record<string, Record<number, number>>; private readonly pointsSystems: Record<string, Record<number, number>>;
constructor( constructor(
logger: Logger, logger: Logger,
pointsSystems: Record<string, Record<number, number>>, pointsSystems: Record<string, Record<number, number>>,
resultRepository?: IResultRepository | null, resultRepository?: ResultRepository | null,
raceRepository?: IRaceRepository | null, raceRepository?: RaceRepository | null,
leagueRepository?: ILeagueRepository | null leagueRepository?: LeagueRepository | null
) { ) {
this.logger = logger; this.logger = logger;
this.pointsSystems = pointsSystems; this.pointsSystems = pointsSystems;
@@ -198,7 +201,7 @@ export class InMemoryStandingRepository implements StandingRepository {
} }
const allResults = await Promise.all( const allResults = await Promise.all(
races.map(async race => { races.map(async (race: any) => {
this.logger.debug(`Fetching results for race ${race.id}.`); this.logger.debug(`Fetching results for race ${race.id}.`);
const results = await this.resultRepository!.findByRaceId(race.id); const results = await this.resultRepository!.findByRaceId(race.id);
this.logger.debug(`Found ${results.length} results for race ${race.id}.`); this.logger.debug(`Found ${results.length} results for race ${race.id}.`);
@@ -219,7 +222,7 @@ export class InMemoryStandingRepository implements StandingRepository {
return Number(position); return Number(position);
}; };
results.forEach((result) => { results.forEach((result: any) => {
const driverIdStr = result.driverId.toString(); const driverIdStr = result.driverId.toString();
let standing = standingsMap.get(driverIdStr); let standing = standingsMap.get(driverIdStr);

View File

@@ -136,7 +136,7 @@ function serializeProtestDefense(defense: Protest['defense']): SerializedProtest
export class PenaltyOrmMapper { export class PenaltyOrmMapper {
toOrmEntity(domain: Penalty): PenaltyOrmEntity { toOrmEntity(domain: Penalty): PenaltyOrmEntity {
const entity = new PenaltyOrmEntity(); const entity = new PenaltyOrmEntity();
entity.id = domain.id; entity.id = domain.id.toString();
entity.leagueId = domain.leagueId; entity.leagueId = domain.leagueId;
entity.raceId = domain.raceId; entity.raceId = domain.raceId;
entity.driverId = domain.driverId; entity.driverId = domain.driverId;
@@ -197,7 +197,7 @@ export class PenaltyOrmMapper {
export class ProtestOrmMapper { export class ProtestOrmMapper {
toOrmEntity(domain: Protest): ProtestOrmEntity { toOrmEntity(domain: Protest): ProtestOrmEntity {
const entity = new ProtestOrmEntity(); const entity = new ProtestOrmEntity();
entity.id = domain.id; entity.id = domain.id.toString();
entity.raceId = domain.raceId; entity.raceId = domain.raceId;
entity.protestingDriverId = domain.protestingDriverId; entity.protestingDriverId = domain.protestingDriverId;
entity.accusedDriverId = domain.accusedDriverId; entity.accusedDriverId = domain.accusedDriverId;

View File

@@ -0,0 +1,7 @@
export * from './DomainEvent';
export * from './Entity';
export * from './Logger';
export * from './Option';
export * from './Result';
export * from './Service';
export * from './ValueObject';