seed data
This commit is contained in:
@@ -2710,6 +2710,21 @@
|
||||
},
|
||||
"joinedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"rating": {
|
||||
"type": "number"
|
||||
},
|
||||
"experienceLevel": {
|
||||
"type": "string"
|
||||
},
|
||||
"wins": {
|
||||
"type": "number"
|
||||
},
|
||||
"podiums": {
|
||||
"type": "number"
|
||||
},
|
||||
"totalRaces": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -6607,6 +6622,21 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"totalWins": {
|
||||
"type": "number"
|
||||
},
|
||||
"totalRaces": {
|
||||
"type": "number"
|
||||
},
|
||||
"performanceLevel": {
|
||||
"type": "string"
|
||||
},
|
||||
"logoUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"rating": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -18,6 +18,11 @@ export class DashboardDriverSummaryDTO {
|
||||
@IsString()
|
||||
avatarUrl!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
category?: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
|
||||
@@ -20,6 +20,11 @@ export class DashboardDriverSummaryDTO {
|
||||
@IsString()
|
||||
avatarUrl?: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
category?: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
|
||||
@@ -23,6 +23,7 @@ export class DashboardOverviewPresenter implements UseCaseOutputPort<DashboardOv
|
||||
name: String(data.currentDriver.driver.name),
|
||||
country: String(data.currentDriver.driver.country),
|
||||
avatarUrl: data.currentDriver.avatarUrl,
|
||||
category: data.currentDriver.driver.category ?? null,
|
||||
rating: data.currentDriver.rating,
|
||||
globalRank: data.currentDriver.globalRank,
|
||||
totalRaces: data.currentDriver.totalRaces,
|
||||
|
||||
@@ -18,4 +18,7 @@ export class DriverDTO {
|
||||
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
category?: string;
|
||||
}
|
||||
@@ -13,6 +13,9 @@ export class DriverLeaderboardItemDTO {
|
||||
@ApiProperty()
|
||||
skillLevel!: string; // Assuming skillLevel is a string like 'Rookie', 'Pro', etc.
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
category?: string;
|
||||
|
||||
@ApiProperty()
|
||||
nationality!: string;
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ export class DriverProfileDriverSummaryDTO {
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
category!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
rating!: number | null;
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ export class GetDriverOutputDTO {
|
||||
@ApiProperty()
|
||||
joinedAt!: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
category?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
rating?: number;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export class DriverPresenter {
|
||||
country: driver.country.toString(),
|
||||
joinedAt: driver.joinedAt.toDate().toISOString(),
|
||||
...(driver.bio ? { bio: driver.bio.toString() } : {}),
|
||||
...(driver.category ? { category: driver.category } : {}),
|
||||
// Add stats fields
|
||||
...(stats ? {
|
||||
rating: stats.rating,
|
||||
|
||||
@@ -18,6 +18,7 @@ export class DriverProfilePresenter
|
||||
avatarUrl: this.getAvatarUrl(result.driverInfo.driver.id) || '',
|
||||
iracingId: result.driverInfo.driver.iracingId.toString(),
|
||||
joinedAt: result.driverInfo.driver.joinedAt.toDate().toISOString(),
|
||||
category: result.driverInfo.driver.category || null,
|
||||
rating: result.driverInfo.rating,
|
||||
globalRank: result.driverInfo.globalRank,
|
||||
consistency: result.driverInfo.consistency,
|
||||
|
||||
@@ -13,6 +13,7 @@ export class DriversLeaderboardPresenter {
|
||||
name: item.driver.name.toString(),
|
||||
rating: item.rating,
|
||||
skillLevel: item.skillLevel,
|
||||
...(item.driver.category !== undefined ? { category: item.driver.category } : {}),
|
||||
nationality: item.driver.country.toString(),
|
||||
racesCompleted: item.racesCompleted,
|
||||
wins: item.wins,
|
||||
|
||||
@@ -95,6 +95,11 @@ export class LeagueWithCapacityAndScoringDTO {
|
||||
@IsNumber()
|
||||
usedSlots!: number;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
category?: string;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true, type: LeagueCapacityAndScoringSocialLinksDTO })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
|
||||
@@ -32,6 +32,7 @@ export class AllLeaguesWithCapacityAndScoringPresenter
|
||||
: {}),
|
||||
},
|
||||
usedSlots: summary.currentDrivers,
|
||||
...(summary.league.category ? { category: summary.league.category } : {}),
|
||||
...mapSocialLinks(summary.league.socialLinks),
|
||||
...(summary.scoringConfig && summary.game && summary.preset
|
||||
? {
|
||||
|
||||
@@ -91,6 +91,70 @@ function buildLeagueCoverSvg(leagueId: string): string {
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
function buildDriverAvatarSvg(driverId: string): string {
|
||||
const hue = hashToHue(driverId);
|
||||
const initials = deriveLeagueLabel(driverId);
|
||||
const bg = `hsl(${hue} 70% 38%)`;
|
||||
const border = `hsl(${hue} 70% 28%)`;
|
||||
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" role="img" aria-label="Driver avatar">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="${bg}"/>
|
||||
<stop offset="100%" stop-color="hsl(${hue} 80% 46%)"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="48" cy="48" r="44" fill="url(#g)" stroke="${border}" stroke-width="3"/>
|
||||
<text x="48" y="56" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial" font-size="32" font-weight="800" text-anchor="middle" fill="white">${initials}</text>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
function buildTrackImageSvg(trackId: string): string {
|
||||
const hue = hashToHue(trackId);
|
||||
const label = escapeXml(deriveLeagueLabel(trackId));
|
||||
const bg1 = `hsl(${hue} 70% 28%)`;
|
||||
const bg2 = `hsl(${(hue + 20) % 360} 65% 35%)`;
|
||||
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="400" viewBox="0 0 1200 400" role="img" aria-label="Track image">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="${bg1}"/>
|
||||
<stop offset="100%" stop-color="${bg2}"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="400" fill="url(#bg)"/>
|
||||
|
||||
<!-- Track outline -->
|
||||
<path d="M 200 200 Q 400 100 600 200 T 1000 200" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="8" stroke-linecap="round"/>
|
||||
<path d="M 200 220 Q 400 120 600 220 T 1000 220" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="6" stroke-linecap="round"/>
|
||||
|
||||
<text x="64" y="110" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial" font-size="40" font-weight="800" fill="rgba(255,255,255,0.92)">Track ${label}</text>
|
||||
<text x="64" y="165" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial" font-size="22" font-weight="600" fill="rgba(255,255,255,0.75)">${escapeXml(trackId)}</text>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
function buildCategoryIconSvg(categoryId: string): string {
|
||||
const hue = hashToHue(categoryId);
|
||||
const label = escapeXml(categoryId.substring(0, 3).toUpperCase());
|
||||
const bg = `hsl(${hue} 70% 38%)`;
|
||||
const border = `hsl(${hue} 70% 28%)`;
|
||||
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" role="img" aria-label="Category icon">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="${bg}"/>
|
||||
<stop offset="100%" stop-color="hsl(${hue} 80% 46%)"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="2" y="2" width="60" height="60" rx="12" fill="url(#g)" stroke="${border}" stroke-width="2"/>
|
||||
<text x="32" y="40" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial" font-size="22" font-weight="800" text-anchor="middle" fill="white">${label}</text>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
@ApiTags('media')
|
||||
@Controller('media')
|
||||
export class MediaController {
|
||||
@@ -159,6 +223,132 @@ export class MediaController {
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('teams/:teamId/logo')
|
||||
@ApiOperation({ summary: 'Get team logo (placeholder)' })
|
||||
@ApiParam({ name: 'teamId', description: 'Team ID' })
|
||||
async getTeamLogo(
|
||||
@Param('teamId') teamId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildLeagueLogoSvg(teamId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('team/:teamId/logo')
|
||||
@ApiOperation({ summary: 'Get team logo (singular path)' })
|
||||
@ApiParam({ name: 'teamId', description: 'Team ID' })
|
||||
async getTeamLogoSingular(
|
||||
@Param('teamId') teamId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildLeagueLogoSvg(teamId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('team/:teamId/logo.png')
|
||||
@ApiOperation({ summary: 'Get team logo with .png extension' })
|
||||
@ApiParam({ name: 'teamId', description: 'Team ID' })
|
||||
async getTeamLogoPng(
|
||||
@Param('teamId') teamId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildLeagueLogoSvg(teamId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('teams/:teamId/cover')
|
||||
@ApiOperation({ summary: 'Get team cover (placeholder)' })
|
||||
@ApiParam({ name: 'teamId', description: 'Team ID' })
|
||||
async getTeamCover(
|
||||
@Param('teamId') teamId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildLeagueCoverSvg(teamId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('drivers/:driverId/avatar')
|
||||
@ApiOperation({ summary: 'Get driver avatar (placeholder)' })
|
||||
@ApiParam({ name: 'driverId', description: 'Driver ID' })
|
||||
async getDriverAvatar(
|
||||
@Param('driverId') driverId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildDriverAvatarSvg(driverId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('avatar/:driverId')
|
||||
@ApiOperation({ summary: 'Get driver avatar (alternative path)' })
|
||||
@ApiParam({ name: 'driverId', description: 'Driver ID' })
|
||||
async getDriverAvatarAlt(
|
||||
@Param('driverId') driverId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildDriverAvatarSvg(driverId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('tracks/:trackId/image')
|
||||
@ApiOperation({ summary: 'Get track image (placeholder)' })
|
||||
@ApiParam({ name: 'trackId', description: 'Track ID' })
|
||||
async getTrackImage(
|
||||
@Param('trackId') trackId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildTrackImageSvg(trackId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('categories/:categoryId/icon')
|
||||
@ApiOperation({ summary: 'Get category icon (placeholder)' })
|
||||
@ApiParam({ name: 'categoryId', description: 'Category ID' })
|
||||
async getCategoryIcon(
|
||||
@Param('categoryId') categoryId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildCategoryIconSvg(categoryId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get('sponsors/:sponsorId/logo')
|
||||
@ApiOperation({ summary: 'Get sponsor logo (placeholder)' })
|
||||
@ApiParam({ name: 'sponsorId', description: 'Sponsor ID' })
|
||||
async getSponsorLogo(
|
||||
@Param('sponsorId') sponsorId: string,
|
||||
@Res() res: Response,
|
||||
): Promise<void> {
|
||||
const svg = buildLeagueLogoSvg(sponsorId);
|
||||
res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'public, max-age=86400');
|
||||
res.status(HttpStatus.OK).send(svg);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Get(':mediaId')
|
||||
@ApiOperation({ summary: 'Get media by ID' })
|
||||
@@ -237,4 +427,4 @@ export class MediaController {
|
||||
res.status(HttpStatus.BAD_REQUEST).json(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ describe('TeamController', () => {
|
||||
it('should return team details', async () => {
|
||||
const teamId = 'team-123';
|
||||
const userId = 'user-456';
|
||||
const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [] }, membership: null, canManage: false };
|
||||
const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false };
|
||||
service.getDetails.mockResolvedValue(result);
|
||||
|
||||
const mockReq = { user: { userId } } as any;
|
||||
@@ -132,7 +132,7 @@ describe('TeamController', () => {
|
||||
describe('getDriverTeam', () => {
|
||||
it('should return driver team', async () => {
|
||||
const driverId = 'driver-123';
|
||||
const result = { team: { id: 'team-456', name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [] }, membership: { role: 'member' as const, joinedAt: '2023-01-01', isActive: true }, isOwner: false, canManage: false };
|
||||
const result = { team: { id: 'team-456', name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: { role: 'member' as const, joinedAt: '2023-01-01', isActive: true }, isOwner: false, canManage: false };
|
||||
service.getDriverTeam.mockResolvedValue(result);
|
||||
|
||||
const response = await controller.getDriverTeam(driverId);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
|
||||
import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, MEDIA_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN, MEDIA_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
|
||||
export {
|
||||
TEAM_REPOSITORY_TOKEN,
|
||||
@@ -8,18 +8,15 @@ export {
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
IMAGE_SERVICE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
TEAM_STATS_REPOSITORY_TOKEN,
|
||||
MEDIA_REPOSITORY_TOKEN,
|
||||
} from './TeamTokens';
|
||||
|
||||
// Import core interfaces
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||
|
||||
// Import concrete in-memory implementations
|
||||
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { InMemoryTeamStatsRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamStatsRepository';
|
||||
import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
|
||||
|
||||
// Import presenters
|
||||
@@ -35,11 +32,6 @@ export const TeamProviders: Provider[] = [
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: TEAM_STATS_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryTeamStatsRepository(logger),
|
||||
inject: [LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: MEDIA_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryMediaRepository(logger),
|
||||
@@ -47,7 +39,6 @@ export const TeamProviders: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: AllTeamsPresenter,
|
||||
useFactory: (teamStatsRepository: ITeamStatsRepository) => new AllTeamsPresenter(teamStatsRepository),
|
||||
inject: [TEAM_STATS_REPOSITORY_TOKEN],
|
||||
useFactory: () => new AllTeamsPresenter(),
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
@@ -113,7 +113,6 @@ describe('TeamService', () => {
|
||||
|
||||
const teamStatsRepository = {
|
||||
getTeamStats: vi.fn(),
|
||||
getTeamStatsSync: vi.fn(),
|
||||
saveTeamStats: vi.fn(),
|
||||
getAllStats: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
@@ -126,6 +125,10 @@ describe('TeamService', () => {
|
||||
saveDriverAvatar: vi.fn(),
|
||||
};
|
||||
|
||||
const resultRepository = {
|
||||
findAll: vi.fn(),
|
||||
};
|
||||
|
||||
const allTeamsPresenter = {
|
||||
reset: vi.fn(),
|
||||
present: vi.fn(),
|
||||
@@ -140,6 +143,7 @@ describe('TeamService', () => {
|
||||
logger,
|
||||
teamStatsRepository as unknown as never,
|
||||
mediaRepository as unknown as never,
|
||||
resultRepository as unknown as never,
|
||||
allTeamsPresenter as unknown as never
|
||||
);
|
||||
});
|
||||
@@ -558,4 +562,4 @@ describe('TeamService', () => {
|
||||
|
||||
executeSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,9 +37,10 @@ import { CreateTeamPresenter } from './presenters/CreateTeamPresenter';
|
||||
import { UpdateTeamPresenter } from './presenters/UpdateTeamPresenter';
|
||||
|
||||
// Tokens
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, MEDIA_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, MEDIA_REPOSITORY_TOKEN, RESULT_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||
import type { IMediaRepository } from '@core/racing/domain/repositories/IMediaRepository';
|
||||
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
|
||||
|
||||
@Injectable()
|
||||
export class TeamService {
|
||||
@@ -50,6 +51,7 @@ export class TeamService {
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
|
||||
@Inject(MEDIA_REPOSITORY_TOKEN) private readonly mediaRepository: IMediaRepository,
|
||||
@Inject(RESULT_REPOSITORY_TOKEN) private readonly resultRepository: IResultRepository,
|
||||
private readonly allTeamsPresenter: AllTeamsPresenter,
|
||||
) {}
|
||||
|
||||
@@ -61,6 +63,7 @@ export class TeamService {
|
||||
this.membershipRepository,
|
||||
this.teamStatsRepository,
|
||||
this.mediaRepository,
|
||||
this.resultRepository,
|
||||
this.logger,
|
||||
this.allTeamsPresenter
|
||||
);
|
||||
@@ -174,13 +177,13 @@ export class TeamService {
|
||||
}
|
||||
|
||||
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching driver team for driverId: ${driverId}`);
|
||||
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
|
||||
|
||||
const presenter = new DriverTeamPresenter();
|
||||
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger, presenter);
|
||||
const result = await useCase.execute({ driverId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching driver team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository';
|
||||
export const IMAGE_SERVICE_TOKEN = 'IImageServicePort';
|
||||
export const LOGGER_TOKEN = 'Logger';
|
||||
export const TEAM_STATS_REPOSITORY_TOKEN = 'ITeamStatsRepository';
|
||||
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
|
||||
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
|
||||
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
|
||||
@@ -326,6 +326,15 @@ export class TeamDTO {
|
||||
@IsArray()
|
||||
leagues!: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
category?: string | undefined;
|
||||
|
||||
@ApiProperty()
|
||||
@IsBoolean()
|
||||
isRecruiting!: boolean;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -37,10 +37,16 @@ export class TeamListItemDTO {
|
||||
@ApiProperty({ required: false, enum: ['beginner', 'intermediate', 'advanced', 'pro'] })
|
||||
performanceLevel?: 'beginner' | 'intermediate' | 'advanced' | 'pro';
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
category?: string | undefined;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
logoUrl?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
rating?: number;
|
||||
|
||||
@ApiProperty()
|
||||
isRecruiting!: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,40 @@
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import type { GetAllTeamsResult } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetAllTeamsOutputDTO } from '../dtos/GetAllTeamsOutputDTO';
|
||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||
import { TeamListItemDTO } from '../dtos/TeamListItemDTO';
|
||||
|
||||
export class AllTeamsPresenter implements UseCaseOutputPort<GetAllTeamsResult> {
|
||||
private model: GetAllTeamsOutputDTO | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly teamStatsRepository: ITeamStatsRepository
|
||||
) {}
|
||||
|
||||
reset(): void {
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
present(result: GetAllTeamsResult): void {
|
||||
const teams: TeamListItemDTO[] = result.teams.map(team => {
|
||||
const dto = new TeamListItemDTO();
|
||||
dto.id = team.id;
|
||||
dto.name = team.name;
|
||||
dto.tag = team.tag;
|
||||
dto.description = team.description || '';
|
||||
dto.memberCount = team.memberCount;
|
||||
dto.leagues = team.leagues || [];
|
||||
dto.totalWins = team.totalWins ?? 0;
|
||||
dto.totalRaces = team.totalRaces ?? 0;
|
||||
dto.performanceLevel = (team.performanceLevel as 'beginner' | 'intermediate' | 'advanced' | 'pro') ?? 'intermediate';
|
||||
dto.specialization = (team.specialization as 'endurance' | 'sprint' | 'mixed') ?? 'mixed';
|
||||
dto.region = team.region ?? '';
|
||||
dto.languages = team.languages ?? [];
|
||||
// Return relative URL for proxying through Next.js rewrites
|
||||
dto.logoUrl = `/api/media/teams/${team.id}/logo`;
|
||||
dto.rating = team.rating ?? 0;
|
||||
dto.category = team.category;
|
||||
dto.isRecruiting = team.isRecruiting;
|
||||
return dto;
|
||||
});
|
||||
|
||||
this.model = {
|
||||
teams: result.teams.map(team => {
|
||||
const stats = this.teamStatsRepository.getTeamStatsSync(team.id.toString());
|
||||
|
||||
return {
|
||||
id: team.id,
|
||||
name: team.name.toString(),
|
||||
tag: team.tag.toString(),
|
||||
description: team.description?.toString() || '',
|
||||
memberCount: team.memberCount,
|
||||
leagues: team.leagues?.map(l => l.toString()) || [],
|
||||
// Add stats fields
|
||||
...(stats ? {
|
||||
totalWins: stats.totalWins,
|
||||
totalRaces: stats.totalRaces,
|
||||
performanceLevel: stats.performanceLevel,
|
||||
specialization: stats.specialization,
|
||||
region: stats.region,
|
||||
languages: stats.languages,
|
||||
logoUrl: stats.logoUrl,
|
||||
rating: stats.rating,
|
||||
} : {
|
||||
totalWins: 0,
|
||||
totalRaces: 0,
|
||||
performanceLevel: 'beginner',
|
||||
specialization: 'mixed',
|
||||
region: '',
|
||||
languages: [],
|
||||
logoUrl: '',
|
||||
rating: 0,
|
||||
}),
|
||||
};
|
||||
}),
|
||||
teams,
|
||||
totalCount: result.totalCount ?? result.teams.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export class DriverTeamPresenter implements UseCaseOutputPort<GetDriverTeamResul
|
||||
description: result.team.description?.toString() || '',
|
||||
ownerId: result.team.ownerId.toString(),
|
||||
leagues: result.team.leagues?.map(l => l.toString()) || [],
|
||||
isRecruiting: result.team.isRecruiting,
|
||||
createdAt: result.team.createdAt.toDate().toISOString(),
|
||||
},
|
||||
membership: {
|
||||
|
||||
@@ -18,6 +18,8 @@ export class TeamDetailsPresenter implements UseCaseOutputPort<GetTeamDetailsRes
|
||||
description: result.team.description?.toString() || '',
|
||||
ownerId: result.team.ownerId.toString(),
|
||||
leagues: result.team.leagues?.map(l => l.toString()) || [],
|
||||
category: result.team.category,
|
||||
isRecruiting: result.team.isRecruiting,
|
||||
createdAt: result.team.createdAt.toDate().toISOString(),
|
||||
},
|
||||
membership: result.membership
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
TeamMembershipOrmEntity,
|
||||
TeamOrmEntity,
|
||||
} from '@adapters/racing/persistence/typeorm/entities/TeamOrmEntities';
|
||||
import { TeamStatsOrmEntity } from '@adapters/racing/persistence/typeorm/entities/TeamStatsOrmEntity';
|
||||
|
||||
import { TypeOrmDriverRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmDriverRepository';
|
||||
import { TypeOrmLeagueMembershipRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueMembershipRepository';
|
||||
@@ -79,9 +80,11 @@ import { TypeOrmTeamMembershipRepository, TypeOrmTeamRepository } from '@adapter
|
||||
|
||||
// Import in-memory implementations for new repositories (TypeORM versions not yet implemented)
|
||||
import { InMemoryDriverStatsRepository } from '@adapters/racing/persistence/inmemory/InMemoryDriverStatsRepository';
|
||||
import { InMemoryTeamStatsRepository } from '@adapters/racing/persistence/inmemory/InMemoryTeamStatsRepository';
|
||||
import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
|
||||
|
||||
// Import TypeORM repository for team stats
|
||||
import { TypeOrmTeamStatsRepository } from '@adapters/racing/persistence/typeorm/repositories/TypeOrmTeamStatsRepository';
|
||||
|
||||
import { DriverOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/DriverOrmMapper';
|
||||
import { LeagueMembershipOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueMembershipOrmMapper';
|
||||
import { LeagueOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/LeagueOrmMapper';
|
||||
@@ -105,6 +108,7 @@ import {
|
||||
import { MoneyOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/MoneyOrmMapper';
|
||||
import { PenaltyOrmMapper, ProtestOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/StewardingOrmMappers';
|
||||
import { TeamMembershipOrmMapper, TeamOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/TeamOrmMappers';
|
||||
import { TeamStatsOrmMapper } from '@adapters/racing/persistence/typeorm/mappers/TeamStatsOrmMapper';
|
||||
|
||||
import { getPointsSystems } from '@adapters/bootstrap/PointsSystems';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
@@ -126,6 +130,7 @@ const typeOrmFeatureImports = [
|
||||
TeamOrmEntity,
|
||||
TeamMembershipOrmEntity,
|
||||
TeamJoinRequestOrmEntity,
|
||||
TeamStatsOrmEntity,
|
||||
|
||||
PenaltyOrmEntity,
|
||||
ProtestOrmEntity,
|
||||
@@ -155,6 +160,7 @@ const typeOrmFeatureImports = [
|
||||
|
||||
{ provide: TeamOrmMapper, useFactory: () => new TeamOrmMapper() },
|
||||
{ provide: TeamMembershipOrmMapper, useFactory: () => new TeamMembershipOrmMapper() },
|
||||
{ provide: TeamStatsOrmMapper, useFactory: () => new TeamStatsOrmMapper() },
|
||||
|
||||
{ provide: PenaltyOrmMapper, useFactory: () => new PenaltyOrmMapper() },
|
||||
{ provide: ProtestOrmMapper, useFactory: () => new ProtestOrmMapper() },
|
||||
@@ -321,8 +327,8 @@ const typeOrmFeatureImports = [
|
||||
},
|
||||
{
|
||||
provide: TEAM_STATS_REPOSITORY_TOKEN,
|
||||
useFactory: (logger: Logger) => new InMemoryTeamStatsRepository(logger),
|
||||
inject: ['Logger'],
|
||||
useFactory: (repo: Repository<TeamStatsOrmEntity>, mapper: TeamStatsOrmMapper) => new TypeOrmTeamStatsRepository(repo, mapper),
|
||||
inject: [getRepositoryToken(TeamStatsOrmEntity), TeamStatsOrmMapper],
|
||||
},
|
||||
{
|
||||
provide: MEDIA_REPOSITORY_TOKEN,
|
||||
@@ -356,4 +362,4 @@ const typeOrmFeatureImports = [
|
||||
MEDIA_REPOSITORY_TOKEN,
|
||||
],
|
||||
})
|
||||
export class PostgresRacingPersistenceModule {}
|
||||
export class PostgresRacingPersistenceModule {}
|
||||
|
||||
@@ -52,6 +52,25 @@ const SKILL_LEVELS: {
|
||||
{ id: 'beginner', label: 'Beginner', icon: Shield, color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30', description: 'Learning the ropes' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// CATEGORY CONFIG
|
||||
// ============================================================================
|
||||
|
||||
const CATEGORIES: {
|
||||
id: string;
|
||||
label: string;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
borderColor: string;
|
||||
}[] = [
|
||||
{ id: 'beginner', label: 'Beginner', color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30' },
|
||||
{ id: 'intermediate', label: 'Intermediate', color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', borderColor: 'border-primary-blue/30' },
|
||||
{ id: 'advanced', label: 'Advanced', color: 'text-purple-400', bgColor: 'bg-purple-400/10', borderColor: 'border-purple-400/30' },
|
||||
{ id: 'pro', label: 'Pro', color: 'text-yellow-400', bgColor: 'bg-yellow-400/10', borderColor: 'border-yellow-400/30' },
|
||||
{ id: 'endurance', label: 'Endurance', color: 'text-orange-400', bgColor: 'bg-orange-400/10', borderColor: 'border-orange-400/30' },
|
||||
{ id: 'sprint', label: 'Sprint', color: 'text-red-400', bgColor: 'bg-red-400/10', borderColor: 'border-red-400/30' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FEATURED DRIVER CARD COMPONENT
|
||||
// ============================================================================
|
||||
@@ -64,6 +83,7 @@ interface FeaturedDriverCardProps {
|
||||
|
||||
function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardProps) {
|
||||
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
||||
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
|
||||
|
||||
const getBorderColor = (pos: number) => {
|
||||
switch (pos) {
|
||||
@@ -98,9 +118,16 @@ function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardPro
|
||||
<span className="text-lg font-bold text-gray-400">#{position}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-[10px] font-medium ${levelConfig?.bgColor} ${levelConfig?.color} border ${levelConfig?.borderColor}`}>
|
||||
{levelConfig?.label}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
{categoryConfig && (
|
||||
<span className={`px-2 py-1 rounded-full text-[10px] font-medium ${categoryConfig.bgColor} ${categoryConfig.color} border ${categoryConfig.borderColor}`}>
|
||||
{categoryConfig.label}
|
||||
</span>
|
||||
)}
|
||||
<span className={`px-2 py-1 rounded-full text-[10px] font-medium ${levelConfig?.bgColor} ${levelConfig?.color} border ${levelConfig?.borderColor}`}>
|
||||
{levelConfig?.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Avatar & Name */}
|
||||
@@ -150,8 +177,8 @@ function SkillDistribution({ drivers }: SkillDistributionProps) {
|
||||
const distribution = SKILL_LEVELS.map((level) => ({
|
||||
...level,
|
||||
count: drivers.filter((d) => d.skillLevel === level.id).length,
|
||||
percentage: drivers.length > 0
|
||||
? Math.round((drivers.filter((d) => d.skillLevel === level.id).length / drivers.length) * 100)
|
||||
percentage: drivers.length > 0
|
||||
? Math.round((drivers.filter((d) => d.skillLevel === level.id).length / drivers.length) * 100)
|
||||
: 0,
|
||||
}));
|
||||
|
||||
@@ -200,6 +227,66 @@ function SkillDistribution({ drivers }: SkillDistributionProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CATEGORY DISTRIBUTION COMPONENT
|
||||
// ============================================================================
|
||||
|
||||
interface CategoryDistributionProps {
|
||||
drivers: DriverLeaderboardItemViewModel[];
|
||||
}
|
||||
|
||||
function CategoryDistribution({ drivers }: CategoryDistributionProps) {
|
||||
const distribution = CATEGORIES.map((category) => ({
|
||||
...category,
|
||||
count: drivers.filter((d) => d.category === category.id).length,
|
||||
percentage: drivers.length > 0
|
||||
? Math.round((drivers.filter((d) => d.category === category.id).length / drivers.length) * 100)
|
||||
: 0,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="mb-10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-purple-400/10 border border-purple-400/20">
|
||||
<BarChart3 className="w-5 h-5 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-white">Category Distribution</h2>
|
||||
<p className="text-xs text-gray-500">Driver population by category</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{distribution.map((category) => (
|
||||
<div
|
||||
key={category.id}
|
||||
className={`p-4 rounded-xl ${category.bgColor} border ${category.borderColor}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className={`text-2xl font-bold ${category.color}`}>{category.count}</span>
|
||||
</div>
|
||||
<p className="text-white font-medium mb-1">{category.label}</p>
|
||||
<div className="w-full h-2 rounded-full bg-deep-graphite/50 overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all duration-500 ${
|
||||
category.id === 'beginner' ? 'bg-green-400' :
|
||||
category.id === 'intermediate' ? 'bg-primary-blue' :
|
||||
category.id === 'advanced' ? 'bg-purple-400' :
|
||||
category.id === 'pro' ? 'bg-yellow-400' :
|
||||
category.id === 'endurance' ? 'bg-orange-400' :
|
||||
'bg-red-400'
|
||||
}`}
|
||||
style={{ width: `${category.percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">{category.percentage}% of drivers</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LEADERBOARD PREVIEW COMPONENT
|
||||
// ============================================================================
|
||||
@@ -258,6 +345,7 @@ function LeaderboardPreview({ drivers, onDriverClick }: LeaderboardPreviewProps)
|
||||
<div className="divide-y divide-charcoal-outline/50">
|
||||
{top5.map((driver, index) => {
|
||||
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
||||
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
|
||||
const position = index + 1;
|
||||
|
||||
return (
|
||||
@@ -285,6 +373,9 @@ function LeaderboardPreview({ drivers, onDriverClick }: LeaderboardPreviewProps)
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<Flag className="w-3 h-3" />
|
||||
{driver.nationality}
|
||||
{categoryConfig && (
|
||||
<span className={categoryConfig.color}>{categoryConfig.label}</span>
|
||||
)}
|
||||
<span className={levelConfig?.color}>{levelConfig?.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -336,6 +427,7 @@ function RecentActivity({ drivers, onDriverClick }: RecentActivityProps) {
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
{activeDrivers.map((driver) => {
|
||||
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
|
||||
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
|
||||
return (
|
||||
<button
|
||||
key={driver.id}
|
||||
@@ -350,7 +442,12 @@ function RecentActivity({ drivers, onDriverClick }: RecentActivityProps) {
|
||||
<p className="text-sm font-medium text-white truncate group-hover:text-performance-green transition-colors">
|
||||
{driver.name}
|
||||
</p>
|
||||
<p className={`text-xs ${levelConfig?.color}`}>{levelConfig?.label}</p>
|
||||
<div className="flex items-center justify-center gap-1 text-xs">
|
||||
{categoryConfig && (
|
||||
<span className={categoryConfig.color}>{categoryConfig.label}</span>
|
||||
)}
|
||||
<span className={levelConfig?.color}>{levelConfig?.label}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -516,6 +613,9 @@ export default function DriversPage() {
|
||||
{/* Skill Distribution */}
|
||||
{!searchQuery && <SkillDistribution drivers={drivers} />}
|
||||
|
||||
{/* Category Distribution */}
|
||||
{!searchQuery && <CategoryDistribution drivers={drivers} />}
|
||||
|
||||
{/* Leaderboard Preview */}
|
||||
<LeaderboardPreview drivers={filteredDrivers} onDriverClick={handleDriverClick} />
|
||||
|
||||
|
||||
@@ -422,7 +422,15 @@ export default function LeaguesPage() {
|
||||
// Group leagues by category for slider view
|
||||
const leaguesByCategory = CATEGORIES.reduce(
|
||||
(acc, category) => {
|
||||
acc[category.id] = searchFilteredLeagues.filter(category.filter);
|
||||
// First try to use the dedicated category field, fall back to scoring-based filtering
|
||||
acc[category.id] = searchFilteredLeagues.filter((league) => {
|
||||
// If league has a category field, use it directly
|
||||
if (league.category) {
|
||||
return league.category === category.id;
|
||||
}
|
||||
// Otherwise fall back to the existing scoring-based filter
|
||||
return category.filter(league);
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{} as Record<CategoryId, LeagueSummaryViewModel[]>,
|
||||
|
||||
@@ -213,6 +213,12 @@ export default function TeamDetailPage() {
|
||||
|
||||
<div className="flex items-center gap-4 text-sm text-gray-400">
|
||||
<span>{memberships.length} {memberships.length === 1 ? 'member' : 'members'}</span>
|
||||
{team.category && (
|
||||
<span className="flex items-center gap-1 text-purple-400">
|
||||
<span className="w-2 h-2 rounded-full bg-purple-400"></span>
|
||||
{team.category}
|
||||
</span>
|
||||
)}
|
||||
{team.createdAt && (
|
||||
<span>
|
||||
Founded {new Date(team.createdAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
|
||||
@@ -267,6 +273,9 @@ export default function TeamDetailPage() {
|
||||
<h3 className="text-xl font-semibold text-white mb-4">Quick Stats</h3>
|
||||
<div className="space-y-3">
|
||||
<StatItem label="Members" value={memberships.length.toString()} color="text-primary-blue" />
|
||||
{team.category && (
|
||||
<StatItem label="Category" value={team.category} color="text-purple-400" />
|
||||
)}
|
||||
{leagueCount > 0 && (
|
||||
<StatItem label="Leagues" value={leagueCount.toString()} color="text-green-400" />
|
||||
)}
|
||||
|
||||
@@ -416,6 +416,12 @@ export default function TeamLeaderboardPage() {
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500 flex-wrap">
|
||||
<span className={`${levelConfig?.color}`}>{levelConfig?.label}</span>
|
||||
{team.category && (
|
||||
<span className="flex items-center gap-1 text-purple-400">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-purple-400"></span>
|
||||
{team.category}
|
||||
</span>
|
||||
)}
|
||||
{team.region && (
|
||||
<span className="flex items-center gap-1 text-gray-400">
|
||||
<Globe className="w-3 h-3 text-neon-aqua" />
|
||||
@@ -497,4 +503,4 @@ export default function TeamLeaderboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,9 +92,17 @@ export default function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeade
|
||||
<p className="text-white font-medium truncate group-hover:text-purple-400 transition-colors">
|
||||
{team.name}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||
<Users className="w-3 h-3" />
|
||||
{team.memberCount} members
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500 flex-wrap">
|
||||
{team.category && (
|
||||
<span className="flex items-center gap-1 text-purple-400">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-purple-400"></span>
|
||||
{team.category}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<Users className="w-3 h-3" />
|
||||
{team.memberCount} members
|
||||
</span>
|
||||
<span className={levelConfig?.color}>{levelConfig?.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,48 @@ function getChampionshipLabel(type?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCategoryLabel(category?: string): string {
|
||||
if (!category) return '';
|
||||
|
||||
switch (category) {
|
||||
case 'driver':
|
||||
return 'Driver';
|
||||
case 'team':
|
||||
return 'Team';
|
||||
case 'nations':
|
||||
return 'Nations';
|
||||
case 'trophy':
|
||||
return 'Trophy';
|
||||
case 'endurance':
|
||||
return 'Endurance';
|
||||
case 'sprint':
|
||||
return 'Sprint';
|
||||
default:
|
||||
return category.charAt(0).toUpperCase() + category.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getCategoryColor(category?: string): string {
|
||||
if (!category) return 'bg-gray-500/20 text-gray-400 border-gray-500/30';
|
||||
|
||||
switch (category) {
|
||||
case 'driver':
|
||||
return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
|
||||
case 'team':
|
||||
return 'bg-blue-500/20 text-blue-400 border-blue-500/30';
|
||||
case 'nations':
|
||||
return 'bg-green-500/20 text-green-400 border-green-500/30';
|
||||
case 'trophy':
|
||||
return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30';
|
||||
case 'endurance':
|
||||
return 'bg-orange-500/20 text-orange-400 border-orange-500/30';
|
||||
case 'sprint':
|
||||
return 'bg-red-500/20 text-red-400 border-red-500/30';
|
||||
default:
|
||||
return 'bg-gray-500/20 text-gray-400 border-gray-500/30';
|
||||
}
|
||||
}
|
||||
|
||||
function getGameColor(gameId?: string): string {
|
||||
switch (gameId) {
|
||||
case 'iracing':
|
||||
@@ -81,6 +123,8 @@ export default function LeagueCard({ league, onClick }: LeagueCardProps) {
|
||||
const gameColorClass = getGameColor(league.scoring?.gameId);
|
||||
const isNew = isNewLeague(league.createdAt);
|
||||
const isTeamLeague = league.maxTeams && league.maxTeams > 0;
|
||||
const categoryLabel = getCategoryLabel(league.category);
|
||||
const categoryColorClass = getCategoryColor(league.category);
|
||||
|
||||
// Calculate fill percentage - use teams for team leagues, drivers otherwise
|
||||
const usedSlots = isTeamLeague ? (league.usedTeamSlots ?? 0) : (league.usedDriverSlots ?? 0);
|
||||
@@ -128,6 +172,11 @@ export default function LeagueCard({ league, onClick }: LeagueCardProps) {
|
||||
{league.scoring.gameName}
|
||||
</span>
|
||||
)}
|
||||
{league.category && (
|
||||
<span className={`px-2 py-0.5 rounded-full text-[10px] font-semibold border ${categoryColorClass}`}>
|
||||
{categoryLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Championship Type Badge - Top Right */}
|
||||
|
||||
@@ -91,7 +91,13 @@ export default function FeaturedRecruiting({ teams, onTeamClick }: FeaturedRecru
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 line-clamp-2 mb-3">{team.description}</p>
|
||||
|
||||
<div className="flex items-center gap-3 text-xs text-gray-400">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-400 flex-wrap">
|
||||
{team.category && (
|
||||
<span className="flex items-center gap-1 text-purple-400">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-purple-400"></span>
|
||||
{team.category}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<Users className="w-3 h-3" />
|
||||
{team.memberCount}
|
||||
|
||||
@@ -88,8 +88,9 @@ export default function SkillLevelSection({
|
||||
id={team.id}
|
||||
name={team.name}
|
||||
description={team.description ?? ''}
|
||||
logo={team.logoUrl}
|
||||
memberCount={team.memberCount}
|
||||
rating={null}
|
||||
rating={team.rating}
|
||||
totalWins={team.totalWins}
|
||||
totalRaces={team.totalRaces}
|
||||
performanceLevel={team.performanceLevel as SkillLevel}
|
||||
@@ -97,10 +98,11 @@ export default function SkillLevelSection({
|
||||
specialization={specialization(team.specialization)}
|
||||
region={team.region ?? ''}
|
||||
languages={team.languages}
|
||||
category={team.category}
|
||||
onClick={() => onTeamClick(team.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ interface TeamCardProps {
|
||||
region?: string;
|
||||
languages?: string[] | undefined;
|
||||
leagues?: string[];
|
||||
category?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
@@ -77,6 +78,7 @@ export default function TeamCard({
|
||||
specialization,
|
||||
region,
|
||||
languages,
|
||||
category,
|
||||
onClick,
|
||||
}: TeamCardProps) {
|
||||
const { mediaService } = useServices();
|
||||
@@ -119,21 +121,27 @@ export default function TeamCard({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Performance Level */}
|
||||
{performanceBadge && (
|
||||
<div className="mt-1.5 flex items-center gap-2">
|
||||
{/* Performance Level & Category */}
|
||||
<div className="mt-1.5 flex items-center gap-2 flex-wrap">
|
||||
{performanceBadge && (
|
||||
<span className={`flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium border ${performanceBadge.bgColor}`}>
|
||||
<performanceBadge.icon className={`w-3 h-3 ${performanceBadge.color}`} />
|
||||
<span className={performanceBadge.color}>{performanceBadge.label}</span>
|
||||
</span>
|
||||
{specializationBadge && (
|
||||
<span className="flex items-center gap-1 text-[10px] text-gray-500">
|
||||
<specializationBadge.icon className={`w-3 h-3 ${specializationBadge.color}`} />
|
||||
{specializationBadge.label}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{specializationBadge && (
|
||||
<span className="flex items-center gap-1 text-[10px] text-gray-500">
|
||||
<specializationBadge.icon className={`w-3 h-3 ${specializationBadge.color}`} />
|
||||
{specializationBadge.label}
|
||||
</span>
|
||||
)}
|
||||
{category && (
|
||||
<span className="flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium bg-purple-500/20 border border-purple-500/30 text-purple-400">
|
||||
<span className="w-2 h-2 rounded-full bg-purple-400"></span>
|
||||
{category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,13 @@ export default function TeamLeaderboardPreview({
|
||||
<p className="text-white font-medium truncate group-hover:text-purple-400 transition-colors">
|
||||
{team.name}
|
||||
</p>
|
||||
<div className="flex items-center gap-3 text-xs text-gray-500">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500 flex-wrap">
|
||||
{team.category && (
|
||||
<span className="flex items-center gap-1 text-purple-400">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-purple-400"></span>
|
||||
{team.category}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<Users className="w-3 h-3" />
|
||||
{team.memberCount}
|
||||
@@ -161,7 +167,7 @@ export default function TeamLeaderboardPreview({
|
||||
{/* Rating */}
|
||||
<div className="text-right">
|
||||
<p className="text-purple-400 font-mono font-semibold">
|
||||
{'—'}
|
||||
{typeof team.rating === 'number' ? Math.round(team.rating).toLocaleString() : '—'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">Rating</p>
|
||||
</div>
|
||||
@@ -172,4 +178,4 @@ export default function TeamLeaderboardPreview({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,13 @@ export default function TopThreePodium({ teams, onClick }: TopThreePodiumProps)
|
||||
{team.name}
|
||||
</p>
|
||||
|
||||
{/* Category */}
|
||||
{team.category && (
|
||||
<p className="text-xs text-purple-400 text-center mt-1">
|
||||
{team.category}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Rating */}
|
||||
<p className={`text-lg md:text-xl font-mono font-bold ${getPositionColor(position)} text-center`}>
|
||||
{'—'}
|
||||
@@ -172,4 +179,4 @@ export default function TopThreePodium({ teams, onClick }: TopThreePodiumProps)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ export class LeagueService {
|
||||
structureSummary: league.scoring?.scoringPresetName ?? 'Custom rules',
|
||||
scoringPatternSummary: league.scoring?.scoringPatternSummary,
|
||||
timingSummary: league.timingSummary ?? '',
|
||||
...(league.category ? { category: league.category } : {}),
|
||||
...(league.scoring ? { scoring: league.scoring } : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DeleteMediaViewModel } from '@/lib/view-models/DeleteMediaViewModel';
|
||||
import { MediaViewModel } from '@/lib/view-models/MediaViewModel';
|
||||
import { UploadMediaViewModel } from '@/lib/view-models/UploadMediaViewModel';
|
||||
import type { MediaApiClient } from '../../api/media/MediaApiClient';
|
||||
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
|
||||
|
||||
// Local request shape mirroring the media upload API contract until a generated type is available
|
||||
type UploadMediaRequest = { file: File; type: string; category?: string };
|
||||
@@ -43,6 +44,7 @@ export class MediaService {
|
||||
|
||||
/**
|
||||
* Get team logo URL
|
||||
* Returns relative URL for proxying through Next.js rewrites
|
||||
*/
|
||||
getTeamLogo(teamId: string): string {
|
||||
return `/api/media/teams/${teamId}/logo`;
|
||||
@@ -50,6 +52,7 @@ export class MediaService {
|
||||
|
||||
/**
|
||||
* Get driver avatar URL
|
||||
* Returns relative URL for proxying through Next.js rewrites
|
||||
*/
|
||||
getDriverAvatar(driverId: string): string {
|
||||
return `/api/media/avatar/${driverId}`;
|
||||
@@ -57,6 +60,7 @@ export class MediaService {
|
||||
|
||||
/**
|
||||
* Get league cover URL
|
||||
* Returns relative URL for proxying through Next.js rewrites
|
||||
*/
|
||||
getLeagueCover(leagueId: string): string {
|
||||
return `/api/media/leagues/${leagueId}/cover`;
|
||||
@@ -64,6 +68,7 @@ export class MediaService {
|
||||
|
||||
/**
|
||||
* Get league logo URL
|
||||
* Returns relative URL for proxying through Next.js rewrites
|
||||
*/
|
||||
getLeagueLogo(leagueId: string): string {
|
||||
return `/api/media/leagues/${leagueId}/logo`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
@@ -10,6 +10,7 @@ export interface DashboardDriverSummaryDTO {
|
||||
name: string;
|
||||
country: string;
|
||||
avatarUrl: string;
|
||||
category?: string;
|
||||
rating?: number;
|
||||
globalRank?: number;
|
||||
totalRaces: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
@@ -12,4 +12,5 @@ export interface DriverDTO {
|
||||
country: string;
|
||||
bio?: string;
|
||||
joinedAt: string;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
@@ -10,6 +10,7 @@ export interface DriverLeaderboardItemDTO {
|
||||
name: string;
|
||||
rating: number;
|
||||
skillLevel: string;
|
||||
category?: string;
|
||||
nationality: string;
|
||||
racesCompleted: number;
|
||||
wins: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
@@ -12,6 +12,7 @@ export interface DriverProfileDriverSummaryDTO {
|
||||
avatarUrl: string;
|
||||
iracingId?: string;
|
||||
joinedAt: string;
|
||||
category?: string;
|
||||
rating?: number;
|
||||
globalRank?: number;
|
||||
consistency?: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* Spec SHA256: 16aae91ec085d450f377d8c914d93df23e86783b2b124fedd1075307d2c5b17f
|
||||
* Spec SHA256: c6aeaeed5d0c2f34dff6fe153482ffa67609b1132bb683867accabf828a8086e
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:generate-types
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user