contract testing
This commit is contained in:
@@ -97,7 +97,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
@@ -213,28 +213,28 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"impressions": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"impressionsChange": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueViewers": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"viewersChange": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"races": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"drivers": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"exposure": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"exposureChange": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -252,13 +252,13 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"activeSponsorships": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"totalInvestment": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"costPerThousandViews": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -334,28 +334,32 @@
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"views": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"date"
|
||||
"date",
|
||||
"views"
|
||||
]
|
||||
},
|
||||
"PrivacySettingsDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"publicProfile": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"showStats": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"showActiveSponsorships": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"allowDirectContact": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -380,22 +384,22 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"emailNewSponsorships": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"emailWeeklyReport": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"emailRaceAlerts": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"emailPaymentAlerts": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"emailNewOpportunities": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
},
|
||||
"emailContractExpiry": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -442,13 +446,13 @@
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"vatAmount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"totalAmount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -519,21 +523,33 @@
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"iracingId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"position": {
|
||||
"type": "string"
|
||||
},
|
||||
"races": {
|
||||
"type": "string"
|
||||
},
|
||||
"impressions": {
|
||||
"type": "string"
|
||||
},
|
||||
"team": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"iracingId",
|
||||
"name",
|
||||
"country"
|
||||
"country",
|
||||
"position",
|
||||
"races",
|
||||
"impressions",
|
||||
"team"
|
||||
]
|
||||
},
|
||||
"CreateSponsorInputDTO": {
|
||||
@@ -555,22 +571,22 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"totalSpent": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"pendingAmount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"nextPaymentDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"nextPaymentAmount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"activeSponsorships": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"averageMonthlySpend": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -595,10 +611,10 @@
|
||||
"type": "string"
|
||||
},
|
||||
"drivers": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"avgViewsPerRace": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1197,12 +1213,6 @@
|
||||
},
|
||||
"scheduledAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"isMyLeague": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1211,9 +1221,7 @@
|
||||
"leagueName",
|
||||
"track",
|
||||
"car",
|
||||
"scheduledAt",
|
||||
"status",
|
||||
"isMyLeague"
|
||||
"scheduledAt"
|
||||
]
|
||||
},
|
||||
"DashboardLeagueStandingSummaryDTO": {
|
||||
@@ -1373,6 +1381,36 @@
|
||||
"leagueName"
|
||||
]
|
||||
},
|
||||
"AllRacesStatusFilterDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"value",
|
||||
"label"
|
||||
]
|
||||
},
|
||||
"AllRacesLeagueFilterDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"UpdatePaymentStatusInputDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1384,7 +1422,7 @@
|
||||
"paymentId"
|
||||
]
|
||||
},
|
||||
"PaymentDto": {
|
||||
"PaymentDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1395,7 +1433,7 @@
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"MembershipFeeDto": {
|
||||
"MembershipFeeDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1410,7 +1448,7 @@
|
||||
"leagueId"
|
||||
]
|
||||
},
|
||||
"MemberPaymentDto": {
|
||||
"MemberPaymentDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1441,7 +1479,7 @@
|
||||
"netAmount"
|
||||
]
|
||||
},
|
||||
"PrizeDto": {
|
||||
"PrizeDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1472,7 +1510,7 @@
|
||||
"amount"
|
||||
]
|
||||
},
|
||||
"WalletDto": {
|
||||
"WalletDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1512,7 +1550,7 @@
|
||||
"currency"
|
||||
]
|
||||
},
|
||||
"TransactionDto": {
|
||||
"TransactionDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -1527,18 +1565,7 @@
|
||||
"walletId"
|
||||
]
|
||||
},
|
||||
"PaymentDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"UploadMediaOutputDTO": {
|
||||
"DeletePrizeResultDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
@@ -1549,11 +1576,22 @@
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"UploadMediaOutputDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"success"
|
||||
]
|
||||
},
|
||||
"UpdateAvatarOutputDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1610,8 +1648,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"uploadedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "number"
|
||||
@@ -1639,7 +1676,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1840,33 +1877,11 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownerId": {
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"type": "object"
|
||||
},
|
||||
"maxDrivers": {
|
||||
"type": "number"
|
||||
},
|
||||
"sessionDuration": {
|
||||
"type": "number"
|
||||
},
|
||||
"visibility": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"ownerId",
|
||||
"settings",
|
||||
"maxDrivers"
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"LeagueSummaryDTO": {
|
||||
@@ -2283,151 +2298,6 @@
|
||||
"driverId"
|
||||
]
|
||||
},
|
||||
"DriverProfileDriverSummaryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"country",
|
||||
"avatarUrl"
|
||||
]
|
||||
},
|
||||
"DriverProfileStatsDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"totalRaces": {
|
||||
"type": "number"
|
||||
},
|
||||
"wins": {
|
||||
"type": "number"
|
||||
},
|
||||
"podiums": {
|
||||
"type": "number"
|
||||
},
|
||||
"dnfs": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"totalRaces",
|
||||
"wins",
|
||||
"podiums",
|
||||
"dnfs"
|
||||
]
|
||||
},
|
||||
"DriverProfileFinishDistributionDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"totalRaces": {
|
||||
"type": "number"
|
||||
},
|
||||
"wins": {
|
||||
"type": "number"
|
||||
},
|
||||
"podiums": {
|
||||
"type": "number"
|
||||
},
|
||||
"topTen": {
|
||||
"type": "number"
|
||||
},
|
||||
"dnfs": {
|
||||
"type": "number"
|
||||
},
|
||||
"other": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"totalRaces",
|
||||
"wins",
|
||||
"podiums",
|
||||
"topTen",
|
||||
"dnfs",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"DriverProfileTeamMembershipDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"teamId": {
|
||||
"type": "string"
|
||||
},
|
||||
"teamName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"teamId",
|
||||
"teamName"
|
||||
]
|
||||
},
|
||||
"DriverProfileSocialFriendSummaryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"country",
|
||||
"avatarUrl"
|
||||
]
|
||||
},
|
||||
"DriverProfileSocialSummaryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"friendsCount": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"friendsCount"
|
||||
]
|
||||
},
|
||||
"DriverProfileAchievementDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"description"
|
||||
]
|
||||
},
|
||||
"GetDriverOutputDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2488,6 +2358,151 @@
|
||||
"driverId"
|
||||
]
|
||||
},
|
||||
"DriverProfileTeamMembershipDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"teamId": {
|
||||
"type": "string"
|
||||
},
|
||||
"teamName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"teamId",
|
||||
"teamName"
|
||||
]
|
||||
},
|
||||
"DriverProfileStatsDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"totalRaces": {
|
||||
"type": "number"
|
||||
},
|
||||
"wins": {
|
||||
"type": "number"
|
||||
},
|
||||
"podiums": {
|
||||
"type": "number"
|
||||
},
|
||||
"dnfs": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"totalRaces",
|
||||
"wins",
|
||||
"podiums",
|
||||
"dnfs"
|
||||
]
|
||||
},
|
||||
"DriverProfileSocialSummaryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"friendsCount": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"friendsCount"
|
||||
]
|
||||
},
|
||||
"DriverProfileSocialFriendSummaryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"country",
|
||||
"avatarUrl"
|
||||
]
|
||||
},
|
||||
"DriverProfileFinishDistributionDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"totalRaces": {
|
||||
"type": "number"
|
||||
},
|
||||
"wins": {
|
||||
"type": "number"
|
||||
},
|
||||
"podiums": {
|
||||
"type": "number"
|
||||
},
|
||||
"topTen": {
|
||||
"type": "number"
|
||||
},
|
||||
"dnfs": {
|
||||
"type": "number"
|
||||
},
|
||||
"other": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"totalRaces",
|
||||
"wins",
|
||||
"podiums",
|
||||
"topTen",
|
||||
"dnfs",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"DriverProfileDriverSummaryDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatarUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"country",
|
||||
"avatarUrl"
|
||||
]
|
||||
},
|
||||
"DriverProfileAchievementDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"description"
|
||||
]
|
||||
},
|
||||
"DriverLeaderboardItemDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
257
apps/api/src/shared/testing/contractValidation.test.ts
Normal file
257
apps/api/src/shared/testing/contractValidation.test.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Contract Validation Tests for API
|
||||
*
|
||||
* These tests validate that the API DTOs and OpenAPI spec are consistent
|
||||
* and that the generated types will be compatible with the website.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { glob } from 'glob';
|
||||
|
||||
interface OpenAPISchema {
|
||||
type?: string;
|
||||
format?: string;
|
||||
$ref?: string;
|
||||
items?: OpenAPISchema;
|
||||
properties?: Record<string, OpenAPISchema>;
|
||||
required?: string[];
|
||||
enum?: string[];
|
||||
nullable?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface OpenAPISpec {
|
||||
openapi: string;
|
||||
info: {
|
||||
title: string;
|
||||
description: string;
|
||||
version: string;
|
||||
};
|
||||
paths: Record<string, any>;
|
||||
components: {
|
||||
schemas: Record<string, OpenAPISchema>;
|
||||
};
|
||||
}
|
||||
|
||||
describe('API Contract Validation', () => {
|
||||
const apiRoot = path.join(__dirname, '../../..');
|
||||
const openapiPath = path.join(apiRoot, 'openapi.json');
|
||||
const generatedTypesDir = path.join(apiRoot, '../website/lib/types/generated');
|
||||
|
||||
describe('OpenAPI Spec Integrity', () => {
|
||||
it('should have a valid OpenAPI spec file', async () => {
|
||||
const specExists = await fs.access(openapiPath).then(() => true).catch(() => false);
|
||||
expect(specExists).toBe(true);
|
||||
});
|
||||
|
||||
it('should have a valid JSON structure', async () => {
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
expect(() => JSON.parse(content)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should have required OpenAPI fields', async () => {
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
const spec: OpenAPISpec = JSON.parse(content);
|
||||
|
||||
expect(spec.openapi).toMatch(/^3\.\d+\.\d+$/);
|
||||
expect(spec.info).toBeDefined();
|
||||
expect(spec.info.title).toBeDefined();
|
||||
expect(spec.info.version).toBeDefined();
|
||||
expect(spec.components).toBeDefined();
|
||||
expect(spec.components.schemas).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have no circular references in schemas', async () => {
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
const spec: OpenAPISpec = JSON.parse(content);
|
||||
const schemas = spec.components.schemas;
|
||||
|
||||
const visited = new Set<string>();
|
||||
const visiting = new Set<string>();
|
||||
|
||||
function detectCircular(schemaName: string): boolean {
|
||||
if (visiting.has(schemaName)) return true;
|
||||
if (visited.has(schemaName)) return false;
|
||||
|
||||
visiting.add(schemaName);
|
||||
const schema = schemas[schemaName];
|
||||
|
||||
if (!schema) {
|
||||
visiting.delete(schemaName);
|
||||
visited.add(schemaName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check properties for references
|
||||
if (schema.properties) {
|
||||
for (const prop of Object.values(schema.properties)) {
|
||||
if (prop.$ref) {
|
||||
const refName = prop.$ref.split('/').pop();
|
||||
if (refName && detectCircular(refName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (prop.items?.$ref) {
|
||||
const refName = prop.items.$ref.split('/').pop();
|
||||
if (refName && detectCircular(refName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visiting.delete(schemaName);
|
||||
visited.add(schemaName);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const schemaName of Object.keys(schemas)) {
|
||||
expect(detectCircular(schemaName)).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('DTO Consistency', () => {
|
||||
it('should have DTO files for all schemas', async () => {
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
const spec: OpenAPISpec = JSON.parse(content);
|
||||
const schemas = Object.keys(spec.components.schemas);
|
||||
|
||||
const generatedFiles = await fs.readdir(generatedTypesDir);
|
||||
const generatedDTOs = generatedFiles
|
||||
.filter(f => f.endsWith('.ts'))
|
||||
.map(f => f.replace('.ts', ''));
|
||||
|
||||
// All schemas should have corresponding generated DTOs
|
||||
for (const schema of schemas) {
|
||||
expect(generatedDTOs).toContain(schema);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have consistent property types between DTOs and schemas', async () => {
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
const spec: OpenAPISpec = JSON.parse(content);
|
||||
const schemas = spec.components.schemas;
|
||||
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
const dtoPath = path.join(generatedTypesDir, `${schemaName}.ts`);
|
||||
const dtoExists = await fs.access(dtoPath).then(() => true).catch(() => false);
|
||||
|
||||
if (!dtoExists) continue;
|
||||
|
||||
const dtoContent = await fs.readFile(dtoPath, 'utf-8');
|
||||
|
||||
// Check that all required properties are present
|
||||
if (schema.required) {
|
||||
for (const requiredProp of schema.required) {
|
||||
expect(dtoContent).toContain(requiredProp);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all properties are present
|
||||
if (schema.properties) {
|
||||
for (const propName of Object.keys(schema.properties)) {
|
||||
expect(dtoContent).toContain(propName);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Type Generation Integrity', () => {
|
||||
it('should have valid TypeScript syntax in generated files', async () => {
|
||||
const files = await fs.readdir(generatedTypesDir);
|
||||
const dtos = files.filter(f => f.endsWith('.ts'));
|
||||
|
||||
for (const file of dtos) {
|
||||
const content = await fs.readFile(path.join(generatedTypesDir, file), 'utf-8');
|
||||
|
||||
// Basic TypeScript syntax checks
|
||||
expect(content).toContain('export interface');
|
||||
expect(content).toContain('{');
|
||||
expect(content).toContain('}');
|
||||
|
||||
// Should not have syntax errors (basic check)
|
||||
expect(content).not.toContain('undefined;');
|
||||
expect(content).not.toContain('any;');
|
||||
}
|
||||
});
|
||||
|
||||
it('should have proper imports for dependencies', async () => {
|
||||
const files = await fs.readdir(generatedTypesDir);
|
||||
const dtos = files.filter(f => f.endsWith('.ts'));
|
||||
|
||||
for (const file of dtos) {
|
||||
const content = await fs.readFile(path.join(generatedTypesDir, file), 'utf-8');
|
||||
const importMatches = content.match(/import type \{ (\w+) \} from '\.\/(\w+)';/g) || [];
|
||||
|
||||
for (const importLine of importMatches) {
|
||||
const match = importLine.match(/import type \{ (\w+) \} from '\.\/(\w+)';/);
|
||||
if (match) {
|
||||
const [, importedType, fromFile] = match;
|
||||
expect(importedType).toBe(fromFile);
|
||||
|
||||
// Check that the imported file exists
|
||||
const importedPath = path.join(generatedTypesDir, `${fromFile}.ts`);
|
||||
const exists = await fs.access(importedPath).then(() => true).catch(() => false);
|
||||
expect(exists).toBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contract Compatibility', () => {
|
||||
it('should maintain backward compatibility for existing DTOs', async () => {
|
||||
// This test ensures that when regenerating types, existing properties aren't removed
|
||||
// unless explicitly intended
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
const spec: OpenAPISpec = JSON.parse(content);
|
||||
|
||||
// Check critical DTOs that are likely used in production
|
||||
const criticalDTOs = [
|
||||
'RequestAvatarGenerationInputDTO',
|
||||
'RequestAvatarGenerationOutputDTO',
|
||||
'UploadMediaInputDTO',
|
||||
'UploadMediaOutputDTO',
|
||||
'RaceDTO',
|
||||
'DriverDTO'
|
||||
];
|
||||
|
||||
for (const dtoName of criticalDTOs) {
|
||||
if (spec.components.schemas[dtoName]) {
|
||||
const dtoPath = path.join(generatedTypesDir, `${dtoName}.ts`);
|
||||
const exists = await fs.access(dtoPath).then(() => true).catch(() => false);
|
||||
expect(exists).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle nullable fields correctly', async () => {
|
||||
const content = await fs.readFile(openapiPath, 'utf-8');
|
||||
const spec: OpenAPISpec = JSON.parse(content);
|
||||
const schemas = spec.components.schemas;
|
||||
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
if (!schema.properties) continue;
|
||||
|
||||
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
||||
const dtoPath = path.join(generatedTypesDir, `${schemaName}.ts`);
|
||||
const dtoContent = await fs.readFile(dtoPath, 'utf-8');
|
||||
|
||||
if (propSchema.nullable) {
|
||||
// Nullable properties should be optional in TypeScript
|
||||
const propRegex = new RegExp(`${propName}\\??:\\s*([\\w\\[\\]<>|]+)\\s*;`);
|
||||
const match = dtoContent.match(propRegex);
|
||||
if (match) {
|
||||
// Should include null in the type or be optional
|
||||
expect(match[1]).toContain('| null');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
230
apps/website/lib/types/contractConsumption.test.ts
Normal file
230
apps/website/lib/types/contractConsumption.test.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* Contract Consumption Tests for Website
|
||||
*
|
||||
* These tests validate that the website can properly consume and use
|
||||
* the generated API types without type errors.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { glob } from 'glob';
|
||||
|
||||
// Import all generated DTOs to ensure they compile
|
||||
import type { RequestAvatarGenerationInputDTO } from './generated/RequestAvatarGenerationInputDTO';
|
||||
import type { RequestAvatarGenerationOutputDTO } from './generated/RequestAvatarGenerationOutputDTO';
|
||||
import type { UploadMediaInputDTO } from './generated/UploadMediaInputDTO';
|
||||
import type { UploadMediaOutputDTO } from './generated/UploadMediaOutputDTO';
|
||||
import type { GetMediaOutputDTO } from './generated/GetMediaOutputDTO';
|
||||
import type { DeleteMediaOutputDTO } from './generated/DeleteMediaOutputDTO';
|
||||
import type { GetAvatarOutputDTO } from './generated/GetAvatarOutputDTO';
|
||||
import type { UpdateAvatarInputDTO } from './generated/UpdateAvatarInputDTO';
|
||||
import type { UpdateAvatarOutputDTO } from './generated/UpdateAvatarOutputDTO';
|
||||
import type { RaceDTO } from './generated/RaceDTO';
|
||||
import type { DriverDTO } from './generated/DriverDTO';
|
||||
|
||||
describe('Website Contract Consumption', () => {
|
||||
const generatedTypesDir = path.join(__dirname, 'generated');
|
||||
|
||||
describe('Generated Types Availability', () => {
|
||||
it('should have generated types directory', async () => {
|
||||
const exists = await fs.access(generatedTypesDir).then(() => true).catch(() => false);
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
|
||||
it('should have expected DTO files', async () => {
|
||||
const requiredDTOs = [
|
||||
'RequestAvatarGenerationInputDTO.ts',
|
||||
'RequestAvatarGenerationOutputDTO.ts',
|
||||
'UploadMediaInputDTO.ts',
|
||||
'UploadMediaOutputDTO.ts',
|
||||
'GetMediaOutputDTO.ts',
|
||||
'DeleteMediaOutputDTO.ts',
|
||||
'GetAvatarOutputDTO.ts',
|
||||
'UpdateAvatarInputDTO.ts',
|
||||
'UpdateAvatarOutputDTO.ts',
|
||||
'RaceDTO.ts',
|
||||
'DriverDTO.ts'
|
||||
];
|
||||
|
||||
const files = await fs.readdir(generatedTypesDir);
|
||||
const tsFiles = files.filter(f => f.endsWith('.ts'));
|
||||
|
||||
for (const required of requiredDTOs) {
|
||||
expect(tsFiles).toContain(required);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have no syntax errors in generated files', async () => {
|
||||
const files = await fs.readdir(generatedTypesDir);
|
||||
const dtos = files.filter(f => f.endsWith('.ts'));
|
||||
|
||||
for (const file of dtos) {
|
||||
const content = await fs.readFile(path.join(generatedTypesDir, file), 'utf-8');
|
||||
|
||||
// Basic syntax validation
|
||||
expect(content).toContain('export interface');
|
||||
expect(content).toContain('{');
|
||||
expect(content).toContain('}');
|
||||
|
||||
// Should not have common syntax errors
|
||||
expect(content).not.toMatch(/interface\s+\w+\s*\{\s*\}/); // Empty interfaces
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Type Compatibility', () => {
|
||||
it('should allow creating valid DTO objects', () => {
|
||||
// Test RequestAvatarGenerationInputDTO
|
||||
const avatarInput: RequestAvatarGenerationInputDTO = {
|
||||
userId: 'user-123',
|
||||
facePhotoData: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
||||
suitColor: 'red'
|
||||
};
|
||||
expect(avatarInput.userId).toBe('user-123');
|
||||
|
||||
// Test RequestAvatarGenerationOutputDTO (success case)
|
||||
const avatarOutputSuccess: RequestAvatarGenerationOutputDTO = {
|
||||
success: true,
|
||||
requestId: 'req-123',
|
||||
avatarUrls: ['https://example.com/avatar1.png', 'https://example.com/avatar2.png']
|
||||
};
|
||||
expect(avatarOutputSuccess.success).toBe(true);
|
||||
expect(avatarOutputSuccess.avatarUrls).toHaveLength(2);
|
||||
|
||||
// Test RequestAvatarGenerationOutputDTO (failure case)
|
||||
const avatarOutputFailure: RequestAvatarGenerationOutputDTO = {
|
||||
success: false,
|
||||
errorMessage: 'Generation failed'
|
||||
};
|
||||
expect(avatarOutputFailure.success).toBe(false);
|
||||
expect(avatarOutputFailure.errorMessage).toBe('Generation failed');
|
||||
});
|
||||
|
||||
it('should handle optional fields correctly', () => {
|
||||
// Test DTOs with optional fields
|
||||
const uploadInput: UploadMediaInputDTO = {
|
||||
type: 'image',
|
||||
category: 'avatar'
|
||||
};
|
||||
expect(uploadInput.type).toBe('image');
|
||||
|
||||
// Test with minimal required fields
|
||||
const minimalUpload: UploadMediaInputDTO = {
|
||||
type: 'image'
|
||||
};
|
||||
expect(minimalUpload.type).toBe('image');
|
||||
});
|
||||
|
||||
it('should support array types', () => {
|
||||
const avatarOutput: RequestAvatarGenerationOutputDTO = {
|
||||
success: true,
|
||||
requestId: 'req-123',
|
||||
avatarUrls: ['url1', 'url2', 'url3']
|
||||
};
|
||||
|
||||
expect(Array.isArray(avatarOutput.avatarUrls)).toBe(true);
|
||||
expect(avatarOutput.avatarUrls?.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should support nested object types', () => {
|
||||
const race: RaceDTO = {
|
||||
id: 'race-123',
|
||||
name: 'Test Race',
|
||||
leagueId: 'league-456',
|
||||
trackName: 'Test Track',
|
||||
startTime: new Date().toISOString(),
|
||||
status: 'scheduled',
|
||||
maxDrivers: 20,
|
||||
registeredDrivers: 5
|
||||
};
|
||||
|
||||
expect(race.id).toBe('race-123');
|
||||
expect(race.name).toBe('Test Race');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Service Integration', () => {
|
||||
it('should work with service layer patterns', () => {
|
||||
// Simulate a service method that uses DTOs
|
||||
function processAvatarRequest(input: RequestAvatarGenerationInputDTO): RequestAvatarGenerationOutputDTO {
|
||||
// Validate input
|
||||
if (!input.userId || !input.facePhotoData) {
|
||||
return {
|
||||
success: false,
|
||||
errorMessage: 'Missing required fields'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
requestId: `req-${Date.now()}`,
|
||||
avatarUrls: [`https://cdn.example.com/avatars/${input.userId}.png`]
|
||||
};
|
||||
}
|
||||
|
||||
const result = processAvatarRequest({
|
||||
userId: 'test-user',
|
||||
facePhotoData: 'base64data',
|
||||
suitColor: 'blue'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.avatarUrls).toBeDefined();
|
||||
expect(result.avatarUrls?.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle API response parsing', () => {
|
||||
// Simulate API response handling
|
||||
const mockApiResponse = {
|
||||
success: true,
|
||||
requestId: 'req-789',
|
||||
avatarUrls: ['https://cdn.example.com/avatar.png']
|
||||
};
|
||||
|
||||
// Type assertion to ensure it matches DTO
|
||||
const parsedResponse: RequestAvatarGenerationOutputDTO = mockApiResponse;
|
||||
|
||||
expect(parsedResponse.success).toBe(true);
|
||||
expect(parsedResponse.avatarUrls).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle missing optional fields', () => {
|
||||
// Test that optional fields can be omitted
|
||||
const minimalOutput: RequestAvatarGenerationOutputDTO = {
|
||||
success: false,
|
||||
errorMessage: 'Error occurred'
|
||||
};
|
||||
|
||||
expect(minimalOutput.success).toBe(false);
|
||||
expect(minimalOutput.errorMessage).toBe('Error occurred');
|
||||
// avatarUrls and requestId are optional in failure case
|
||||
});
|
||||
|
||||
it('should allow type narrowing based on success flag', () => {
|
||||
function handleAvatarResponse(response: RequestAvatarGenerationOutputDTO) {
|
||||
if (response.success) {
|
||||
// Success case - avatarUrls should be available
|
||||
expect(response.avatarUrls).toBeDefined();
|
||||
expect(response.avatarUrls!.length).toBeGreaterThan(0);
|
||||
} else {
|
||||
// Failure case - errorMessage should be available
|
||||
expect(response.errorMessage).toBeDefined();
|
||||
}
|
||||
}
|
||||
|
||||
handleAvatarResponse({
|
||||
success: true,
|
||||
requestId: 'req-1',
|
||||
avatarUrls: ['url1']
|
||||
});
|
||||
|
||||
handleAvatarResponse({
|
||||
success: false,
|
||||
errorMessage: 'Failed'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface GetRaceDetailParamsDTODTO {
|
||||
raceId: string;
|
||||
driverId: string;
|
||||
export interface AllRacesLeagueFilterDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface IracingAuthRedirectResult {
|
||||
redirectUrl: string;
|
||||
state: string;
|
||||
}
|
||||
export interface AllRacesStatusFilterDTO {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
@@ -8,6 +8,6 @@ export interface AvailableLeagueDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
game: string;
|
||||
drivers: number;
|
||||
avgViewsPerRace: number;
|
||||
drivers: string;
|
||||
avgViewsPerRace: string;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
|
||||
export interface BillingStatsDTO {
|
||||
totalSpent: number;
|
||||
pendingAmount: number;
|
||||
totalSpent: string;
|
||||
pendingAmount: string;
|
||||
nextPaymentDate: string;
|
||||
nextPaymentAmount: number;
|
||||
activeSponsorships: number;
|
||||
averageMonthlySpend: number;
|
||||
nextPaymentAmount: string;
|
||||
activeSponsorships: string;
|
||||
averageMonthlySpend: string;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,4 @@ export interface DashboardRaceSummaryDTO {
|
||||
track: string;
|
||||
car: string;
|
||||
scheduledAt: string;
|
||||
status: string;
|
||||
isMyLeague: boolean;
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
*/
|
||||
|
||||
export interface DeleteMediaOutputDTO {
|
||||
success: boolean;
|
||||
success: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface LoginParams {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
export interface DeletePrizeResultDTO {
|
||||
success: boolean;
|
||||
}
|
||||
@@ -6,7 +6,10 @@
|
||||
|
||||
export interface DriverDTO {
|
||||
id: string;
|
||||
iracingId: string;
|
||||
name: string;
|
||||
country: string;
|
||||
position: string;
|
||||
races: string;
|
||||
impressions: string;
|
||||
team: string;
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
export interface DriverProfileDriverSummaryDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
country: string;
|
||||
avatarUrl: string;
|
||||
iracingId: string | null;
|
||||
joinedAt: string;
|
||||
rating: number | null;
|
||||
globalRank: number | null;
|
||||
consistency: number | null;
|
||||
bio: string | null;
|
||||
totalDrivers: number | null;
|
||||
}
|
||||
|
||||
export interface DriverProfileStatsDTO {
|
||||
totalRaces: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
dnfs: number;
|
||||
avgFinish: number | null;
|
||||
bestFinish: number | null;
|
||||
worstFinish: number | null;
|
||||
finishRate: number | null;
|
||||
winRate: number | null;
|
||||
podiumRate: number | null;
|
||||
percentile: number | null;
|
||||
rating: number | null;
|
||||
consistency: number | null;
|
||||
overallRank: number | null;
|
||||
}
|
||||
|
||||
export interface DriverProfileFinishDistributionDTO {
|
||||
totalRaces: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
topTen: number;
|
||||
dnfs: number;
|
||||
other: number;
|
||||
}
|
||||
|
||||
export interface DriverProfileTeamMembershipDTO {
|
||||
teamId: string;
|
||||
teamName: string;
|
||||
teamTag: string | null;
|
||||
role: string;
|
||||
joinedAt: string;
|
||||
isCurrent: boolean;
|
||||
}
|
||||
|
||||
export interface DriverProfileSocialFriendSummaryDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
country: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface DriverProfileSocialSummaryDTO {
|
||||
friendsCount: number;
|
||||
friends: DriverProfileSocialFriendSummaryDTO[];
|
||||
}
|
||||
|
||||
export type DriverProfileSocialPlatform = 'twitter' | 'youtube' | 'twitch' | 'discord';
|
||||
|
||||
export type DriverProfileAchievementRarity = 'common' | 'rare' | 'epic' | 'legendary';
|
||||
|
||||
export interface DriverProfileAchievementDTO {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap';
|
||||
rarity: DriverProfileAchievementRarity;
|
||||
earnedAt: string;
|
||||
}
|
||||
|
||||
export interface DriverProfileSocialHandleDTO {
|
||||
platform: DriverProfileSocialPlatform;
|
||||
handle: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface DriverProfileExtendedProfileDTO {
|
||||
socialHandles: DriverProfileSocialHandleDTO[];
|
||||
achievements: DriverProfileAchievementDTO[];
|
||||
racingStyle: string;
|
||||
favoriteTrack: string;
|
||||
favoriteCar: string;
|
||||
timezone: string;
|
||||
availableHours: string;
|
||||
lookingForTeam: boolean;
|
||||
openToRequests: boolean;
|
||||
}
|
||||
|
||||
export interface DriverProfileDTO {
|
||||
currentDriver: DriverProfileDriverSummaryDTO | null;
|
||||
stats: DriverProfileStatsDTO | null;
|
||||
finishDistribution: DriverProfileFinishDistributionDTO | null;
|
||||
teamMemberships: DriverProfileTeamMembershipDTO[];
|
||||
socialSummary: DriverProfileSocialSummaryDTO;
|
||||
extendedProfile: DriverProfileExtendedProfileDTO | null;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface TeamListItemDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
memberCount: number;
|
||||
leagues: string[];
|
||||
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
}
|
||||
|
||||
export interface GetAllTeamsOutputDTO {
|
||||
teams: TeamListItemDTO[];
|
||||
totalCount: number;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface TeamDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
leagues: string[];
|
||||
createdAt?: string;
|
||||
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
}
|
||||
|
||||
export interface MembershipDTO {
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface GetDriverTeamOutputDTO {
|
||||
team: TeamDTO;
|
||||
membership: MembershipDTO;
|
||||
isOwner: boolean;
|
||||
canManage: boolean;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ export interface GetMediaOutputDTO {
|
||||
url: string;
|
||||
type: string;
|
||||
category?: string;
|
||||
/** Format: date-time */
|
||||
uploadedAt: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export interface GetSponsorOutputDTO {
|
||||
sponsor: {
|
||||
id: string;
|
||||
name: string;
|
||||
logoUrl?: string;
|
||||
websiteUrl?: string;
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface TeamDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
leagues: string[];
|
||||
createdAt?: string;
|
||||
specialization?: 'endurance' | 'sprint' | 'mixed';
|
||||
region?: string;
|
||||
languages?: string[];
|
||||
}
|
||||
|
||||
export interface MembershipDTO {
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
export interface GetTeamDetailsOutputDTO {
|
||||
team: TeamDTO;
|
||||
membership: MembershipDTO | null;
|
||||
canManage: boolean;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface TeamJoinRequestDTO {
|
||||
requestId: string;
|
||||
driverId: string;
|
||||
driverName: string;
|
||||
teamId: string;
|
||||
status: 'pending' | 'approved' | 'rejected';
|
||||
requestedAt: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface GetTeamJoinRequestsOutputDTO {
|
||||
requests: TeamJoinRequestDTO[];
|
||||
pendingCount: number;
|
||||
totalCount: number;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface TeamMemberDTO {
|
||||
driverId: string;
|
||||
driverName: string;
|
||||
role: 'owner' | 'manager' | 'member';
|
||||
joinedAt: string;
|
||||
isActive: boolean;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
export interface GetTeamMembersOutputDTO {
|
||||
members: TeamMemberDTO[];
|
||||
totalCount: number;
|
||||
ownerCount: number;
|
||||
managerCount: number;
|
||||
memberCount: number;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export interface InvoiceDTO {
|
||||
invoiceNumber: string;
|
||||
date: string;
|
||||
dueDate: string;
|
||||
amount: number;
|
||||
vatAmount: number;
|
||||
totalAmount: number;
|
||||
amount: string;
|
||||
vatAmount: string;
|
||||
totalAmount: string;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
import { ProtestDTO } from './ProtestDTO';
|
||||
import { RaceDTO } from './RaceDTO';
|
||||
|
||||
export interface DriverSummaryDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LeagueAdminProtestsDTO {
|
||||
protests: ProtestDTO[];
|
||||
racesById: { [raceId: string]: RaceDTO };
|
||||
driversById: { [driverId: string]: DriverSummaryDTO };
|
||||
}
|
||||
@@ -7,10 +7,4 @@
|
||||
export interface LeagueWithCapacityDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
settings: Record<string, unknown>;
|
||||
maxDrivers: number;
|
||||
sessionDuration?: number;
|
||||
visibility?: string;
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface LoginWithIracingCallbackParams {
|
||||
code: string;
|
||||
state: string;
|
||||
returnTo?: string;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface MemberPaymentDto {
|
||||
export interface MemberPaymentDTO {
|
||||
id: string;
|
||||
feeId: string;
|
||||
driverId: string;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface MembershipFeeDto {
|
||||
export interface MembershipFeeDTO {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
|
||||
export interface NotificationSettingsDTO {
|
||||
emailNewSponsorships: boolean;
|
||||
emailWeeklyReport: boolean;
|
||||
emailRaceAlerts: boolean;
|
||||
emailPaymentAlerts: boolean;
|
||||
emailNewOpportunities: boolean;
|
||||
emailContractExpiry: boolean;
|
||||
emailNewSponsorships: string;
|
||||
emailWeeklyReport: string;
|
||||
emailRaceAlerts: string;
|
||||
emailPaymentAlerts: string;
|
||||
emailNewOpportunities: string;
|
||||
emailContractExpiry: string;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
export interface PrivacySettingsDTO {
|
||||
publicProfile: boolean;
|
||||
showStats: boolean;
|
||||
showActiveSponsorships: boolean;
|
||||
allowDirectContact: boolean;
|
||||
publicProfile: string;
|
||||
showStats: string;
|
||||
showActiveSponsorships: string;
|
||||
allowDirectContact: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface PrizeDto {
|
||||
export interface PrizeDTO {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
seasonId: string;
|
||||
|
||||
@@ -8,4 +8,5 @@ export interface RaceDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
date: string;
|
||||
views: string;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
import { RacePenaltyDTO } from './RacePenaltyDTO';
|
||||
|
||||
export interface RacePenaltiesDTO {
|
||||
penalties: RacePenaltyDTO[];
|
||||
driverMap: Record<string, string>;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface RecordEngagementInputDTO {
|
||||
eventType: string;
|
||||
userId?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface RecordPageViewInputDTO {
|
||||
path: string;
|
||||
userId?: string;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface SignupParams {
|
||||
email: string;
|
||||
password: string;
|
||||
displayName: string;
|
||||
iracingCustomerId?: string;
|
||||
primaryDriverId?: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
export interface SponsorDashboardInvestmentDTO {
|
||||
activeSponsorships: number;
|
||||
totalInvestment: number;
|
||||
costPerThousandViews: number;
|
||||
activeSponsorships: string;
|
||||
totalInvestment: string;
|
||||
costPerThousandViews: string;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
*/
|
||||
|
||||
export interface SponsorDashboardMetricsDTO {
|
||||
impressions: number;
|
||||
impressionsChange: number;
|
||||
uniqueViewers: number;
|
||||
viewersChange: number;
|
||||
races: number;
|
||||
drivers: number;
|
||||
exposure: number;
|
||||
exposureChange: number;
|
||||
impressions: string;
|
||||
impressionsChange: string;
|
||||
uniqueViewers: string;
|
||||
viewersChange: string;
|
||||
races: string;
|
||||
drivers: string;
|
||||
exposure: string;
|
||||
exposureChange: string;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
export interface SponsorshipPricingItemDTO {
|
||||
id: string;
|
||||
level: string;
|
||||
price: number;
|
||||
price: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface TransactionDto {
|
||||
export interface TransactionDTO {
|
||||
id: string;
|
||||
walletId: string;
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
*/
|
||||
|
||||
export interface UpdateAvatarOutputDTO {
|
||||
success: boolean;
|
||||
success: string;
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Auto-generated DTO from OpenAPI spec
|
||||
* This file is generated by scripts/generate-api-types.ts
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface UpdateTeamInputDTO {
|
||||
name?: string;
|
||||
tag?: string;
|
||||
description?: string;
|
||||
}
|
||||
@@ -5,5 +5,5 @@
|
||||
*/
|
||||
|
||||
export interface UploadMediaOutputDTO {
|
||||
success: boolean;
|
||||
success: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Do not edit manually - regenerate using: npm run api:sync-types
|
||||
*/
|
||||
|
||||
export interface WalletDto {
|
||||
export interface WalletDTO {
|
||||
id: string;
|
||||
leagueId: string;
|
||||
balance: number;
|
||||
|
||||
Reference in New Issue
Block a user