fix issues in core

This commit is contained in:
2025-12-23 17:31:45 +01:00
parent d04a21fe02
commit 4318b380d9
34 changed files with 116 additions and 103 deletions

View File

@@ -7,6 +7,7 @@
"sourceType": "module",
"ecmaVersion": 2022
},
"ignorePatterns": ["**/dist/**", "**/*.d.ts"],
"settings": {
"import/resolver": {
"typescript": {}
@@ -83,6 +84,54 @@
]
}
},
{
"files": ["core/**/application/dto/**/*.ts", "core/**/application/dtos/**/*.ts"],
"rules": {
"no-restricted-syntax": [
"error",
{
"selector": "Program",
"message": "core/*/application/dto is forbidden. Use application result models + output ports; DTOs belong in API/website layers."
}
]
}
},
{
"files": ["core/**/infrastructure/**/*.ts"],
"rules": {
"no-restricted-syntax": [
"error",
{
"selector": "Program",
"message": "core/*/infrastructure is forbidden. Implementations must live in adapters/ and be wired in apps/."
}
]
}
},
{
"files": ["core/**/domain/ports/**/*.ts"],
"rules": {
"no-restricted-syntax": [
"error",
{
"selector": "Program",
"message": "core/*/domain/ports is forbidden. Ports belong in application/ports (or shared application layer), not domain."
}
]
}
},
{
"files": ["core/**/shared/presentation/**/*.ts"],
"rules": {
"no-restricted-syntax": [
"error",
{
"selector": "Program",
"message": "core/shared/presentation is forbidden. Presentation belongs in API or website layers."
}
]
}
},
{
"files": ["apps/website/**/*.ts"],
"rules": {

View File

@@ -4,13 +4,12 @@
* Manages user session using cookies. This is a placeholder implementation.
*/
import type { AuthenticatedUserDTO } from '@core/identity/application/dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '@core/identity/application/dto/AuthSessionDTO';
import type { IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
import { Logger } from '@core/shared/application';
import type { AuthenticatedUser } from '@core/identity/application/ports/IdentityProviderPort';
import type { AuthSession, IdentitySessionPort } from '@core/identity/application/ports/IdentitySessionPort';
import type { Logger } from '@core/shared/application';
export class CookieIdentitySessionAdapter implements IdentitySessionPort {
private currentSession: AuthSessionDTO | null = null;
private currentSession: AuthSession | null = null;
constructor(private readonly logger: Logger) {
this.logger.info('CookieIdentitySessionAdapter initialized.');
@@ -18,14 +17,14 @@ export class CookieIdentitySessionAdapter implements IdentitySessionPort {
// For demo, we'll start with no session.
}
async getCurrentSession(): Promise<AuthSessionDTO | null> {
async getCurrentSession(): Promise<AuthSession | null> {
this.logger.debug('[CookieIdentitySessionAdapter] Getting current session.');
return Promise.resolve(this.currentSession);
}
async createSession(user: AuthenticatedUserDTO): Promise<AuthSessionDTO> {
async createSession(user: AuthenticatedUser): Promise<AuthSession> {
this.logger.debug(`[CookieIdentitySessionAdapter] Creating session for user: ${user.id}`);
const newSession: AuthSessionDTO = {
const newSession: AuthSession = {
user: user,
issuedAt: Date.now(),
expiresAt: Date.now() + 3600 * 1000, // 1 hour expiration

View File

@@ -33,7 +33,7 @@ import { InMemoryDriverRatingProvider } from '@adapters/racing/ports/InMemoryDri
import { InMemoryDriverStatsService } from '@adapters/racing/services/InMemoryDriverStatsService';
import { InMemoryRankingService } from '@adapters/racing/services/InMemoryRankingService';
import { IImageServicePort } from '@core/racing/application/ports/IImageServicePort';
import { InMemorySocialGraphRepository } from '@core/social/infrastructure/inmemory/InMemorySocialAndFeed';
import { InMemorySocialGraphRepository } from '@adapters/social/persistence/inmemory/InMemorySocialAndFeed';
// Import presenters
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';

View File

@@ -1,4 +1,4 @@
import { Presenter } from '@core/shared/presentation';
import type { Presenter } from '../../../shared/presentation/Presenter';
import type { GetLeagueProtestsResult } from '@core/racing/application/use-cases/GetLeagueProtestsUseCase';
import { LeagueAdminProtestsDTO } from '../dtos/LeagueAdminProtestsDTO';
import { ProtestDTO } from '../dtos/ProtestDTO';

View File

@@ -1,4 +1,4 @@
import { Presenter } from '@core/shared/presentation';
import type { Presenter } from '../../../shared/presentation/Presenter';
import type { GetLeagueSeasonsResult } from '@core/racing/application/use-cases/GetLeagueSeasonsUseCase';
import { LeagueSeasonSummaryDTO } from '../dtos/LeagueSeasonSummaryDTO';

View File

@@ -1,6 +1,6 @@
import type { GetLeagueStandingsResult } from '@core/racing/application/use-cases/GetLeagueStandingsUseCase';
import { LeagueStandingsDTO } from '../dtos/LeagueStandingsDTO';
import type { Presenter } from '@core/shared/presentation';
import type { Presenter } from '../../../shared/presentation/Presenter';
export class LeagueStandingsPresenter implements Presenter<GetLeagueStandingsResult, LeagueStandingsDTO> {
private result: LeagueStandingsDTO | null = null;

View File

@@ -1,6 +1,6 @@
import type { GetLeagueStatsResult } from '@core/racing/application/use-cases/GetLeagueStatsUseCase';
import { LeagueStatsDTO } from '../dtos/LeagueStatsDTO';
import type { Presenter } from '@core/shared/presentation';
import type { Presenter } from '../../../shared/presentation/Presenter';
export class LeagueStatsPresenter implements Presenter<GetLeagueStatsResult, LeagueStatsDTO> {
private result: LeagueStatsDTO | null = null;

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { AwardPrizeResultDTO } from '../dtos/AwardPrizeDTO';
export interface IAwardPrizePresenter extends Presenter<AwardPrizeResultDTO, AwardPrizeResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { CreatePrizeResultDTO } from '../dtos/CreatePrizeDTO';
export interface ICreatePrizePresenter extends Presenter<CreatePrizeResultDTO, CreatePrizeResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { DeletePrizeResultDTO } from '../dtos/DeletePrizeDTO';
export interface IDeletePrizePresenter extends Presenter<DeletePrizeResultDTO, DeletePrizeResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { GetMembershipFeesResultDTO } from '../dtos/GetMembershipFeesDTO';
export interface IGetMembershipFeesPresenter extends Presenter<GetMembershipFeesResultDTO, GetMembershipFeesResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { GetPrizesResultDTO } from '../dtos/GetPrizesDTO';
export interface IGetPrizesPresenter extends Presenter<GetPrizesResultDTO, GetPrizesResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { GetWalletResultDTO } from '../dtos/GetWalletDTO';
export interface IGetWalletPresenter extends Presenter<GetWalletResultDTO, GetWalletResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { ProcessWalletTransactionResultDTO } from '../dtos/ProcessWalletTransactionDTO';
export interface IProcessWalletTransactionPresenter extends Presenter<ProcessWalletTransactionResultDTO, ProcessWalletTransactionResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { UpdateMemberPaymentResultDTO } from '../dtos/UpdateMemberPaymentDTO';
export interface IUpdateMemberPaymentPresenter extends Presenter<UpdateMemberPaymentResultDTO, UpdateMemberPaymentResultDTO> {}

View File

@@ -1,4 +1,4 @@
import type { Presenter } from '@core/shared/presentation/Presenter';
import type { Presenter } from '../../../shared/presentation/Presenter';
import { UpsertMembershipFeeResultDTO } from '../dtos/UpsertMembershipFeeDTO';
export interface IUpsertMembershipFeePresenter extends Presenter<UpsertMembershipFeeResultDTO, UpsertMembershipFeeResultDTO> {}

View File

@@ -0,0 +1,5 @@
export interface Presenter<Input, ResponseModel> {
present(input: Input): void;
getResponseModel(): ResponseModel | null;
reset(): void;
}

View File

@@ -1,6 +0,0 @@
export interface Logger {
debug(message: string, ...args: unknown[]): void;
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
}

View File

@@ -1,7 +1,6 @@
import { describe, it, expect, vi, type Mock } from 'vitest';
import { GetCurrentUserSessionUseCase } from './GetCurrentUserSessionUseCase';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
describe('GetCurrentUserSessionUseCase', () => {
@@ -11,7 +10,7 @@ describe('GetCurrentUserSessionUseCase', () => {
clearSession: Mock;
};
let logger: Logger;
let output: UseCaseOutputPort<AuthSessionDTO | null> & { present: Mock };
let output: UseCaseOutputPort<AuthSession | null> & { present: Mock };
let useCase: GetCurrentUserSessionUseCase;
beforeEach(() => {
@@ -40,7 +39,7 @@ describe('GetCurrentUserSessionUseCase', () => {
});
it('returns the current auth session when one exists', async () => {
const session: AuthSessionDTO = {
const session: AuthSession = {
user: {
id: 'user-1',
email: 'test@example.com',

View File

@@ -2,9 +2,8 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
import { HandleAuthCallbackUseCase } from './HandleAuthCallbackUseCase';
import type { IdentityProviderPort } from '../ports/IdentityProviderPort';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import type { AuthCallbackCommandDTO } from '../dto/AuthCallbackCommandDTO';
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { AuthCallbackCommand, AuthenticatedUser } from '../ports/IdentityProviderPort';
import type { AuthSession } from '../ports/IdentitySessionPort';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
describe('HandleAuthCallbackUseCase', () => {
@@ -17,7 +16,7 @@ describe('HandleAuthCallbackUseCase', () => {
clearSession: Mock;
};
let logger: Logger;
let output: UseCaseOutputPort<AuthSessionDTO> & { present: Mock };
let output: UseCaseOutputPort<AuthSession> & { present: Mock };
let useCase: HandleAuthCallbackUseCase;
beforeEach(() => {
@@ -48,20 +47,20 @@ describe('HandleAuthCallbackUseCase', () => {
});
it('completes auth and creates a session', async () => {
const command: AuthCallbackCommandDTO = {
const command: AuthCallbackCommand = {
provider: 'IRACING_DEMO',
code: 'auth-code',
state: 'state-123',
returnTo: 'https://app/callback',
};
const user: AuthenticatedUserDTO = {
const user: AuthenticatedUser = {
id: 'user-1',
email: 'test@example.com',
displayName: 'Test User',
};
const session: AuthSessionDTO = {
const session: AuthSession = {
user,
issuedAt: Date.now(),
expiresAt: Date.now() + 1000,

View File

@@ -2,8 +2,7 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
import { SignupWithEmailUseCase } from './SignupWithEmailUseCase';
import type { SignupWithEmailInput } from './SignupWithEmailUseCase';
import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
import type { AuthSession, IdentitySessionPort } from '../ports/IdentitySessionPort';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
type SignupWithEmailOutput = unknown;
@@ -58,7 +57,7 @@ describe('SignupWithEmailUseCase', () => {
userRepository.findByEmail.mockResolvedValue(null);
const session: AuthSessionDTO = {
const session: AuthSession = {
user: {
id: 'user-1',
email: command.email.toLowerCase(),

View File

@@ -5,7 +5,7 @@
*/
import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository';
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
import type { AuthenticatedUser } from '../ports/IdentityProviderPort';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
@@ -103,7 +103,7 @@ export class SignupWithEmailUseCase {
await this.userRepository.create(newUser);
// Create session
const authenticatedUser: AuthenticatedUserDTO = {
const authenticatedUser: AuthenticatedUser = {
id: newUser.id,
displayName: newUser.displayName,
email: newUser.email,

View File

@@ -1,6 +0,0 @@
/**
* Infrastructure layer exports for notifications package
*/
// This infrastructure layer is empty as the actual implementations
// are in the adapters directory

View File

@@ -2,5 +2,4 @@ export * from './application/Result';
export * as application from './application';
export * as domain from './domain';
export * as errors from './errors';
export * from './presentation';
export * from './application/AsyncUseCase';

View File

@@ -1,6 +0,0 @@
// This must not be used within core. It's in presentation layer, e.g. to be used in an API.
export interface Presenter<InputDTO, ResponseModel> {
present(input: InputDTO): void;
getResponseModel(): ResponseModel | null;
reset(): void;
}

View File

@@ -1 +0,0 @@
export * from './Presenter';

View File

@@ -1,22 +0,0 @@
export type FeedItemType =
| 'race_result'
| 'championship_standing'
| 'league_announcement'
| 'friend_joined_league'
| 'friend_won_race';
export interface FeedItemDTO {
id: string;
timestamp: string;
type: FeedItemType;
actorFriendId?: string;
actorDriverId?: string;
leagueId?: string;
raceId?: string;
teamId?: string;
position?: number;
headline: string;
body?: string;
ctaLabel?: string;
ctaHref?: string;
}

View File

@@ -1,8 +1,13 @@
export interface CurrentUserSocialDTO {
export type SocialUserSummary = {
driverId: string;
displayName: string;
avatarUrl: string;
countryCode: string;
primaryTeamId?: string;
primaryLeagueId?: string;
}
};
export type SocialFriendSummary = SocialUserSummary & {
isOnline: boolean;
lastSeen: Date;
};

View File

@@ -2,8 +2,7 @@ import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { ISocialGraphRepository } from '../../domain/repositories/ISocialGraphRepository';
import type { CurrentUserSocialDTO } from '../dto/CurrentUserSocialDTO';
import type { FriendDTO } from '../dto/FriendDTO';
import type { SocialFriendSummary, SocialUserSummary } from '../types/SocialUser';
export interface GetCurrentUserSocialParams {
driverId: string;
@@ -12,8 +11,8 @@ export interface GetCurrentUserSocialParams {
export type GetCurrentUserSocialInput = GetCurrentUserSocialParams;
export interface GetCurrentUserSocialResult {
currentUser: CurrentUserSocialDTO;
friends: FriendDTO[];
currentUser: SocialUserSummary;
friends: SocialFriendSummary[];
}
export type GetCurrentUserSocialErrorCode = 'REPOSITORY_ERROR';
@@ -61,15 +60,16 @@ export class GetCurrentUserSocialUseCase {
// The social graph context currently only knows about relationships.
// Profile fields for the current user are expected to be enriched by identity/profile contexts.
const friends: FriendDTO[] = friendsDomain.map((friend) => ({
const friends: SocialFriendSummary[] = friendsDomain.map((friend) => ({
driverId: friend.id,
displayName: friend.name.toString(),
avatarUrl: '',
countryCode: '',
isOnline: false,
lastSeen: new Date(),
}));
const currentUser: CurrentUserSocialDTO = {
const currentUser: SocialUserSummary = {
driverId,
displayName: '',
avatarUrl: '',

View File

@@ -1,19 +1,19 @@
import { randomUUID } from 'crypto';
import { createStaticRacingSeed } from '@core/testing-support';
import type { IdentityProviderPort } from '../../application/ports/IdentityProviderPort';
import type { StartAuthCommandDTO } from '../../application/dto/StartAuthCommandDTO';
import type { AuthCallbackCommandDTO } from '../../application/dto/AuthCallbackCommandDTO';
import type { AuthenticatedUserDTO } from '../../application/dto/AuthenticatedUserDTO';
import type {
AuthCallbackCommand,
AuthenticatedUser,
IdentityProviderPort,
StartAuthCommand,
} from '@core/identity/application/ports/IdentityProviderPort';
export class IracingDemoIdentityProviderAdapter implements IdentityProviderPort {
private readonly seedDriverId: string;
constructor() {
const seed = createStaticRacingSeed(42);
this.seedDriverId = seed.drivers[0]?.id ?? 'driver-1';
this.seedDriverId = 'driver-1';
}
async startAuth(command: StartAuthCommandDTO): Promise<{ redirectUrl: string; state: string }> {
async startAuth(command: StartAuthCommand): Promise<{ redirectUrl: string; state: string }> {
const state = randomUUID();
const params = new URLSearchParams();
@@ -29,7 +29,7 @@ export class IracingDemoIdentityProviderAdapter implements IdentityProviderPort
};
}
async completeAuth(command: AuthCallbackCommandDTO): Promise<AuthenticatedUserDTO> {
async completeAuth(command: AuthCallbackCommand): Promise<AuthenticatedUser> {
if (!command.code) {
throw new Error('Missing auth code');
}
@@ -37,7 +37,7 @@ export class IracingDemoIdentityProviderAdapter implements IdentityProviderPort
throw new Error('Missing auth state');
}
const user: AuthenticatedUserDTO = {
const user: AuthenticatedUser = {
id: 'demo-user',
displayName: 'GridPilot Demo Driver',
iracingCustomerId: '000000',

View File

@@ -3,7 +3,7 @@ import { League } from '@core/racing/domain/entities/League';
import { Race } from '@core/racing/domain/entities/Race';
import type { Result } from '@core/racing/domain/entities/Result';
import type { FeedItem } from '@core/social/domain/types/FeedItem';
import type { FriendDTO } from '@core/social/application/dto/FriendDTO';
import type { SocialFriendSummary } from '@core/social/application/types/SocialUser';
import { faker } from '../../helpers/faker/faker';
import { getLeagueBanner, getDriverAvatar } from '../../helpers/images/images';
import type { Friendship, RacingMembership } from './RacingSeedCore';
@@ -166,11 +166,11 @@ export function createFeedEvents(
export function buildFriends(
drivers: Driver[],
memberships: RacingMembership[],
): FriendDTO[] {
): SocialFriendSummary[] {
return drivers.map((driver) => {
const membership = memberships.find((m) => m.driverId === driver.id);
const base: FriendDTO = {
const base: SocialFriendSummary = {
driverId: driver.id,
displayName: driver.name,
avatarUrl: getDriverAvatar(driver.id),

View File

@@ -5,7 +5,7 @@ import { Result } from '@core/racing/domain/entities/Result';
import { Standing } from '@core/racing/domain/entities/Standing';
import type { FeedItem } from '@core/social/domain/types/FeedItem';
import type { FriendDTO } from '@core/social/application/dto/FriendDTO';
import type { SocialFriendSummary } from '@core/social/application/types/SocialUser';
import { faker } from '../../helpers/faker/faker';
import { getTeamLogo } from '../../helpers/images/images';
@@ -128,7 +128,7 @@ export const sponsorshipPricings = staticSeed.sponsorshipPricings;
* Derived friend DTOs for UI consumption.
* This preserves the previous demo-data `friends` shape.
*/
export const friends: FriendDTO[] = buildFriends(staticSeed.drivers, staticSeed.memberships);
export const friends: SocialFriendSummary[] = buildFriends(staticSeed.drivers, staticSeed.memberships);
/**
* Top leagues with banner URLs for UI.