Skip to content

Integrations Module

External service integrations for multi-platform contest data aggregation.

Overview

The Integrations module is responsible for integrating with external competitive programming platforms and communication services. It serves as the bridge between CodeNotify and external APIs.

Location

Server/src/integrations/
├── integrations.module.ts    # Main module configuration
├── platforms/                # Platform adapters for contest data
│   ├── platforms.module.ts
│   ├── base/
│   │   ├── platform.interface.ts
│   │   └── platform-adapter.abstract.ts
│   ├── codeforces/
│   │   ├── codeforces.adapter.ts
│   │   └── codeforces.module.ts
│   ├── leetcode/
│   │   ├── leetcode.adapter.ts
│   │   └── leetcode.module.ts
│   ├── codechef/
│   │   ├── codechef.adapter.ts
│   │   └── codechef.module.ts
│   └── atcoder/
│       ├── atcoder.adapter.ts
│       └── atcoder.module.ts
└── whatsapp/
    └── whatsapp.service.ts   # WhatsApp integration (stub)

Module Structure

IntegrationsModule

Main module that aggregates all integration services.

typescript
@Module({
  providers: [WhatsappService],
  imports: [PlatformsModule],
  exports: [PlatformsModule],
})
export class IntegrationsModule {}

Key Features:

  • Imports and exports PlatformsModule for contest data
  • Provides WhatsappService for future WhatsApp integration
  • Serves as the central integration hub

Sub-Modules

1. PlatformsModule

Manages all competitive programming platform integrations.

Purpose: Fetch and synchronize contest data from multiple platforms

Platforms Supported:

  • Codeforces - REST API integration
  • LeetCode - GraphQL API integration
  • CodeChef - REST API integration
  • AtCoder - Community API integration

Architecture:

PlatformsModule
├── Platform Registry (Factory Provider)
├── CodeforcesModule → CodeforcesAdapter
├── LeetcodeModule → LeetCodeAdapter
├── CodechefModule → CodeChefAdapter
└── AtcoderModule → AtCoderAdapter

Exports:

  • PLATFORM_ADAPTERS_TOKEN - Array of enabled platform adapters
  • Individual platform modules

Documentation: See Platform Adapters for detailed documentation.

2. WhatsappService

Stub service for future WhatsApp Business API integration.

Location: server/src/integrations/whatsapp/whatsapp.service.ts

Current Implementation:

typescript
@Injectable()
export class WhatsappService {}

Status: 🚧 Stub implementation - Not yet functional

Future Plans:

  • WhatsApp Business API integration
  • Message template management
  • Delivery status tracking
  • Rate limiting and queue management

Platform Adapters

Adapter Pattern

All platform integrations follow the Adapter Pattern with a unified interface.

PlatformAdapter Interface

typescript
interface PlatformAdapter {
  readonly platformName: ContestPlatform;
  fetchContests(): Promise<ContestData[]>;
  fetchUpcomingContests(): Promise<ContestData[]>;
  fetchRunningContests(): Promise<ContestData[]>;
  transformToInternalFormat(data: any): ContestData;
  healthCheck(): Promise<boolean>;
}

BasePlatformAdapter

Abstract base class providing common functionality:

typescript
abstract class BasePlatformAdapter implements PlatformAdapter {
  protected readonly logger: Logger;
  
  constructor(public readonly config: PlatformConfig) {
    this.logger = new Logger(this.constructor.name);
  }
  
  // HTTP request handling with retry logic
  protected async makeRequest<T>(url: string, options?: RequestInit): Promise<T>
  
  // Utility methods
  protected sleep(ms: number): Promise<void>
  protected getErrorMessage(error: unknown): string
  protected unixToDate(timestamp: number): Date
  protected calculateDuration(startTime: Date, endTime: Date): number
  protected filterUpcoming(contests: ContestData[]): ContestData[]
  protected filterRunning(contests: ContestData[]): ContestData[]
  
  // Health check
  async healthCheck(): Promise<boolean>
}

Features:

  • Automatic retry with exponential backoff
  • Timeout handling with AbortController
  • Error logging and tracking
  • Common utility methods
  • Health check implementation

Platform Registry

Dynamic platform adapter registration using factory provider.

typescript
@Module({
  imports: [
    CodeforcesModule,
    LeetcodeModule,
    CodechefModule,
    AtcoderModule
  ],
  providers: [
    {
      provide: PLATFORM_ADAPTERS_TOKEN,
      useFactory: (
        codeforces: CodeforcesAdapter,
        leetcode: LeetCodeAdapter,
        codechef: CodeChefAdapter,
        atcoder: AtCoderAdapter,
      ): PlatformAdapter[] => {
        // Only return enabled adapters
        return [codeforces, leetcode, codechef, atcoder].filter(
          adapter => adapter.config.enabled
        );
      },
      inject: [
        CodeforcesAdapter,
        LeetCodeAdapter,
        CodeChefAdapter,
        AtCoderAdapter,
      ],
    },
  ],
  exports: [PLATFORM_ADAPTERS_TOKEN, /* ... */],
})
export class PlatformsModule {}

Benefits:

  • Dynamic adapter injection
  • Easy to add new platforms
  • Automatic filtering of disabled adapters
  • Type-safe adapter registry

Unified Data Format

All adapters transform platform-specific data to a unified ContestData format:

typescript
interface ContestData {
  platformId: string;           // Platform-specific ID
  name: string;                 // Contest name
  platform: ContestPlatform;    // Platform enum
  phase: ContestPhase;          // Contest phase
  type: ContestType;            // Contest type
  startTime: Date;              // Start time
  endTime: Date;                // End time
  durationMinutes: number;      // Duration in minutes
  description?: string;         // Optional description
  websiteUrl?: string;          // Contest URL
  registrationUrl?: string;     // Registration URL
  preparedBy?: string;          // Contest author
  difficulty?: DifficultyLevel; // Difficulty level
  participantCount?: number;    // Number of participants
  problemCount?: number;        // Number of problems
  country?: string;             // Country (if applicable)
  city?: string;                // City (if applicable)
  platformMetadata?: Record<string, any>; // Platform-specific data
  isActive?: boolean;           // Active status
  lastSyncedAt?: Date;          // Last sync timestamp
}

Platform Implementations

1. Codeforces Adapter

API Type: REST API
Endpoint: https://codeforces.com/api/contest.list
Status: ✅ Enabled
Timeout: 15000ms

Features:

  • Fetches all contests from Codeforces API
  • Supports 5 contest phases (BEFORE, CODING, PENDING_SYSTEM_TEST, SYSTEM_TEST, FINISHED)
  • 3 contest types (CF, IOI, ICPC)
  • Tracks frozen scoreboard status

Documentation: Codeforces Adapter

2. LeetCode Adapter

API Type: GraphQL
Endpoint: https://leetcode.com/graphql
Status: ✅ Enabled
Timeout: 15000ms

Features:

  • GraphQL query for contest data
  • Detects Weekly and Biweekly contests
  • Custom GraphQL request handler
  • Includes contest images and descriptions

Documentation: LeetCode Adapter

3. CodeChef Adapter

API Type: REST API
Endpoint: https://www.codechef.com/api/list/contests/all
Status: ✅ Enabled
Timeout: 15000ms

Features:

  • Fetches present, future, and past contests
  • 4 contest types (STARTERS, LUNCH_TIME, COOK_OFF, LONG)
  • Difficulty mapping based on type
  • Limits past contests to 20 most recent

Documentation: CodeChef Adapter

4. AtCoder Adapter

API Type: REST API (Community)
Endpoint: https://kenkoooo.com/atcoder/resources/contests.json
Status: ✅ Enabled
Timeout: 15000ms

Features:

  • Uses community-maintained AtCoder Problems API
  • 4 contest types (ABC, ARC, AGC, AHC)
  • Filters to last 30 days + future contests
  • Rate change metadata tracking

Documentation: AtCoder Adapter

Integration with ContestsService

The Integrations module is consumed by the ContestsService for contest synchronization.

Dependency Injection

typescript
@Injectable()
export class ContestsService {
  private platformAdapters: Map<ContestPlatform, PlatformAdapter> = new Map();

  constructor(
    @Inject(PLATFORM_ADAPTERS_TOKEN)
    private readonly adapters: PlatformAdapter[],
  ) {
    // Build adapter registry
    this.adapters.forEach(adapter => {
      this.platformAdapters.set(adapter.platformName, adapter);
    });
  }
}

Sync Operations

Sync Single Platform:

typescript
async syncPlatform(platform: ContestPlatform): Promise<PlatformSyncResult> {
  const adapter = this.platformAdapters.get(platform);
  if (!adapter) {
    throw new Error(`Platform ${platform} not found`);
  }
  
  const contests = await adapter.fetchContests();
  // Upsert contests to database
  // Return sync results
}

Sync All Platforms:

typescript
async syncAllPlatforms(): Promise<PlatformSyncResult[]> {
  const results = await Promise.allSettled(
    Array.from(this.platformAdapters.keys()).map(platform =>
      this.syncPlatform(platform)
    )
  );
  return results;
}

Configuration

Platform Configuration

Each adapter is configured via PlatformConfig:

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)
}

Environment Variables

bash
# Codeforces
CODEFORCES_API_URL=https://codeforces.com/api
CODEFORCES_TIMEOUT=15000
CODEFORCES_ENABLED=true

# LeetCode
LEETCODE_API_URL=https://leetcode.com/graphql
LEETCODE_TIMEOUT=15000
LEETCODE_ENABLED=true

# CodeChef
CODECHEF_API_URL=https://www.codechef.com/api/list/contests/all
CODECHEF_TIMEOUT=15000
CODECHEF_ENABLED=true

# AtCoder
ATCODER_API_URL=https://kenkoooo.com/atcoder/resources/contests.json
ATCODER_TIMEOUT=15000
ATCODER_ENABLED=true
ATCODER_DAYS_FILTER=30

# WhatsApp (Future)
WHATSAPP_API_KEY=your-api-key
WHATSAPP_PHONE_NUMBER=+1234567890

Error Handling

Retry Logic

All adapters implement automatic retry with exponential backoff:

typescript
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  try {
    const response = await fetch(url, { signal: controller.signal });
    return await response.json();
  } catch (error) {
    if (attempt === maxAttempts) throw error;
    await this.sleep(retryDelay * attempt); // Exponential backoff
  }
}

Timeout Handling

Requests are aborted if they exceed the configured timeout:

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

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

Error Logging

All errors are logged with context:

typescript
this.logger.error(
  `Failed to fetch contests from ${this.platformName}: ${error.message}`
);

Health Checks

Individual Adapter Health

typescript
async healthCheck(): Promise<boolean> {
  try {
    await this.makeRequest(this.config.apiUrl);
    return true;
  } catch (error) {
    this.logger.error(`Health check failed: ${error.message}`);
    return false;
  }
}

All Platforms Health

bash
# Check health of all platforms
GET /contests/health

Response:
{
  "codeforces": { "status": "healthy", "enabled": true },
  "leetcode": { "status": "healthy", "enabled": true },
  "codechef": { "status": "healthy", "enabled": true },
  "atcoder": { "status": "healthy", "enabled": true }
}

Scheduled Synchronization

Contests are automatically synchronized every 6 hours via the scheduler:

typescript
@Cron('0 */6 * * *') // Every 6 hours
async handleContestSync() {
  this.logger.log('Starting scheduled contest sync');
  
  for (const [platform, adapter] of this.platformAdapters) {
    try {
      await this.syncPlatform(platform);
      this.logger.log(`Successfully synced ${platform}`);
    } catch (error) {
      this.logger.error(`Failed to sync ${platform}: ${error.message}`);
    }
  }
}

Adding a New Platform

Step 1: Create Adapter

typescript
// src/integrations/platforms/newplatform/newplatform.adapter.ts
@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(c => this.transformToInternalFormat(c));
  }
  
  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.name,
      platform: ContestPlatform.NEWPLATFORM,
      phase: this.determinePhase(data),
      type: 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(),
    };
  }
}

Step 2: Create Module

typescript
// src/integrations/platforms/newplatform/newplatform.module.ts
@Module({
  providers: [NewPlatformAdapter],
  exports: [NewPlatformAdapter],
})
export class NewPlatformModule {}

Step 3: Register in PlatformsModule

typescript
// src/integrations/platforms/platforms.module.ts
@Module({
  imports: [
    CodeforcesModule,
    LeetcodeModule,
    CodechefModule,
    AtcoderModule,
    NewPlatformModule, // Add new module
  ],
  providers: [
    {
      provide: PLATFORM_ADAPTERS_TOKEN,
      useFactory: (
        codeforces: CodeforcesAdapter,
        leetcode: LeetCodeAdapter,
        codechef: CodeChefAdapter,
        atcoder: AtCoderAdapter,
        newplatform: NewPlatformAdapter, // Add new adapter
      ): PlatformAdapter[] => {
        return [
          codeforces,
          leetcode,
          codechef,
          atcoder,
          newplatform, // Add to array
        ].filter(adapter => adapter.config.enabled);
      },
      inject: [
        CodeforcesAdapter,
        LeetCodeAdapter,
        CodeChefAdapter,
        AtCoderAdapter,
        NewPlatformAdapter, // Add to inject array
      ],
    },
  ],
  exports: [
    PLATFORM_ADAPTERS_TOKEN,
    CodeforcesModule,
    LeetcodeModule,
    CodechefModule,
    AtcoderModule,
    NewPlatformModule, // Export new module
  ],
})
export class PlatformsModule {}

Step 4: Update Contest Schema

Add new platform to enum:

typescript
export enum ContestPlatform {
  CODEFORCES = 'codeforces',
  LEETCODE = 'leetcode',
  CODECHEF = 'codechef',
  ATCODER = 'atcoder',
  NEWPLATFORM = 'newplatform', // Add new platform
}

Step 5: Test

bash
# Manual sync
POST /contests/sync/newplatform

# Get contests
GET /contests/platform/newplatform

# Health check
GET /contests/health

Best Practices

✅ Do

  1. Extend BasePlatformAdapter - Use provided utilities
  2. Implement all interface methods - Required for adapter pattern
  3. Handle errors gracefully - Log and retry appropriately
  4. Transform to unified format - Use ContestData interface
  5. Add health checks - Monitor adapter status
  6. Configure timeouts - Prevent hanging requests
  7. Test thoroughly - Unit and integration tests
  8. Document API specifics - Rate limits, authentication, etc.

❌ Don't

  1. Don't bypass retry logic - Use makeRequest()
  2. Don't ignore timeouts - Respect configured limits
  3. Don't hardcode values - Use configuration
  4. Don't skip error logging - Track all failures
  5. Don't make direct fetch calls - Use BasePlatformAdapter methods
  6. Don't ignore health checks - Implement properly
  7. Don't forget to filter - Only return enabled adapters

Testing

Unit Tests

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

  beforeEach(() => {
    adapter = new CodeforcesAdapter(configService);
  });

  it('should fetch contests', async () => {
    const contests = await adapter.fetchContests();
    expect(contests).toBeDefined();
    expect(Array.isArray(contests)).toBe(true);
  });

  it('should transform contest correctly', () => {
    const cfContest = { /* ... */ };
    const result = adapter.transformToInternalFormat(cfContest);
    expect(result.platform).toBe(ContestPlatform.CODEFORCES);
  });
});

Integration Tests

typescript
describe('PlatformsModule (e2e)', () => {
  it('should provide all enabled adapters', () => {
    const adapters = app.get(PLATFORM_ADAPTERS_TOKEN);
    expect(adapters.length).toBeGreaterThan(0);
    adapters.forEach(adapter => {
      expect(adapter.config.enabled).toBe(true);
    });
  });
});

Monitoring

Key Metrics

  • Sync Success Rate: Percentage of successful syncs
  • API Response Time: Average response time per platform
  • Error Rate: Failed requests per platform
  • Contest Count: Number of contests per platform
  • Health Status: Current health of each adapter

Logging

typescript
// Successful sync
this.logger.log(`Successfully fetched 150 contests from Codeforces`);

// Failed sync
this.logger.error(`Failed to fetch contests from LeetCode: Timeout`);

// Health check
this.logger.warn(`Health check failed for CodeChef`);

Troubleshooting

Platform API Down

Symptoms: All requests to a platform fail

Solutions:

  1. Check platform API status page
  2. Verify network connectivity
  3. Check API endpoint URL
  4. Review error logs for patterns
  5. Implement circuit breaker pattern

Timeout Issues

Symptoms: Requests consistently timeout

Solutions:

  1. Increase timeout in configuration
  2. Check network latency
  3. Verify API is responsive
  4. Implement request caching
  5. Use pagination if available

Rate Limiting

Symptoms: 429 Too Many Requests errors

Solutions:

  1. Reduce sync frequency
  2. Implement request throttling
  3. Use exponential backoff
  4. Cache responses
  5. Contact platform for rate limit increase

Future Enhancements

Planned Features

  • [ ] WhatsApp Integration - Full WhatsApp Business API integration
  • [ ] Telegram Integration - Telegram bot for notifications
  • [ ] Discord Integration - Discord webhook support
  • [ ] Slack Integration - Slack workspace integration
  • [ ] Webhook Support - Custom webhook notifications
  • [ ] GraphQL Federation - Unified GraphQL API
  • [ ] Real-time Updates - WebSocket for live contest updates
  • [ ] Caching Layer - Redis cache for API responses
  • [ ] Circuit Breaker - Prevent cascading failures
  • [ ] API Versioning - Support multiple API versions

Scalability Improvements

  1. Distributed Sync - Parallel sync across multiple workers
  2. Event-Driven Architecture - Use message queues for sync jobs
  3. API Gateway - Centralized API management
  4. Service Mesh - Advanced traffic management
  5. Auto-scaling - Dynamic resource allocation

Built with ❤️ for competitive programmers worldwide.