Skip to content

Base Platform Adapter ​

Abstract base class providing common functionality for all platform adapters.

Overview ​

BasePlatformAdapter implements the PlatformAdapter interface and provides shared utilities for HTTP requests, error handling, retry logic, and data transformation.

Location ​

Server/src/integrations/platforms/base/platform-adapter.abstract.ts

Class Definition ​

typescript
export abstract class BasePlatformAdapter implements PlatformAdapter {
  protected readonly logger: Logger;

  constructor(public readonly config: PlatformConfig) {
    this.logger = new Logger(this.constructor.name);
  }

  abstract readonly platformName: ContestPlatform;
  abstract fetchContests(): Promise<ContestData[]>;
  abstract fetchUpcomingContests(): Promise<ContestData[]>;
  abstract fetchRunningContests(): Promise<ContestData[]>;
  abstract transformToInternalFormat(data: any): ContestData;
}

Configuration ​

PlatformConfig Interface ​

typescript
interface PlatformConfig {
  enabled: boolean;           // Enable/disable adapter
  apiUrl: string;            // Base API URL
  timeout: number;           // Request timeout (ms)
  retryAttempts?: number;    // Max retry attempts (default: 3)
  retryDelay?: number;       // Retry delay (ms, default: 1000)
}

Core Methods ​

makeRequest<T>() ​

HTTP request with automatic retry logic and timeout handling.

typescript
protected async makeRequest<T>(
  url: string,
  options?: RequestInit
): Promise<T>

Features:

  • Automatic retry with exponential backoff
  • Configurable timeout using AbortController
  • Custom User-Agent header
  • Error logging for each attempt
  • Throws error after max attempts

Example:

typescript
const data = await this.makeRequest<ApiResponse>(
  'https://api.platform.com/contests'
);

healthCheck() ​

Verify platform API connectivity.

typescript
async healthCheck(): Promise<boolean>

Returns: true if API is reachable, false otherwise

Example:

typescript
const isHealthy = await adapter.healthCheck();
if (!isHealthy) {
  logger.warn('Platform API is down');
}

Utility Methods ​

sleep() ​

Async sleep utility for retry delays.

typescript
protected sleep(ms: number): Promise<void>

getErrorMessage() ​

Extract error message from unknown error types.

typescript
protected getErrorMessage(error: unknown): string

Handles:

  • Error instances
  • Plain objects
  • Primitive values

unixToDate() ​

Convert Unix timestamp to Date object.

typescript
protected unixToDate(timestamp: number): Date

Example:

typescript
const date = this.unixToDate(1707998400); // 2024-02-15T12:00:00Z

calculateDuration() ​

Calculate duration in minutes between two dates.

typescript
protected calculateDuration(startTime: Date, endTime: Date): number

Returns: Duration in minutes (rounded)

filterUpcoming() ​

Filter contests that haven't started yet.

typescript
protected filterUpcoming(contests: ContestData[]): ContestData[]

Returns: Contests where startTime > now

filterRunning() ​

Filter currently running contests.

typescript
protected filterRunning(contests: ContestData[]): ContestData[]

Returns: Contests where startTime <= now && endTime >= now

Error Handling ​

Retry Logic ​

typescript
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  try {
    // Make request
    return response;
  } catch (error) {
    if (attempt === maxAttempts) throw error;
    await this.sleep(retryDelay * attempt); // Exponential backoff
  }
}

Timeout Handling ​

typescript
const controller = new AbortController();
const timeoutId = setTimeout(
  () => controller.abort(),
  this.config.timeout
);

const response = await fetch(url, {
  signal: controller.signal
});

clearTimeout(timeoutId);

Implementation Example ​

typescript
@Injectable()
export class NewPlatformAdapter extends BasePlatformAdapter {
  readonly platformName = ContestPlatform.NEWPLATFORM;

  constructor() {
    const config: PlatformConfig = {
      enabled: true,
      apiUrl: 'https://api.newplatform.com',
      timeout: 15000,
      retryAttempts: 3,
      retryDelay: 1000,
    };
    super(config);
  }

  async fetchContests(): Promise<ContestData[]> {
    const response = await this.makeRequest<ApiResponse>(
      `${this.config.apiUrl}/contests`
    );
    
    return response.contests.map(contest =>
      this.transformToInternalFormat(contest)
    );
  }

  async fetchUpcomingContests(): Promise<ContestData[]> {
    const contests = await this.fetchContests();
    return this.filterUpcoming(contests);
  }

  async fetchRunningContests(): Promise<ContestData[]> {
    const contests = await this.fetchContests();
    return this.filterRunning(contests);
  }

  transformToInternalFormat(data: any): ContestData {
    return {
      platformId: data.id,
      name: data.title,
      platform: ContestPlatform.NEWPLATFORM,
      phase: this.determinePhase(data.startTime, data.endTime),
      type: this.mapType(data.type),
      startTime: new Date(data.startTime),
      endTime: new Date(data.endTime),
      durationMinutes: this.calculateDuration(
        new Date(data.startTime),
        new Date(data.endTime)
      ),
      isActive: true,
      lastSyncedAt: new Date(),
    };
  }
}

Testing ​

typescript
describe('BasePlatformAdapter', () => {
  let adapter: TestAdapter;

  beforeEach(() => {
    adapter = new TestAdapter({
      enabled: true,
      apiUrl: 'https://test.com',
      timeout: 5000,
      retryAttempts: 2,
    });
  });

  it('should retry on failure', async () => {
    // Mock failed requests
    jest.spyOn(global, 'fetch')
      .mockRejectedValueOnce(new Error('Network error'))
      .mockResolvedValueOnce({ ok: true, json: async () => ({}) });

    await expect(adapter.makeRequest('https://test.com')).resolves.toBeDefined();
  });

  it('should filter upcoming contests', () => {
    const contests = [
      { startTime: new Date('2025-01-01'), endTime: new Date('2025-01-02') },
      { startTime: new Date('2020-01-01'), endTime: new Date('2020-01-02') },
    ];

    const upcoming = adapter.filterUpcoming(contests);
    expect(upcoming).toHaveLength(1);
  });
});

Best Practices ​

✅ Do ​

  1. Always call super(config) in constructor
  2. Use provided utility methods for common operations
  3. Log all operations for debugging
  4. Handle errors gracefully with try-catch
  5. Validate API responses before transformation
  6. Use exponential backoff for retries

❌ Don't ​

  1. Don't bypass retry logic - use makeRequest()
  2. Don't ignore timeouts - respect config.timeout
  3. Don't swallow errors - let them propagate
  4. Don't make direct fetch calls - use makeRequest()
  5. Don't hardcode values - use config

Built with ❤️ for competitive programmers worldwide.