fix issues

This commit is contained in:
2026-01-01 15:17:09 +01:00
parent f001df3744
commit aee182b09e
17 changed files with 241 additions and 442 deletions

View File

@@ -1,3 +1,4 @@
import type { Provider } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { InMemoryAdminPersistenceModule } from '../../persistence/inmemory/InMemoryAdminPersistenceModule';
import { AdminService } from './AdminService';
@@ -7,6 +8,7 @@ import { DashboardStatsPresenter } from './presenters/DashboardStatsPresenter';
import { AuthModule } from '../auth/AuthModule';
import { ListUsersUseCase } from '@core/admin/application/use-cases/ListUsersUseCase';
import { GetDashboardStatsUseCase } from './use-cases/GetDashboardStatsUseCase';
import { InitializationLogger } from '../../shared/logging/InitializationLogger';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { ListUsersResult } from '@core/admin/application/use-cases/ListUsersUseCase';
import type { DashboardStatsResult } from './use-cases/GetDashboardStatsUseCase';
@@ -16,38 +18,80 @@ export const ADMIN_USER_REPOSITORY_TOKEN = 'IAdminUserRepository';
export const LIST_USERS_OUTPUT_PORT_TOKEN = 'ListUsersOutputPort';
export const DASHBOARD_STATS_OUTPUT_PORT_TOKEN = 'DashboardStatsOutputPort';
const initLogger = InitializationLogger.getInstance();
const adminProviders: Provider[] = [
AdminService,
ListUsersPresenter,
DashboardStatsPresenter,
{
provide: LIST_USERS_OUTPUT_PORT_TOKEN,
useExisting: ListUsersPresenter,
},
{
provide: DASHBOARD_STATS_OUTPUT_PORT_TOKEN,
useExisting: DashboardStatsPresenter,
},
{
provide: ListUsersUseCase,
useFactory: (
repository: IAdminUserRepository,
output: UseCaseOutputPort<ListUsersResult>,
) => new ListUsersUseCase(repository, output),
inject: [ADMIN_USER_REPOSITORY_TOKEN, LIST_USERS_OUTPUT_PORT_TOKEN],
},
{
provide: GetDashboardStatsUseCase,
useFactory: (
repository: IAdminUserRepository,
output: UseCaseOutputPort<DashboardStatsResult>,
) => new GetDashboardStatsUseCase(repository, output),
inject: [ADMIN_USER_REPOSITORY_TOKEN, DASHBOARD_STATS_OUTPUT_PORT_TOKEN],
},
];
// Diagnostics: Nest will crash with "metatype is not a constructor" if any provider resolves
// to a non-constructable value (e.g. undefined import, object, etc.).
for (const provider of adminProviders) {
// Class providers are functions at runtime.
if (typeof provider === 'function') {
continue;
}
// Custom providers should be objects with a `provide` token.
if (!provider || typeof provider !== 'object' || !('provide' in provider)) {
initLogger.error(
`[AdminModule] Invalid provider entry (expected class or provider object): ${String(provider)}`,
);
continue;
}
const token = (provider as { provide: unknown }).provide;
const tokenLabel = typeof token === 'function' ? token.name : String(token);
if ('useClass' in provider) {
const useClass = (provider as { useClass?: unknown }).useClass;
if (typeof useClass !== 'function') {
initLogger.error(
`[AdminModule] Provider "${tokenLabel}" has non-constructable useClass: ${String(useClass)}`,
);
}
}
if ('useExisting' in provider) {
const useExisting = (provider as { useExisting?: unknown }).useExisting;
if (typeof useExisting !== 'function' && typeof useExisting !== 'string' && typeof useExisting !== 'symbol') {
initLogger.warn(
`[AdminModule] Provider "${tokenLabel}" has suspicious useExisting: ${String(useExisting)}`,
);
}
}
}
@Module({
imports: [InMemoryAdminPersistenceModule, AuthModule],
controllers: [AdminController],
providers: [
AdminService,
ListUsersPresenter,
DashboardStatsPresenter,
{
provide: LIST_USERS_OUTPUT_PORT_TOKEN,
useExisting: ListUsersPresenter,
},
{
provide: DASHBOARD_STATS_OUTPUT_PORT_TOKEN,
useExisting: DashboardStatsPresenter,
},
{
provide: ListUsersUseCase,
useFactory: (
repository: IAdminUserRepository,
output: UseCaseOutputPort<ListUsersResult>,
) => new ListUsersUseCase(repository, output),
inject: [ADMIN_USER_REPOSITORY_TOKEN, LIST_USERS_OUTPUT_PORT_TOKEN],
},
{
provide: GetDashboardStatsUseCase,
useFactory: (
repository: IAdminUserRepository,
output: UseCaseOutputPort<DashboardStatsResult>,
) => new GetDashboardStatsUseCase(repository, output),
inject: [ADMIN_USER_REPOSITORY_TOKEN, DASHBOARD_STATS_OUTPUT_PORT_TOKEN],
},
],
providers: [...adminProviders],
exports: [AdminService],
})
export class AdminModule {}
export class AdminModule {}

View File

@@ -25,15 +25,25 @@ export class AuthSessionDTO {
export class SignupParamsDTO {
@ApiProperty()
@IsEmail()
email!: string;
@ApiProperty()
@IsString()
@MinLength(8)
password!: string;
@ApiProperty()
@IsString()
@MinLength(2)
displayName!: string;
@ApiProperty({ required: false })
iracingCustomerId?: string;
@ApiProperty({ required: false })
primaryDriverId?: string;
@ApiProperty({ required: false, nullable: true })
avatarUrl?: string | null;
}

View File

@@ -11,4 +11,4 @@ import { DashboardProviders } from './DashboardProviders';
providers: [DashboardService, ...DashboardProviders],
exports: [DashboardService],
})
export class DashboardModule {}
export class DashboardModule {}

View File

@@ -20,20 +20,34 @@ import { DashboardOverviewUseCase } from '@core/racing/application/use-cases/Das
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresenter';
import { DashboardService } from './DashboardService';
import {
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
IMAGE_SERVICE_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
LEAGUE_REPOSITORY_TOKEN,
LOGGER_TOKEN,
RACE_REGISTRATION_REPOSITORY_TOKEN,
RACE_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
} from './DashboardTokens';
// Define injection tokens
export const LOGGER_TOKEN = 'Logger';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const DASHBOARD_OVERVIEW_USE_CASE_TOKEN = 'DashboardOverviewUseCase';
export const DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN = 'DashboardOverviewOutputPort';
// Re-export tokens for convenience (legacy imports)
export {
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
IMAGE_SERVICE_TOKEN,
LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
LEAGUE_REPOSITORY_TOKEN,
LOGGER_TOKEN,
RACE_REGISTRATION_REPOSITORY_TOKEN,
RACE_REPOSITORY_TOKEN,
RESULT_REPOSITORY_TOKEN,
STANDING_REPOSITORY_TOKEN,
} from './DashboardTokens';
export const DashboardProviders: Provider[] = [
DashboardOverviewPresenter,
@@ -93,19 +107,4 @@ export const DashboardProviders: Provider[] = [
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
],
},
{
provide: DashboardService,
useFactory: (
logger: Logger,
dashboardOverviewUseCase: DashboardOverviewUseCase,
presenter: DashboardOverviewPresenter,
imageService: ImageServicePort,
) => new DashboardService(logger, dashboardOverviewUseCase, presenter, imageService),
inject: [
LOGGER_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
IMAGE_SERVICE_TOKEN,
],
},
];
];

View File

@@ -7,8 +7,13 @@ import { DashboardOverviewPresenter } from './presenters/DashboardOverviewPresen
import type { Logger } from '@core/shared/application/Logger';
import type { ImageServicePort } from '@core/media/application/ports/ImageServicePort';
// Tokens
import { DASHBOARD_OVERVIEW_USE_CASE_TOKEN, LOGGER_TOKEN, DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN, IMAGE_SERVICE_TOKEN } from './DashboardProviders';
// Tokens (standalone to avoid circular imports)
import {
DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN,
DASHBOARD_OVERVIEW_USE_CASE_TOKEN,
IMAGE_SERVICE_TOKEN,
LOGGER_TOKEN,
} from './DashboardTokens';
@Injectable()
export class DashboardService {
@@ -228,4 +233,4 @@ export class DashboardService {
friends: [],
};
}
}
}

View File

@@ -0,0 +1,16 @@
// Dashboard injection tokens
// NOTE: kept in a standalone file to avoid circular imports between
// providers and services.
export const LOGGER_TOKEN = 'Logger';
export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
export const RACE_REPOSITORY_TOKEN = 'IRaceRepository';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository';
export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository';
export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository';
export const RACE_REGISTRATION_REPOSITORY_TOKEN = 'IRaceRegistrationRepository';
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
export const DASHBOARD_OVERVIEW_USE_CASE_TOKEN = 'DashboardOverviewUseCase';
export const DASHBOARD_OVERVIEW_OUTPUT_PORT_TOKEN = 'DashboardOverviewOutputPort';

View File

@@ -87,13 +87,17 @@ async function bootstrap() {
// Handle uncaught errors
process.on('uncaughtException', (error) => {
console.error('🚨 Uncaught Exception:', error.message);
console.error('🚨 Uncaught Exception:', error.stack ?? error.message);
process.exit(1);
});
process.on('unhandledRejection', (reason: unknown) => {
console.error('🚨 Unhandled Rejection:', reason instanceof Error ? reason.message : reason);
if (reason instanceof Error) {
console.error('🚨 Unhandled Rejection:', reason.stack ?? reason.message);
} else {
console.error('🚨 Unhandled Rejection:', reason);
}
process.exit(1);
});
bootstrap();
bootstrap();