website refactor

This commit is contained in:
2026-01-17 22:55:03 +01:00
parent 64d9e7fd16
commit 69d4cce7f1
64 changed files with 1146 additions and 1014 deletions

View File

@@ -108,6 +108,7 @@ export class AuthService {
userId: coreSession.user.id,
email: coreSession.user.email ?? '',
displayName: coreSession.user.displayName,
...(coreSession.user.primaryDriverId ? { primaryDriverId: coreSession.user.primaryDriverId } : {}),
...(role !== undefined ? { role } : {}),
},
};
@@ -138,6 +139,7 @@ export class AuthService {
id: userDTO.userId,
displayName: userDTO.displayName,
email: userDTO.email,
...(userDTO.primaryDriverId ? { primaryDriverId: userDTO.primaryDriverId } : {}),
...(inferredRole ? { role: inferredRole } : {}),
});
@@ -173,6 +175,7 @@ export class AuthService {
id: userDTO.userId,
displayName: userDTO.displayName,
email: userDTO.email,
...(userDTO.primaryDriverId ? { primaryDriverId: userDTO.primaryDriverId } : {}),
...(inferredRole ? { role: inferredRole } : {}),
});
@@ -212,6 +215,7 @@ export class AuthService {
id: userDTO.userId,
displayName: userDTO.displayName,
email: userDTO.email,
...(userDTO.primaryDriverId ? { primaryDriverId: userDTO.primaryDriverId } : {}),
...(inferredRole ? { role: inferredRole } : {}),
},
sessionOptions

View File

@@ -3,7 +3,7 @@ import { CanActivate, ExecutionContext, Inject, Injectable } from '@nestjs/commo
import { IDENTITY_SESSION_PORT_TOKEN } from './AuthProviders';
type AuthenticatedRequest = {
user?: { userId: string; role?: string | undefined };
user?: { userId: string; role?: string | undefined; primaryDriverId?: string | undefined };
};
@Injectable()
@@ -22,9 +22,11 @@ export class AuthenticationGuard implements CanActivate {
const session = await this.sessionPort.getCurrentSession();
if (session?.user?.id) {
console.log(`[AuthenticationGuard] Session found for user: ${session.user.id}, primaryDriverId: ${session.user.primaryDriverId}`);
request.user = {
userId: session.user.id,
role: session.user.role
role: session.user.role,
primaryDriverId: session.user.primaryDriverId
};
}

View File

@@ -45,7 +45,7 @@ describe('DashboardController', () => {
};
mockService.getDashboardOverview.mockResolvedValue(overview);
const result = await controller.getDashboardOverview(driverId, { user: { userId: driverId } });
const result = await controller.getDashboardOverview(driverId, { user: { userId: driverId, primaryDriverId: driverId } });
expect(mockService.getDashboardOverview).toHaveBeenCalledWith(driverId);
expect(result).toEqual(overview);
@@ -55,7 +55,7 @@ describe('DashboardController', () => {
describe('auth guards (HTTP)', () => {
let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string; primaryDriverId?: string } }> } = {
getCurrentSession: vi.fn(async () => null),
};
@@ -128,7 +128,7 @@ describe('DashboardController', () => {
it('allows endpoint when authenticated via session port', async () => {
vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
token: 't',
user: { id: 'user-1' },
user: { id: 'user-1', primaryDriverId: 'driver-1' },
});
await request(app.getHttpServer()).get('/dashboard/overview?driverId=d1').expect(200);

View File

@@ -4,7 +4,7 @@ import { DashboardService } from './DashboardService';
import { DashboardOverviewDTO } from './dtos/DashboardOverviewDTO';
type AuthenticatedRequest = {
user?: { userId: string };
user?: { userId: string; primaryDriverId?: string };
};
@ApiTags('dashboard')
@@ -21,10 +21,10 @@ export class DashboardController {
@Query('driverId') _driverId: string,
@Req() req: AuthenticatedRequest,
): Promise<DashboardOverviewDTO> {
const userId = req.user?.userId;
if (!userId) {
throw new UnauthorizedException('Unauthorized');
const driverId = req.user?.primaryDriverId;
if (!driverId) {
throw new UnauthorizedException('Unauthorized: No driver associated with user');
}
return this.dashboardService.getDashboardOverview(userId);
return this.dashboardService.getDashboardOverview(driverId);
}
}

View File

@@ -22,7 +22,7 @@ import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
interface AuthenticatedRequest extends Request {
user?: { userId: string };
user?: { userId: string; primaryDriverId?: string };
}
describe('DriverController', () => {
@@ -82,13 +82,13 @@ describe('DriverController', () => {
it('should return current driver if userId exists', async () => {
const userId = 'user-123';
const driver: GetDriverOutputDTO = { id: 'driver-123', name: 'Driver' } as GetDriverOutputDTO;
service.getCurrentDriver.mockResolvedValue(driver);
service.getDriver.mockResolvedValue(driver);
const mockReq: Partial<AuthenticatedRequest> = { user: { userId } };
const mockReq: Partial<AuthenticatedRequest> = { user: { userId, primaryDriverId: userId } };
const result = await controller.getCurrentDriver(mockReq as AuthenticatedRequest);
expect(service.getCurrentDriver).toHaveBeenCalledWith(userId);
expect(service.getDriver).toHaveBeenCalledWith(userId);
expect(result).toEqual(driver);
});
@@ -188,7 +188,7 @@ describe('DriverController', () => {
describe('auth guards (HTTP)', () => {
let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string; primaryDriverId?: string } }> } = {
getCurrentSession: vi.fn(async () => null),
};
@@ -215,7 +215,7 @@ describe('DriverController', () => {
provide: DriverService,
useValue: {
getDriversLeaderboard: vi.fn(async () => ({ drivers: [], totalRaces: 0, totalWins: 0, activeCount: 0 })),
getCurrentDriver: vi.fn(async () => ({ id: 'd1' })),
getDriver: vi.fn(async () => ({ id: 'd1' })),
},
},
],
@@ -249,7 +249,7 @@ describe('DriverController', () => {
it('allows non-public endpoint when authenticated via session port', async () => {
vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
token: 't',
user: { id: 'user-1' },
user: { id: 'user-1', primaryDriverId: 'driver-1' },
});
await request(app.getHttpServer()).get('/drivers/current').expect(200);

View File

@@ -13,7 +13,7 @@ import { GetDriverOutputDTO } from './dtos/GetDriverOutputDTO';
import { GetDriverProfileOutputDTO } from './dtos/GetDriverProfileOutputDTO';
type AuthenticatedRequest = {
user?: { userId: string };
user?: { userId: string; primaryDriverId?: string };
};
@@ -43,12 +43,12 @@ export class DriverController {
@ApiResponse({ status: 200, description: 'Current driver data', type: GetDriverOutputDTO })
@ApiResponse({ status: 404, description: 'Driver not found' })
async getCurrentDriver(@Req() req: AuthenticatedRequest): Promise<GetDriverOutputDTO | null> {
const userId = req.user?.userId;
if (!userId) {
const driverId = req.user?.primaryDriverId;
if (!driverId) {
return null;
}
return await this.driverService.getCurrentDriver(userId);
return await this.driverService.getDriver(driverId);
}
@Post('complete-onboarding')

View File

@@ -19,10 +19,10 @@ describe('DriverService', () => {
// Mocks for presenters
const driversLeaderboardPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
const driverStatsPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
const completeOnboardingPresenter = { getResponseModel: vi.fn() };
const driverRegistrationStatusPresenter = { getResponseModel: vi.fn() };
const completeOnboardingPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
const driverRegistrationStatusPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
const driverPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
const driverProfilePresenter = { getResponseModel: vi.fn() };
const driverProfilePresenter = { present: vi.fn(), getResponseModel: vi.fn() };
const getDriverLiveriesPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
beforeEach(() => {

View File

@@ -116,6 +116,7 @@ export class DriverService {
if (result.isErr()) {
throw new Error(result.unwrapErr().details.message);
}
await this.completeOnboardingPresenter!.present(result.unwrap());
return this.completeOnboardingPresenter!.getResponseModel();
}
@@ -132,6 +133,7 @@ export class DriverService {
if (result.isErr()) {
throw new Error(result.unwrapErr().details.message);
}
await this.driverRegistrationStatusPresenter!.present(result.unwrap());
return this.driverRegistrationStatusPresenter!.getResponseModel();
}
@@ -190,6 +192,7 @@ export class DriverService {
if (result.isErr()) {
throw new Error(result.unwrapErr().details.message);
}
await this.driverProfilePresenter!.present(result.unwrap());
return this.driverProfilePresenter!.getResponseModel();
}

View File

@@ -78,9 +78,10 @@ async function bootstrap() {
// Start server
try {
await app.listen(3000);
console.log('✅ API Server started successfully on port 3000');
console.log('📚 Swagger docs: http://localhost:3000/api/docs');
const port = process.env.PORT || 3000;
await app.listen(port);
console.log(`✅ API Server started successfully on port ${port}`);
console.log(`📚 Swagger docs: http://localhost:${port}/api/docs`);
} catch (error: unknown) {
console.error('❌ Failed to start API server:', error instanceof Error ? error.message : 'Unknown error');
process.exit(1);