Skip to content

Platform Integrations ​

Multi-platform contest aggregation using the Adapter pattern.

Supported Platforms ​

PlatformStatusAPI TypeSync Interval
Codeforces✅ ActiveRESTEvery 6 hours
LeetCode✅ ActiveGraphQLEvery 6 hours
CodeChef✅ ActiveRESTEvery 6 hours
AtCoder✅ ActiveRESTEvery 6 hours

Adapter Pattern ​

Interface ​

typescript
interface PlatformAdapter {
  platformName: string;
  enabled: boolean;
  fetchContests(): Promise<ContestData[]>;
  fetchUpcomingContests(): Promise<ContestData[]>;
  fetchRunningContests(): Promise<ContestData[]>;
  healthCheck(): Promise<boolean>;
}

Base Implementation ​

typescript
abstract class BasePlatformAdapter implements PlatformAdapter {
  abstract platformName: string;
  abstract enabled: boolean;
  
  protected async makeRequest(url: string): Promise<any> {
    // Retry logic, timeout, error handling
  }
  
  protected abstract transformToInternalFormat(data: any): ContestData[];
}

Platform Details ​

1. Codeforces ​

API: https://codeforces.com/api/contest.list

Features:

  • Div1, Div2, Div3 contests
  • Educational rounds
  • Global rounds
  • ICPC-style contests

Contest Types:

  • CF - Codeforces Round
  • IOI - IOI-style
  • ICPC - ICPC-style

Phases:

  • BEFORE - Not started
  • CODING - In progress
  • PENDING_SYSTEM_TEST - Testing
  • SYSTEM_TEST - System testing
  • FINISHED - Completed

Metadata:

typescript
{
  frozen: boolean,
  relativeTimeSeconds: number
}

2. LeetCode ​

API: https://leetcode.com/graphql

Features:

  • Weekly contests (Sunday 2:30 AM UTC)
  • Biweekly contests (Saturday 2:30 AM UTC)
  • Virtual contests

Contest Types:

  • WEEKLY - Weekly Contest
  • BIWEEKLY - Biweekly Contest

Phases:

  • UPCOMING - Not started
  • RUNNING - In progress
  • ENDED - Completed

Metadata:

typescript
{
  titleSlug: string,
  isVirtual: boolean,
  cardImg: string,
  originStartTime: number
}

GraphQL Query:

graphql
query {
  allContests {
    title
    titleSlug
    startTime
    duration
    originStartTime
    isVirtual
    cardImg
    description
  }
}

3. CodeChef ​

API: https://www.codechef.com/api/list/contests/all

Features:

  • Long Challenge (10 days)
  • Cook-Off (2.5 hours)
  • Lunchtime (3 hours)
  • Starters (2 hours)

Contest Types:

  • LONG - Long Challenge
  • COOK_OFF - Cook-Off
  • LUNCH_TIME - Lunchtime
  • STARTERS - Starters

Phases:

  • NOT_STARTED - Not started
  • STARTED - In progress
  • COMPLETED - Finished

Difficulty Mapping:

  • STARTERS → BEGINNER
  • LUNCH_TIME → MEDIUM
  • COOK_OFF → MEDIUM
  • LONG → HARD

4. AtCoder ​

API: https://kenkoooo.com/atcoder/resources/contests.json

Features:

  • ABC (Beginner Contest)
  • ARC (Regular Contest)
  • AGC (Grand Contest)
  • AHC (Heuristic Contest)

Contest Types:

  • ABC - Beginner Contest
  • ARC - Regular Contest
  • AGC - Grand Contest
  • AHC - Heuristic Contest

Difficulty Mapping:

  • ABC → BEGINNER
  • ARC → MEDIUM
  • AGC → EXPERT
  • AHC → HARD

Metadata:

typescript
{
  rate_change: string
}

Data Transformation ​

Unified Format ​

typescript
interface ContestData {
  platformId: string;
  name: string;
  platform: 'codeforces' | 'leetcode' | 'codechef' | 'atcoder';
  phase: string;
  type: string;
  startTime: Date;
  endTime: Date;
  durationMinutes: number;
  description?: string;
  websiteUrl?: string;
  registrationUrl?: string;
  difficulty?: 'BEGINNER' | 'EASY' | 'MEDIUM' | 'HARD' | 'EXPERT';
  participantCount?: number;
  problemCount?: number;
  platformMetadata: object;
}

Example Transformation ​

typescript
// Codeforces API response
{
  id: 1234,
  name: "Codeforces Round #900 (Div. 2)",
  type: "CF",
  phase: "BEFORE",
  startTimeSeconds: 1707998400,
  durationSeconds: 7200
}

// Transformed to internal format
{
  platformId: "1234",
  name: "Codeforces Round #900 (Div. 2)",
  platform: "codeforces",
  phase: "BEFORE",
  type: "CF",
  startTime: new Date(1707998400 * 1000),
  endTime: new Date((1707998400 + 7200) * 1000),
  durationMinutes: 120,
  websiteUrl: "https://codeforces.com/contest/1234",
  difficulty: "MEDIUM",
  platformMetadata: { frozen: false }
}

Synchronization ​

Automatic Sync ​

Schedule: Every 6 hours (configurable)

typescript
@Cron(CronExpression.EVERY_6_HOURS)
async syncAllPlatforms() {
  const adapters = this.platformAdapters.values();
  
  for (const adapter of adapters) {
    if (!adapter.enabled) continue;
    
    try {
      const contests = await adapter.fetchContests();
      await this.upsertContests(contests);
    } catch (error) {
      this.logger.error(`Sync failed for ${adapter.platformName}`);
    }
  }
}

Manual Sync ​

Admin Only:

bash
# Sync specific platform
curl -X POST http://localhost:3000/contests/sync/codeforces \
  -H "Authorization: Bearer <admin_token>"

# Sync all platforms
curl -X POST http://localhost:3000/contests/sync/all \
  -H "Authorization: Bearer <admin_token>"

Upsert Logic ​

typescript
async upsertContests(contests: ContestData[]) {
  for (const contest of contests) {
    await Contest.findOneAndUpdate(
      { platformId: contest.platformId, platform: contest.platform },
      { $set: contest },
      { upsert: true, new: true }
    );
  }
}

Error Handling ​

Retry Logic ​

typescript
async makeRequest(url: string, retries = 3): Promise<any> {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await axios.get(url, { timeout: 15000 });
      return response.data;
    } catch (error) {
      if (i === retries - 1) throw error;
      await this.sleep(1000 * (i + 1)); // Exponential backoff
    }
  }
}

Health Checks ​

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

Adding New Platforms ​

1. Create Adapter ​

typescript
@Injectable()
export class NewPlatformAdapter extends BasePlatformAdapter {
  platformName = 'newplatform';
  enabled = true;
  
  async fetchContests(): Promise<ContestData[]> {
    const data = await this.makeRequest('https://api.newplatform.com/contests');
    return this.transformToInternalFormat(data);
  }
  
  protected transformToInternalFormat(data: any): ContestData[] {
    return data.contests.map(contest => ({
      platformId: contest.id,
      name: contest.title,
      platform: 'newplatform',
      // ... map other fields
    }));
  }
}

2. Register in Module ​

typescript
@Module({
  providers: [
    {
      provide: PLATFORM_ADAPTERS,
      useFactory: () => [
        new CodeforcesAdapter(),
        new LeetCodeAdapter(),
        new CodeChefAdapter(),
        new AtCoderAdapter(),
        new NewPlatformAdapter(), // Add here
      ],
    },
  ],
})
export class PlatformsModule {}

3. Update Schema ​

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

Configuration ​

Enable/Disable Platforms ​

bash
CODEFORCES_ENABLED=true
LEETCODE_ENABLED=true
CODECHEF_ENABLED=true
ATCODER_ENABLED=true

Sync Configuration ​

bash
# Sync interval (cron expression)
CONTEST_SYNC_INTERVAL=0 */6 * * *

# Request timeout (milliseconds)
PLATFORM_TIMEOUT=15000

# Retry attempts
PLATFORM_RETRY_ATTEMPTS=3

Monitoring ​

Sync Logs ​

[ContestScheduler] Starting scheduled contest sync
[ContestsService] Syncing codeforces: 5 new, 12 updated, 0 failed
[ContestsService] Syncing leetcode: 2 new, 8 updated, 0 failed
[ContestsService] Syncing codechef: 3 new, 5 updated, 0 failed
[ContestsService] Syncing atcoder: 4 new, 6 updated, 0 failed
[ContestScheduler] Sync completed: 14 new, 31 updated, 0 failed

Health Status ​

bash
# Check platform health
curl http://localhost:3000/contests/platform/codeforces

Best Practices ​

✅ Do ​

  1. Implement retry logic for API failures
  2. Use timeouts to prevent hanging
  3. Log all operations for debugging
  4. Handle rate limits gracefully
  5. Validate data before storing
  6. Use unique constraints (platformId + platform)

❌ Don't ​

  1. Don't sync too frequently (respect rate limits)
  2. Don't ignore errors silently
  3. Don't block on failed platforms
  4. Don't store invalid data
  5. Don't hardcode API URLs

Built with ❤️ for competitive programmers worldwide.