Skip to content

Conversation

@KATO-Hiro
Copy link
Collaborator

@KATO-Hiro KATO-Hiro commented Nov 21, 2024

close #1514

Summary by CodeRabbit

  • New Features

    • Added a new contest type, AOJ_JAG, to enhance contest categorization.
    • Expanded API functionality to support fetching contests and tasks for both PCK and JAG types.
    • Introduced new types and enums to facilitate contest classification and naming.
    • Enhanced localization capabilities with new contest label translations.
  • Tests

    • Introduced new test cases for AOJ_JAG contests across various functions to ensure accurate classification and URL generation.
    • Enhanced test coverage for contest classification and priority determination logic.
  • Documentation

    • Updated documentation to include information about the new JAG contest type in the AIZU ONLINE JUDGE section.

@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2024

Walkthrough

The changes in this pull request introduce a new contest type, AOJ_JAG, into the Entity-Relationship Diagram (ERD) and the corresponding database schema. This addition is reflected across multiple files, including the Prisma schema, utility functions, and test cases, enhancing the functionality related to contest handling. The modifications include updates to methods for fetching contests and tasks, as well as the introduction of new types and enums to support the new contest type. The overall structure and existing functionalities remain intact, with no significant alterations to existing entities or relationships.

Changes

File Change Summary
prisma/ERD.md Added new entry AOJ_JAG to ContestType entity.
prisma/migrations/20241120113542_add_aoj_jag_to_contest_type/migration.sql Added enum value 'AOJ_JAG' to enum type "ContestType".
prisma/schema.prisma Added enum value AOJ_JAG to ContestType enum.
src/lib/clients/aizu_online_judge.ts Added enums ChallengeContestType and JagRound; updated fetchChallengeContests and fetchChallengeTasks methods to handle JAG contests.
src/lib/clients/index.ts Added documentation for "JAG (ACM-ICPC Japan Alumni Group Contest)" in the AIZU ONLINE JUDGE section.
src/lib/types/contest.ts Added new type ContestLabelTranslations and updated ContestType constant to include AOJ_JAG.
src/lib/utils/contest.ts Added AOJ_JAG to classification logic; updated related functions to handle new contest type.
src/lib/utils/task.ts Updated AojGenerator to handle contest IDs starting with "JAG".
src/test/lib/utils/contest.test.ts Added test cases for classifyContest, getContestPriority, and getContestNameLabel for AOJ JAG.
src/test/lib/utils/task.test.ts Added new test suite for getTaskUrl related to AOJ JAG contests.
src/test/lib/utils/test_cases/contest_name_and_task_index.ts Added new test cases and types specific to AOJ JAG contests.
src/test/lib/utils/test_cases/contest_name_labels.ts Added new export aojJag with test cases for AOJ JAG contests.
src/test/lib/utils/test_cases/contest_type.ts Added new constant aojJagContestData and export aojJag for AOJ JAG contests.
src/test/lib/utils/test_cases/task_url.ts Updated aojPck export, added aojJag export, and modified test case structure for JAG contests.

Poem

In the meadow where contests bloom,
A new type hops, dispelling gloom.
AOJ JAG joins the fun, oh what a sight,
With tasks and challenges, all day and night!
So gather 'round, let the coding commence,
For every new entry, we leap with suspense! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 2e0f95c and 7cd1e41.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (7)
src/lib/clients/aizu_online_judge.ts (7)

Line range hint 33-100: Well-structured type definitions with proper type safety!

The new type system effectively maps contest types to their corresponding rounds, preventing invalid combinations at compile time.


108-286: Robust cache implementation with proper resource management!

The cache implementation includes all essential features:

  • Time-based expiration
  • Size-based eviction
  • Health monitoring
  • Proper cleanup mechanism
  • Input validation

Line range hint 288-346: Well-designed configuration system with proper initialization!

The configuration system allows flexible customization of cache parameters while maintaining type safety.


348-355: Correct implementation of resource cleanup!

The dispose method properly releases resources by disposing of both caches, preventing memory leaks.


514-544: Secure implementation of endpoint building with proper validation!

The implementation includes:

  • Length validation
  • Character set restrictions
  • Path traversal prevention
  • Proper URL encoding

Line range hint 571-729: Well-implemented task fetching with proper caching and error handling!

The implementation mirrors the robust approach used in contest fetching, ensuring consistency across the codebase.


Line range hint 360-494: Robust implementation of contest fetching with proper error handling!

The concurrent fetching implementation with Promise.allSettled ensures partial results are returned even if some requests fail.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (10)
src/lib/types/contest.ts (1)

74-83: Consider enhancing type safety for language codes

While the implementation is functional, consider improving type safety by using literal types for language codes instead of generic string keys.

Here's a suggested improvement:

-export type ContestLabelTranslations = {
-  [key: string]: string;
-};
+type LanguageCode = 'en' | 'ja';  // Add more language codes as needed
+export type ContestLabelTranslations = {
+  [key in LanguageCode]: string;
+};

This change would:

  • Prevent accidental use of invalid language codes
  • Make it clear which languages are supported
  • Provide better IDE autocompletion
src/test/lib/utils/test_cases/task_url.ts (1)

141-152: Consider enhancing the pattern documentation.

While the contest ID format is documented, it would be helpful to also document the task ID patterns and their relationship with contest years, similar to how it's documented for PCK contests.

Add this to the comment block:

 // JAG contests follow these patterns:
 // - Contest ID format: JAG(Prelim|Regional)<YEAR>
+// - Task ID format:
+//   - Recent contests (2021+): 33xx-33xx
+//   - Older contests (2005-2006): 20xx-20xx
prisma/schema.prisma (1)

227-227: Consider database migration strategy.

Since this change adds a new enum value, ensure you have a proper migration strategy in place:

  1. Deploy schema changes before application code
  2. Consider existing records and default values
  3. Update any relevant database seeds or test data
  4. Have a rollback plan ready

Would you like assistance in creating a comprehensive migration plan or reviewing the migration scripts?

src/lib/utils/task.ts (1)

Line range hint 7-15: Well-structured for platform extensibility

The URL generation system's chain of responsibility pattern makes it easy to add new platforms like JAG. This aligns well with the TODO comment about supporting additional platforms (Codeforces, yukicoder, BOJ).

Consider documenting the pattern for adding new platforms in the codebase, as this will help future contributors implement support for the mentioned platforms in the TODO.

src/test/lib/utils/test_cases/contest_name_labels.ts (1)

359-444: Add documentation for year coverage pattern

The test cases cover various years with some gaps (e.g., 2007-2008, 2012-2019 for prelims). Consider adding a comment explaining if these gaps represent years when contests weren't held, similar to the documentation pattern used for university contests above (see UTPC/TUPC comment).

 export const aojJag = [
+  // Note: Test cases cover years when JAG contests were actually held
+  // Prelims: 2005-2006, 2009-2011, 2020-2024
+  // Regionals: 2005-2006, 2009-2011, 2016-2017, 2020-2022
src/test/lib/utils/test_cases/contest_name_and_task_index.ts (1)

485-500: Consider refactoring to reduce code duplication.

The generateAojJagTestCases function follows a similar pattern to other test case generators. Consider extracting the common pattern into a reusable utility function to reduce duplication.

Example refactor:

+const generateContestTestCases = <T extends string>(
+  contestIds: T[],
+  taskIndices: string[],
+  formatName: (contestId: T, taskIndex: string) => string,
+  formatExpected: (contestId: T, taskIndex: string) => string,
+) => {
+  return zip(contestIds, taskIndices).map(([contestId, taskIndex]) => {
+    return createTestCaseForContestNameAndTaskIndex(formatName(contestId, taskIndex))({
+      contestId: contestId,
+      taskTableIndex: taskIndex,
+      expected: formatExpected(contestId, taskIndex),
+    });
+  });
+};

-const generateAojJagTestCases = (
-  contestIds: JagContestIds,
-  taskIndices: string[],
-): { name: string; value: TestCaseForContestNameAndTaskIndex }[] => {
-  return zip(contestIds, taskIndices).map(([contestId, taskIndex]) => {
-    const testCase = createTestCaseForContestNameAndTaskIndex(
-      `AOJ, JAG${contestId} - ${taskIndex}`,
-    )({
-      contestId: `JAG${contestId}`,
-      taskTableIndex: taskIndex,
-      expected: `AOJ - JAG${contestId.replace('Prelim', '模擬国内予選').replace('Regional', '模擬アジア地区予選')} - ${taskIndex}`,
-    });
-    return testCase;
-  });
-};
+const generateAojJagTestCases = (contestIds: JagContestIds, taskIndices: string[]) =>
+  generateContestTestCases(
+    contestIds,
+    taskIndices,
+    (contestId, taskIndex) => `AOJ, JAG${contestId} - ${taskIndex}`,
+    (contestId, taskIndex) =>
+      `AOJ - JAG${contestId.replace('Prelim', '模擬国内予選').replace('Regional', '模擬アジア地区予選')} - ${taskIndex}`,
+  );
src/test/lib/utils/contest.test.ts (1)

491-497: Consider adding test cases for edge cases in contest name labels.

While the basic test structure is good, consider adding edge cases such as:

  • Invalid contest IDs
  • Mixed case variations
  • Special characters in contest names
src/lib/utils/contest.ts (1)

Line range hint 300-307: Avoid reassigning function parameters in getAojChallengeLabel.

Assigning new values to function parameters can lead to confusion and potential bugs. Consider using a new variable to hold the modified contest ID for better clarity and maintainability.

Apply this diff to improve code clarity:

 function getAojChallengeLabel(translations: ContestLabelTranslations, contestId: string): string {
   const baseLabel = 'AOJ - ';
+  let label = contestId;
   Object.entries(translations).forEach(([abbrEnglish, japanese]) => {
-    contestId = contestId.replace(abbrEnglish, japanese);
+    label = label.replace(abbrEnglish, japanese);
   });
-  return baseLabel + contestId;
+  return baseLabel + label;
 }
src/lib/clients/aizu_online_judge.ts (2)

197-197: Use toUpperCase() instead of toLocaleUpperCase() for consistent casing

When converting contestType to uppercase for logging, using toUpperCase() ensures consistent behavior across all locales. The toLocaleUpperCase() method's output can vary depending on the user's locale, which might lead to inconsistent logging.

Apply this change:

- const contestTypeLabel = contestType.toLocaleUpperCase();
+ const contestTypeLabel = contestType.toUpperCase();

358-358: Use toUpperCase() instead of toLocaleUpperCase() for consistent casing

As with the previous instance, replace toLocaleUpperCase() with toUpperCase() to maintain consistency in string casing regardless of locale settings.

Apply this change:

- const contestTypeLabel = contestType.toLocaleUpperCase();
+ const contestTypeLabel = contestType.toUpperCase();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0c339b8 and 862d9ba.

📒 Files selected for processing (14)
  • prisma/ERD.md (1 hunks)
  • prisma/migrations/20241120113542_add_aoj_jag_to_contest_type/migration.sql (1 hunks)
  • prisma/schema.prisma (1 hunks)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
  • src/lib/clients/index.ts (1 hunks)
  • src/lib/types/contest.ts (3 hunks)
  • src/lib/utils/contest.ts (4 hunks)
  • src/lib/utils/task.ts (1 hunks)
  • src/test/lib/utils/contest.test.ts (4 hunks)
  • src/test/lib/utils/task.test.ts (1 hunks)
  • src/test/lib/utils/test_cases/contest_name_and_task_index.ts (1 hunks)
  • src/test/lib/utils/test_cases/contest_name_labels.ts (1 hunks)
  • src/test/lib/utils/test_cases/contest_type.ts (1 hunks)
  • src/test/lib/utils/test_cases/task_url.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • prisma/migrations/20241120113542_add_aoj_jag_to_contest_type/migration.sql
  • src/lib/clients/index.ts
🔇 Additional comments (21)
src/lib/types/contest.ts (1)

26-29: LGTM! Well-documented addition of AOJ_JAG contest type

The new contest type and its documentation are well-implemented, maintaining consistency with the existing pattern and clearly describing its purpose.

Also applies to: 51-51

prisma/ERD.md (1)

32-32: LGTM! Verify schema consistency.

The addition of AOJ_JAG to the ContestType entity is well-integrated and aligns with the PR objectives to support JAG preliminary and regional tasks.

Let's verify the consistency of this change with related schema files:

✅ Verification successful

Schema changes are consistent across ERD, schema, and migrations

The addition of AOJ_JAG is properly reflected in:

  • ERD diagram (prisma/ERD.md)
  • Prisma schema with appropriate comment (prisma/schema.prisma)
  • Migration file with correct ALTER TYPE statement (prisma/migrations/20241120113542_add_aoj_jag_to_contest_type/migration.sql)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that AOJ_JAG is consistently defined across schema files

# Check if AOJ_JAG is present in the Prisma schema
echo "Checking Prisma schema..."
rg "AOJ_JAG" prisma/schema.prisma

# Check if AOJ_JAG is included in migration files
echo "Checking migration files..."
rg "AOJ_JAG" prisma/migrations/

Length of output: 367

src/test/lib/utils/test_cases/task_url.ts (3)

131-139: LGTM! Improved naming and URL structure.

The changes improve naming consistency and correctly reflect the AOJ URL structure for PCK contests.


Line range hint 154-161: LGTM! Consistent implementation.

The JAG contest export follows the established patterns and correctly implements the URL generation for JAG tasks.


Line range hint 141-161: Verify JAG contest coverage and URL format.

Let's verify that:

  1. All JAG contest types are covered
  2. The URL format matches AOJ's API expectations
✅ Verification successful

Let me gather more information about the JAG contest implementation to ensure comprehensive verification.


JAG contest implementation is complete and correctly structured

The verification confirms that:

  1. JAG contest types (Prelim and Regional) are properly covered in the implementation:
    • Contest pattern is correctly defined in regex: ^JAG(Prelim|Regional|Summer|Winter|Spring)\d*$
    • Test cases cover both Prelim and Regional contests from 2005 to 2024
  2. URL format is consistent with AOJ's API:
    • Tasks URL is correctly defined as ${AOJ_BASE_URL}/problems
    • Task URLs are consistently formatted as ${AOJ_TASKS_URL}/${taskId}
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify JAG contest coverage and URL format

# Check if we have test cases for all JAG contest types
echo "Checking JAG contest types in the codebase..."
rg -g '!test_cases' "JAG(Prelim|Regional)" 

# Verify URL format consistency with AOJ API
echo "Verifying URL format in AOJ API related files..."
rg -g '*.ts' "AOJ_TASKS_URL.*task" 

Length of output: 630


Script:

#!/bin/bash
# Check for JAG contest-related implementations
echo "Checking JAG contest implementations..."
rg "JAG.*contest" -A 5

# Look for AOJ URL constants and their usage
echo "Checking AOJ URL constants..."
rg "AOJ_.*URL.*=.*" -A 2

# Check for any other JAG-related patterns
echo "Looking for additional JAG patterns..."
rg "JAG.*\.(Prelim|Regional)" -A 3

Length of output: 7886

prisma/schema.prisma (1)

227-227: LGTM! Verify enum value handling in the application.

The addition of AOJ_JAG to ContestType is well-placed and properly documented. The enum value follows the established naming convention and is logically grouped with other AOJ-related types.

Let's verify the handling of this new enum value across the codebase:

✅ Verification successful

LGTM! The new AOJ_JAG enum value is properly integrated.

The verification shows that the new AOJ_JAG contest type is well-integrated across the codebase:

  • Properly added to the ContestType enum in src/lib/types/contest.ts
  • Correctly handled in contest classification logic in src/lib/utils/contest.ts
  • Test cases are implemented in src/test/lib/utils/test_cases/contest_type.ts
  • Contest priority is properly set in the priority map (priority 20)
  • Pattern matching for JAG contests is implemented (/^JAG(Prelim|Regional|Summer|Winter|Spring)\d*$/)
  • Test data structure exists in contest_name_and_task_index.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for proper handling of the new AOJ_JAG contest type
# across the codebase

# Check for enum usage in TypeScript/JavaScript files
echo "Checking for ContestType enum usage:"
rg -t typescript -t javascript "ContestType|AOJ_JAG" -A 3

# Check for any hardcoded contest type lists that might need updating
echo "Checking for potential places that need updates:"
rg -t typescript -t javascript "AOJ_PCK|AOJ_COURSES" -A 3

Length of output: 379


Script:

#!/bin/bash
# Let's try with just the file extension patterns instead of file type flags
echo "Checking for ContestType enum usage:"
rg "ContestType|AOJ_JAG" --glob "*.{ts,js,tsx,jsx}" -A 3

echo -e "\nChecking for potential places that need updates:"
rg "AOJ_PCK|AOJ_COURSES" --glob "*.{ts,js,tsx,jsx}" -A 3

Length of output: 50730

src/lib/utils/task.ts (1)

33-37: LGTM! Verify JAG contest ID patterns

The implementation correctly extends the canHandle method to support JAG contests. The code is clean and follows the existing pattern.

Let's verify the JAG contest ID patterns to ensure comprehensive coverage:

✅ Verification successful

Let me check the contest ID pattern more precisely by looking at the regex pattern used in the codebase.


JAG contest ID pattern is well-defined and verified

The implementation is correct and complete. The codebase shows that:

  1. JAG contest IDs follow a strict pattern: ^JAG(Prelim|Regional|Summer|Winter|Spring)\d*$
  2. Test cases cover both Prelim and Regional contests extensively
  3. The simple startsWith('JAG') check in the canHandle method is sufficient as other validation is handled by the contest type detection logic in contest.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any JAG-related contest IDs in the codebase to verify the pattern
# and ensure we're not missing any edge cases

# Search for JAG-related patterns in test files and source code
rg -i "jag.*contest" --type ts
rg "contestId.*['\"]JAG" --type ts

Length of output: 9655


Script:

#!/bin/bash
# Check for any regex patterns or validations for JAG contest IDs
ast-grep --pattern 'if ($_ /^JAG$$$/.exec($_)) {
  $$$
}'

Length of output: 59


Script:

#!/bin/bash
# Let's try a different pattern to find JAG-related regex validations
rg "JAG.*\{" -A 2 --type ts

Length of output: 9669

src/test/lib/utils/test_cases/contest_type.ts (1)

347-368: LGTM! Well-structured contest data

The aojJagContestData follows consistent naming patterns and includes a comprehensive set of both preliminary and regional contests. The data structure aligns well with other contest type definitions in the file.

src/test/lib/utils/test_cases/contest_name_labels.ts (1)

359-444: Well-structured implementation with consistent naming patterns

The test cases follow consistent naming patterns for both contest IDs and expected labels, maintaining the same structure as other contest types in the file. The implementation effectively supports the PR objective of enabling JAG task imports.

src/test/lib/utils/test_cases/contest_name_and_task_index.ts (3)

420-467: LGTM! Well-structured test data with clear documentation.

The test data follows consistent patterns and provides good coverage for both preliminary and regional rounds across multiple years.


502-504: LGTM! Export implementation is consistent.

The export statement follows the established pattern and correctly flattens the test cases array.


469-484: Verify completeness of JAG contest years.

The JagYear type includes years from 2005 to 2024, but several years are missing (2007-2008, 2012-2016, 2018-2019). Please verify if these gaps are intentional or if we should include test cases for these years.

Run the following script to check for any JAG contests in the missing years:

✅ Verification successful

Add missing JAG contest years to the type definition

Several JAG contest years found in the codebase are not included in the JagYear type:

  • 2012-2015: Found in atcoder_problems/tasks.json with contest IDs like jag2012autumn, jag2013summer-day3, etc.
  • 2016: Found in test cases with JAGRegional2016

Let's verify if these contests follow the same Prelim or Regional pattern before suggesting their addition.


The JagYear type definition is correct as is

The missing years in the JagYear type appear to be intentional. While there are JAG contests from 2012-2016 in the codebase, they follow different naming patterns (like jag2012autumn, jag2013summer-day3) except for JAGRegional2016. The JagYear type is specifically used with JagContestId type that only accepts ${JagRound}${JagYear} format where JagRound is either 'Prelim' or 'Regional'. The search results confirm that contests from missing years don't follow this specific format, except for 2016 which is already included.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for JAG contest problems from missing years
# Expected: No results if the gaps are intentional

missing_years=(2007 2008 2012 2013 2014 2015 2016 2018 2019)

for year in "${missing_years[@]}"; do
  echo "Checking year $year:"
  rg -i "JAG.*${year}" || true
done

Length of output: 2221


Script:

#!/bin/bash
# Check the contest format for these years
rg -i "jag.*201[2-6]" -A 2 src/test/lib/clients/test_data/atcoder_problems/tasks.json
rg -i "JAG(Prelim|Regional)201[2-6]" src/test/lib/utils/test_cases/

Length of output: 1037

src/test/lib/utils/task.test.ts (1)

81-88: LGTM! Test suite follows established patterns.

The new test suite for AOJ JAG contests is well-structured and consistent with existing test patterns.

Let's verify the test cases are properly defined:

✅ Verification successful

Test cases for AOJ JAG are properly defined and well-structured

The verification shows that:

  • Test cases are properly defined in src/test/lib/utils/test_cases/task_url.ts
  • The implementation includes both JAG Preliminary and Regional contests
  • Test data is generated using createTestCaseForTaskUrl helper
  • Test cases follow the expected structure with contestId, taskId, and expected URL
  • The test suite is consistently used across multiple test files for different functionalities (contest type, task URL, contest name)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify test cases for AOJ JAG contests

# Check if test cases file exists and contains AOJ JAG test cases
rg -A 5 "aojJag.*=.*\[" src/test/lib/utils/test_cases/task_url.ts

# Verify test case structure matches the type definition
ast-grep --pattern 'interface TestCaseForTaskUrl {
  $$$
  contestId: string;
  taskId: string;
  expected: string;
  $$$
}'

Length of output: 194


Script:

#!/bin/bash
# Let's try a different approach to locate and verify the test cases

# First, find the test cases file
fd "task_url.ts" --type f

# Then check its content for AOJ JAG related test cases
rg "aojJag" --type ts -C 5

# Also verify the interface definition
rg "interface TestCaseForTaskUrl" --type ts -C 5

Length of output: 8401

src/test/lib/utils/contest.test.ts (3)

625-635: LGTM! Task index tests are well-structured.

The test implementation for adding contest names to task indices follows the established pattern and maintains consistency with other AOJ contest types.


341-347: LGTM! Priority tests maintain consistency.

The priority tests for AOJ JAG contests align well with the existing test patterns and verify the contest priority mapping.

Let's verify the priority mapping is defined:

✅ Verification successful

Priority mapping for AOJ JAG contests is properly defined and consistent

The verification confirms that:

  • AOJ_JAG is defined as a contest type in src/lib/types/contest.ts
  • The priority for AOJ_JAG is set to 20 in the contestTypePriorities Map in src/lib/utils/contest.ts
  • The contest type is correctly identified through regex pattern /^JAG(Prelim|Regional|Summer|Winter|Spring)\d*$/
  • Test cases properly verify the priority mapping using getContestPriority()
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if AOJ_JAG priority is defined in the contest priorities
rg "AOJ_JAG.*contestTypePriorities" ./src/lib/utils/

Length of output: 54


Script:

#!/bin/bash
# Let's first check the contest type definitions
rg "AOJ_JAG" ./src -A 3 -B 3

# Then check contest priorities separately
rg "contestTypePriorities" ./src -A 3 -B 3

Length of output: 16921


175-181: LGTM! Test structure follows established patterns.

The test cases for AOJ JAG contests are well-organized and consistent with the existing test structure.

Let's verify the test cases are comprehensive:

✅ Verification successful

Test cases for AOJ JAG contests are comprehensive and well-structured

The test cases in contest_type.ts and contest_name_labels.ts cover both JAG Preliminary (2005-2024) and Regional (2005-2022) contests, with proper contest IDs and expected labels. The test structure follows the established patterns and maintains consistency with other contest types.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if all AOJ JAG test cases are covered
rg -A 2 "aojJag.*forEach" ./src/test/lib/utils/test_cases/

Length of output: 60


Script:

#!/bin/bash
# Let's first find the test cases file
fd "test_cases" --type f

# Then let's look for AOJ JAG related constants or test cases
rg -l "aojJag" 

Length of output: 308


Script:

#!/bin/bash
# Let's examine the test cases for AOJ JAG contests
cat src/test/lib/utils/test_cases/contest_type.ts
cat src/test/lib/utils/test_cases/contest_name_labels.ts
cat src/test/lib/utils/test_cases/contest_name_and_task_index.ts

Length of output: 38425

src/lib/utils/contest.ts (5)

84-86: The AOJ_JAG contest type is properly classified.

The regular expression correctly matches contest IDs starting with JAG followed by Prelim, Regional, Summer, Winter, or Spring, and any subsequent digits. The classification logic is consistent with existing patterns.


227-227: AOJ_JAG added to contestTypePriorities with appropriate priority.

Assigning a priority of 20 to ContestType.AOJ_JAG aligns with the existing priority structure, placing it after AOJ_PCK and maintaining the intended ordering.


279-283: Correctly handle JAG contests in getContestNameLabel.

The addition of the condition for contest IDs starting with JAG and utilizing getAojChallengeLabel with JAG_TRANSLATIONS ensures that JAG contests are properly labeled.


289-293: PCK_TRANSLATIONS defined correctly for label translations.

The translation mappings for PCK contests are accurate, enabling proper substitution in contest labels.


295-298: JAG_TRANSLATIONS defined correctly for label translations.

The translation mappings for JAG contests are accurately defined, facilitating correct label generation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (6)
src/lib/utils/contest.ts (3)

227-227: Update priority documentation

The priority for AOJ_JAG (20) is higher than AOJ_PCK (19), but this isn't reflected in the documentation comment above. Consider updating the comment to include the rationale for AOJ_JAG's priority.

  * Priority assignment rationale:
  * - Educational contests (0-10): ABS, ABC, APG4B, etc.
  * - Contests for genius (11-15): ARC, AGC, and their variants
  * - Special contests (16-17): UNIVERSITY, OTHERS
- * - External platforms (18-19): AOJ_COURSES, AOJ_PCK
+ * - External platforms (18-20): AOJ_COURSES, AOJ_PCK, AOJ_JAG

295-298: Add JSDoc documentation

Consider adding JSDoc documentation for JAG_TRANSLATIONS similar to other translation constants in the file.

+/**
+ * Maps JAG contest type abbreviations to their Japanese translations.
+ *
+ * @example
+ * {
+ *   Prelim: '模擬国内予選',
+ *   Regional: '模擬アジア地区予選'
+ * }
+ */
 const JAG_TRANSLATIONS = {
   Prelim: '模擬国内予選',
   Regional: '模擬アジア地区予選',
 };

300-308: Enhance type safety

The function implementation is clean and reusable. Consider enhancing type safety by adding a return type annotation and making the translations parameter readonly.

-function getAojChallengeLabel(translations: ContestLabelTranslations, contestId: string): string {
+function getAojChallengeLabel(translations: Readonly<ContestLabelTranslations>, contestId: string): string {
   const baseLabel = 'AOJ - ';
   let label = contestId;
 
   Object.entries(translations).forEach(([abbrEnglish, japanese]) => {
     label = label.replace(abbrEnglish, japanese);
   });
 
   return baseLabel + label;
 }
src/lib/clients/aizu_online_judge.ts (3)

129-144: Consider enhancing the logging message with contest type details.

While the implementation is solid, the final log message could be more informative by breaking down the count by contest type.

-      console.log(`Found AOJ: ${contests.length} contests.`);
+      console.log(
+        `Found AOJ contests - Total: ${contests.length} ` +
+        `(Courses: ${courses.length}, PCK: ${pckPrelims.length + pckFinals.length}, ` +
+        `JAG: ${jagPrelims.length + jagRegionals.length})`
+      );

Line range hint 191-236: Consider caching API responses for frequently accessed contests.

The implementation is robust, but consider adding caching for the API responses to improve performance and reduce API calls. This would be particularly beneficial if the contests data is accessed frequently.

Consider implementing a simple in-memory cache:

private contestCache: Map<string, ContestsForImport> = new Map();

private async fetchChallengeContests<T extends ChallengeContestType>(
  contestType: T,
  round: ChallengeRoundMap[T],
): Promise<ContestsForImport> {
  const cacheKey = `${contestType}_${round}`;
  if (this.contestCache.has(cacheKey)) {
    return this.contestCache.get(cacheKey)!;
  }
  
  // ... existing implementation ...
  
  this.contestCache.set(cacheKey, contests);
  return contests;
}

Line range hint 266-288: Enhance logging message with task type details.

Similar to the contests logging, the tasks logging could be more informative by breaking down the count by task type.

-      console.log(`Found AOJ: ${tasks.length} tasks.`);
+      console.log(
+        `Found AOJ tasks - Total: ${tasks.length} ` +
+        `(Courses: ${courses.length}, PCK: ${pckPrelims.length + pckFinals.length}, ` +
+        `JAG: ${jagPrelims.length + jagRegionals.length})`
+      );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 862d9ba and 47c0238.

📒 Files selected for processing (4)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
  • src/lib/utils/contest.ts (4 hunks)
  • src/test/lib/utils/test_cases/contest_name_and_task_index.ts (1 hunks)
  • src/test/lib/utils/test_cases/contest_name_labels.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/lib/utils/test_cases/contest_name_labels.ts
🔇 Additional comments (8)
src/lib/utils/contest.ts (2)

282-283: LGTM!

The JAG contest label handling follows the same pattern as PCK contests and correctly uses the new generic getAojChallengeLabel function.


84-86: ⚠️ Potential issue

Verify handling of seasonal JAG contests

The regex pattern includes Summer, Winter, and Spring variants, but these aren't covered in the JAG_TRANSLATIONS. This might lead to inconsistent labeling for seasonal JAG contests.

Consider either:

  1. Removing the seasonal variants if they're not needed, or
  2. Adding translations for them in JAG_TRANSLATIONS:
 const JAG_TRANSLATIONS = {
   Prelim: '模擬国内予選',
   Regional: '模擬アジア地区予選',
+  Summer: '夏季',
+  Winter: '冬季',
+  Spring: '春季',
 };
src/lib/clients/aizu_online_judge.ts (2)

Line range hint 33-109: LGTM! Well-structured type definitions with proper type safety.

The new enums and type definitions are well-documented and provide excellent type safety through the ChallengeRoundMap type. This ensures that only valid combinations of contest types and rounds can be used.


Line range hint 353-399: Enhance error handling and consider caching API responses.

While the implementation is solid, consider these improvements:

  1. Add caching similar to the suggested improvement for fetchChallengeContests
  2. Verify that API errors are properly propagated and handled by the caller
✅ Verification successful

Let me verify the caching patterns to provide a complete response.


Let me check for any caching implementations using a different pattern.


Error handling is properly implemented, but caching could be beneficial

Based on the error handling patterns found in the codebase:

  • The method follows the consistent error handling pattern used throughout the codebase
  • Errors are properly logged with context and propagated up
  • The error messages are descriptive and include the contest type and round information

Adding caching would indeed be beneficial to:

  • Reduce API calls for frequently accessed contest data
  • Improve response times for repeated requests
  • Reduce load on the AOJ API
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check error handling patterns in the codebase
rg -A 2 "catch \(error\)" | grep -v "return \[\]"

Length of output: 6779


Script:

#!/bin/bash
# Check caching patterns in the codebase
rg -A 5 "private\s+cache" src/lib/clients/

Length of output: 44


Script:

#!/bin/bash
# Check for any caching implementations or memoization
rg -A 5 "cache|memoize|memo" src/lib/clients/

Length of output: 47

src/test/lib/utils/test_cases/contest_name_and_task_index.ts (4)

420-470: LGTM! Well-structured test data with comprehensive coverage.

The test data structure is well-organized and properly documented, covering both preliminary and regional rounds across different years with clear task ID patterns.


491-504: LGTM! Well-designed generic function.

The generateContestTestCases function is a great example of code reuse and abstraction. It effectively generalizes the test case generation pattern while maintaining type safety.


506-513: LGTM! Clean implementation of JAG-specific test case generator.

The generateAojJagTestCases function effectively utilizes the generic generator while handling JAG-specific formatting requirements.


515-517: LGTM! Consistent export implementation.

The export follows the established pattern in the file, making it easy to understand and maintain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
src/lib/utils/contest.ts (2)

289-307: Add JSDoc documentation for PCK_TRANSLATIONS

For consistency, consider adding similar JSDoc documentation to PCK_TRANSLATIONS as provided for JAG_TRANSLATIONS.

+/**
+ * Maps PCK contest type abbreviations to their Japanese translations.
+ *
+ * @example
+ * {
+ *   PCK: 'パソコン甲子園',
+ *   Prelim: '予選',
+ *   Final: '本選'
+ * }
+ */
 const PCK_TRANSLATIONS = {
   PCK: 'パソコン甲子園',
   Prelim: '予選',
   Final: '本選',
 };

309-320: Consider moving constant outside function scope

The baseLabel constant is recreated on every function call but its value never changes.

+const AOJ_BASE_LABEL = 'AOJ - ';
+
 function getAojChallengeLabel(
   translations: Readonly<ContestLabelTranslations>,
   contestId: string,
 ): string {
-  const baseLabel = 'AOJ - ';
   let label = contestId;

   Object.entries(translations).forEach(([abbrEnglish, japanese]) => {
     label = label.replace(abbrEnglish, japanese);
   });

-  return baseLabel + label;
+  return AOJ_BASE_LABEL + label;
 }
src/lib/clients/aizu_online_judge.ts (3)

117-130: Consider implementing a cache invalidation strategy

While the cache implementation is clean and well-documented, it lacks a cache invalidation mechanism. This could potentially lead to:

  1. Memory leaks in long-running applications
  2. Stale data if the upstream API is updated

Consider implementing one of these strategies:

  • Time-based expiration
  • LRU (Least Recently Used) cache
  • Maximum cache size limit
 const contestCache: Map<string, ContestsForImport> = new Map();
+const CACHE_TTL = 3600000; // 1 hour in milliseconds
+type CacheEntry<T> = {
+  data: T;
+  timestamp: number;
+};
+
+function getCacheEntry<T>(cache: Map<string, CacheEntry<T>>, key: string): T | undefined {
+  const entry = cache.get(key);
+  if (!entry) return undefined;
+  
+  if (Date.now() - entry.timestamp > CACHE_TTL) {
+    cache.delete(key);
+    return undefined;
+  }
+  
+  return entry.data;
+}

151-164: Enhance error handling for better debugging

While the concurrent fetching is efficient, the error handling could be more specific to help with debugging issues. Consider:

  1. Logging which specific fetch operation failed
  2. Preserving partial results if only some fetches fail
-      const [courses, pckPrelims, pckFinals, jagPrelims, jagRegionals] = await Promise.all([
-        this.fetchCourseContests(),
-        this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.PRELIM),
-        this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.FINAL),
-        this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.PRELIM),
-        this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.REGIONAL),
-      ]);
+      const results = await Promise.allSettled([
+        { type: 'courses', promise: this.fetchCourseContests() },
+        { type: 'pck_prelim', promise: this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.PRELIM) },
+        { type: 'pck_final', promise: this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.FINAL) },
+        { type: 'jag_prelim', promise: this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.PRELIM) },
+        { type: 'jag_regional', promise: this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.REGIONAL) },
+      ].map(async ({ type, promise }) => {
+        try {
+          return { type, data: await promise };
+        } catch (error) {
+          console.error(`Failed to fetch ${type} contests:`, error);
+          return { type, data: [] };
+        }
+      }));
+      
+      const contests = results.flatMap(result => result.data);

Line range hint 224-264: Consider making endpoint construction more robust

The endpoint construction could be made more robust by:

  1. Using URL path joining to prevent double slashes
  2. URL encoding the path segments
+  private buildEndpoint(segments: string[]): string {
+    return segments
+      .map(segment => encodeURIComponent(segment))
+      .join('/');
+  }

   private async fetchChallengeContests<T extends ChallengeContestType>(
     contestType: T,
     round: ChallengeRoundMap[T],
   ): Promise<ContestsForImport> {
     // ...
     const results = await this.fetchApiWithConfig<AOJChallengeContestAPI>({
       baseApiUrl: AOJ_API_BASE_URL,
-      endpoint: `challenges/cl/${contestType}/${round}`,
+      endpoint: this.buildEndpoint(['challenges', 'cl', contestType, round]),
       // ...
     });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 47c0238 and e1f51ae.

📒 Files selected for processing (2)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
  • src/lib/utils/contest.ts (5 hunks)
🔇 Additional comments (5)
src/lib/utils/contest.ts (2)

197-197: LGTM! Priority assignment follows documented grouping

The priority assignment for AOJ_JAG (20) aligns with the documented grouping for external platforms (18-20).

Also applies to: 227-227


279-283: LGTM! Good refactoring for code reuse

The shared getAojChallengeLabel function effectively handles both PCK and JAG contest labels while preventing accidental modifications to translations through Readonly.

src/lib/clients/aizu_online_judge.ts (3)

Line range hint 33-109: Well-structured type definitions with proper type safety!

The implementation of ChallengeRoundMap type effectively ensures type safety between contest types and their corresponding rounds, preventing invalid combinations at compile time.


309-321: Similar improvements as getContests method apply here


Line range hint 398-440: Similar improvements as fetchChallengeContests method apply here

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
src/lib/utils/contest.ts (2)

279-283: Add validation for contest ID prefix

While the code works correctly when the prefix matches, it's good practice to validate the prefix before processing. Consider adding validation:

  if (contest_id.startsWith('PCK')) {
+   if (!/^PCK/.test(contest_id)) {
+     return contest_id.toUpperCase();
+   }
    return getAojChallengeLabel(PCK_TRANSLATIONS, contest_id);
  }

  if (contest_id.startsWith('JAG')) {
+   if (!/^JAG/.test(contest_id)) {
+     return contest_id.toUpperCase();
+   }
    return getAojChallengeLabel(JAG_TRANSLATIONS, contest_id);
  }

321-331: Enhance type safety and error handling

Consider these improvements to make the function more robust:

  1. Use a more specific type for translations:
- translations: Readonly<ContestLabelTranslations>,
+ translations: Readonly<Record<string, string>>,
  1. Add error handling for missing translations:
  function getAojChallengeLabel(
    translations: Readonly<Record<string, string>>,
    contestId: string,
  ): string {
    let label = contestId;
+   const matchedTranslation = Object.entries(translations).find(
+     ([abbrEnglish]) => contestId.includes(abbrEnglish)
+   );
+   
+   if (!matchedTranslation) {
+     return aojBaseLabel + contestId;
+   }

-   Object.entries(translations).forEach(([abbrEnglish, japanese]) => {
-     label = label.replace(abbrEnglish, japanese);
-   });
+   const [abbrEnglish, japanese] = matchedTranslation;
+   label = label.replace(abbrEnglish, japanese);

    return aojBaseLabel + label;
  }
src/lib/clients/aizu_online_judge.ts (3)

117-186: Consider moving cache to instance level

The cache maps (contestCache and taskCache) are currently defined at module level. This could lead to memory leaks in long-running applications as the cache would be shared across all instances of AojApiClient.

Consider moving the cache to instance level:

 export class AojApiClient extends ContestSiteApiClient {
+  private readonly contestCache: Map<string, CacheEntry<ContestsForImport>> = new Map();
+  private readonly taskCache: Map<string, CacheEntry<TasksForImport>> = new Map();

Line range hint 266-323: Enhance error logging for better debugging

While the implementation is solid, the error logging could be more specific to help with debugging.

Consider enhancing the error logging:

-      console.error(`Failed to fetch from AOJ ${contestTypeLabel} ${round} contests:`, error);
+      console.error(
+        `Failed to fetch from AOJ ${contestTypeLabel} ${round} contests:`,
+        error instanceof Error ? error.message : error
+      );

326-334: Add input validation to buildEndpoint method

The method should validate its input to prevent potential issues with undefined or null segments.

Consider adding input validation:

   private buildEndpoint(segments: string[]): string {
+    if (!segments?.length) {
+      throw new Error('Endpoint segments array cannot be empty');
+    }
     return segments.map((segment) => encodeURIComponent(segment)).join('/');
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e1f51ae and 9b26c66.

📒 Files selected for processing (2)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
  • src/lib/utils/contest.ts (5 hunks)
🔇 Additional comments (6)
src/lib/utils/contest.ts (2)

84-86: Previous review comment is still applicable


197-197: LGTM! Priority assignment follows the documented rationale

The AOJ_JAG priority (20) is correctly placed in the external platforms category (18-20) alongside other AOJ contests.

Also applies to: 227-227

src/lib/clients/aizu_online_judge.ts (4)

Line range hint 33-109: Well-structured type system implementation!

The type definitions and enums create a robust type-safe relationship between contest types and their corresponding rounds. The ChallengeRoundMap type effectively prevents mismatches between contest types and rounds.


200-220: LGTM! Efficient implementation with concurrent fetching

The method effectively uses Promise.all for concurrent fetching of different contest types, with clear and informative logging.


Line range hint 361-388: LGTM! Consistent implementation with getContests

The method follows the same efficient pattern as getContests, using concurrent fetching and providing clear logging.


Line range hint 452-509: Apply consistent error logging enhancement

Similar to the suggestion for fetchChallengeContests, the error logging here could be more specific.

Apply the same error logging enhancement as suggested for fetchChallengeContests.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

117-168: Consider implementing a more robust cache invalidation strategy.

While the current cache implementation with TTL is good, consider adding:

  1. A maximum cache size to prevent memory issues
  2. A method to manually invalidate the cache when needed
  3. Error handling for cache operations
+const MAX_CACHE_SIZE = 1000; // Adjust based on your needs

+class Cache<T> {
+  private cache: Map<string, CacheEntry<T>> = new Map();
+
+  set(key: string, data: T): void {
+    if (this.cache.size >= MAX_CACHE_SIZE) {
+      const oldestKey = this.findOldestEntry();
+      if (oldestKey) this.cache.delete(oldestKey);
+    }
+    this.cache.set(key, { data, timestamp: Date.now() });
+  }
+
+  private findOldestEntry(): string | undefined {
+    let oldestKey: string | undefined;
+    let oldestTime = Infinity;
+    
+    for (const [key, entry] of this.cache.entries()) {
+      if (entry.timestamp < oldestTime) {
+        oldestTime = entry.timestamp;
+        oldestKey = key;
+      }
+    }
+    
+    return oldestKey;
+  }
+}

207-220: Consider adding retry logic for failed API requests.

The concurrent fetching of contests is well implemented, but network failures could benefit from retry logic.

+const MAX_RETRIES = 3;
+const RETRY_DELAY = 1000; // ms

+private async withRetry<T>(
+  operation: () => Promise<T>,
+  retries = MAX_RETRIES
+): Promise<T> {
+  try {
+    return await operation();
+  } catch (error) {
+    if (retries > 0) {
+      await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
+      return this.withRetry(operation, retries - 1);
+    }
+    throw error;
+  }
+}

473-478: Consider adding cache warmup mechanism.

The cache implementation could benefit from a warmup mechanism to pre-populate frequently accessed data.

+interface CacheConfig {
+  warmupEnabled: boolean;
+  warmupKeys: string[];
+}
+
+private async warmupCache(): Promise<void> {
+  const warmupKeys = [
+    `${ChallengeContestType.PCK}_${PckRound.PRELIM}`,
+    `${ChallengeContestType.PCK}_${PckRound.FINAL}`,
+    `${ChallengeContestType.JAG}_${JagRound.PRELIM}`,
+    `${ChallengeContestType.JAG}_${JagRound.REGIONAL}`
+  ];
+
+  await Promise.all(
+    warmupKeys.map(key => this.fetchChallengeTasks(
+      key.split('_')[0] as ChallengeContestType,
+      key.split('_')[1] as ChallengeRoundMap[ChallengeContestType]
+    ))
+  );
+}

486-487: Add rate limiting to prevent API abuse.

Consider implementing rate limiting for API requests to avoid overwhelming the AOJ API.

+private rateLimiter = new RateLimiter({
+  tokensPerInterval: 10,
+  interval: 'second',
+});

 private async fetchApiWithConfig<T>(...): Promise<T> {
+  await this.rateLimiter.removeTokens(1);
   // existing code
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9b26c66 and 8f8b842.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (2)
src/lib/clients/aizu_online_judge.ts (2)

33-39: LGTM! Well-structured enum for challenge contest types.

The enum provides a clear and type-safe way to distinguish between PCK and JAG contest types.


94-109: LGTM! Excellent type safety implementation.

The combination of JagRound enum and ChallengeRoundMap type ensures type safety between contest types and their corresponding rounds at compile time.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
src/lib/clients/aizu_online_judge.ts (3)

117-208: Consider making cache parameters configurable

While the cache implementation is well-designed, consider making the CACHE_TTL and MAX_CACHE_SIZE configurable through the constructor to enhance flexibility for different use cases.

 class Cache<T> {
   private cache: Map<string, CacheEntry<T>> = new Map();
+  constructor(
+    private readonly ttl: number = CACHE_TTL,
+    private readonly maxSize: number = MAX_CACHE_SIZE
+  ) {}

   set(key: string, data: T): void {
-    if (this.cache.size >= MAX_CACHE_SIZE) {
+    if (this.cache.size >= this.maxSize) {
       const oldestKey = this.findOldestEntry();

       if (oldestKey) {
         this.cache.delete(oldestKey);
       }
     }

     this.cache.set(key, { data, timestamp: Date.now() });
   }

   get(key: string): T | undefined {
     const entry = this.cache.get(key);

     if (!entry) {
       return undefined;
     }

-    if (Date.now() - entry.timestamp > CACHE_TTL) {
+    if (Date.now() - entry.timestamp > this.ttl) {
       this.cache.delete(key);
       return undefined;
     }

     return entry.data;
   }

244-264: Enhance error handling granularity

While the concurrent fetching is well-implemented, consider catching errors for each contest type separately to prevent a single failure from affecting all contest types.

-const [courses, pckPrelims, pckFinals, jagPrelims, jagRegionals] = await Promise.all([
-  this.fetchCourseContests(),
-  this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.PRELIM),
-  this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.FINAL),
-  this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.PRELIM),
-  this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.REGIONAL),
-]);
+const results = await Promise.allSettled([
+  { type: 'courses', promise: this.fetchCourseContests() },
+  { type: 'pck_prelim', promise: this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.PRELIM) },
+  { type: 'pck_final', promise: this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.FINAL) },
+  { type: 'jag_prelim', promise: this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.PRELIM) },
+  { type: 'jag_regional', promise: this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.REGIONAL) },
+].map(async ({ type, promise }) => {
+  try {
+    return await promise;
+  } catch (error) {
+    console.error(`Failed to fetch ${type} contests:`, error);
+    return [];
+  }
+}));

Line range hint 420-447: Consider reducing code duplication with contest fetching

The task fetching logic closely mirrors the contest fetching logic. Consider extracting the common pattern into a reusable utility function to reduce duplication and maintain consistency.

+private async fetchAllTypes<T>(
+  fetchFn: (type: ChallengeContestType, round: ChallengeRoundMap[ChallengeContestType]) => Promise<T[]>,
+  baseFn: () => Promise<T[]>
+): Promise<T[]> {
+  const results = await Promise.allSettled([
+    { type: 'courses', promise: baseFn() },
+    { type: 'pck_prelim', promise: fetchFn(ChallengeContestType.PCK, PckRound.PRELIM) },
+    { type: 'pck_final', promise: fetchFn(ChallengeContestType.PCK, PckRound.FINAL) },
+    { type: 'jag_prelim', promise: fetchFn(ChallengeContestType.JAG, JagRound.PRELIM) },
+    { type: 'jag_regional', promise: fetchFn(ChallengeContestType.JAG, JagRound.REGIONAL) },
+  ].map(async ({ type, promise }) => {
+    try {
+      return await promise;
+    } catch (error) {
+      console.error(`Failed to fetch ${type}:`, error);
+      return [];
+    }
+  }));
+  return results.flat();
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8f8b842 and a79a312.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (2)
src/lib/clients/aizu_online_judge.ts (2)

Line range hint 33-109: Well-structured type definitions with proper type safety!

The implementation of ChallengeRoundMap effectively ensures type safety between contest types and their corresponding rounds, preventing invalid combinations at compile-time.


370-393: Robust URL endpoint builder with proper validation!

The implementation includes proper input validation and URL encoding, effectively preventing potential security issues with malformed URLs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

117-219: Consider adding cache invalidation mechanism.

While the cache implementation is solid with TTL and size limits, consider adding a method to manually invalidate the cache when needed (e.g., for testing or when data is known to be stale).

 class Cache<T> {
   private cache: Map<string, CacheEntry<T>> = new Map();

+  /**
+   * Clears all entries from the cache.
+   */
+  clear(): void {
+    this.cache.clear();
+  }

262-275: Consider adding error type information.

The error logging could be more informative by including the error type and message.

-      console.log(
+      console.info(
         `Found AOJ contests - Total: ${contests.length} ` +
           `(Courses: ${courses.length}, PCK: ${pckPrelims.length + pckFinals.length}, ` +
           `JAG: ${jagPrelims.length + jagRegionals.length})`,
       );
     } catch (error) {
-      console.error(`Failed to fetch contests from AOJ API:`, error);
+      console.error(`Failed to fetch contests from AOJ API:`, {
+        error: error instanceof Error ? error.message : String(error),
+        type: error?.constructor?.name
+      });

387-404: Consider strengthening URL segment validation.

While the current validation is good, consider adding length limits and additional checks for potential security issues.

 private buildEndpoint(segments: string[]): string {
   if (!segments?.length) {
     throw new Error('Endpoint segments array cannot be empty');
   }

   // Allow alphanumeric characters, hyphens, and underscores
   const validateSegment = (segment: string): boolean => {
-    return /^[a-zA-Z0-9-_]+$/.test(segment);
+    const MAX_SEGMENT_LENGTH = 100;
+    return segment.length <= MAX_SEGMENT_LENGTH && 
+           /^[a-zA-Z0-9-_]+$/.test(segment) &&
+           !segment.includes('..');
   };

   for (const segment of segments) {
     if (!validateSegment(segment)) {
-      throw new Error(`Invalid segment: ${segment}`);
+      throw new Error(`Invalid segment: ${segment}. Segments must be alphanumeric with hyphens and underscores, max length ${MAX_SEGMENT_LENGTH}`);
     }
   }

   return segments.map((segment) => encodeURIComponent(segment)).join('/');
 }

446-458: Consider adding retry mechanism for API calls.

The concurrent API calls could benefit from a retry mechanism to handle temporary failures.

Consider implementing a retry mechanism with exponential backoff for the Promise.all calls to handle temporary API failures gracefully.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a79a312 and ae1285d.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (5)
src/lib/clients/aizu_online_judge.ts (5)

33-39: LGTM! Well-structured enum definition.

The ChallengeContestType enum is well-defined and follows TypeScript best practices.


91-109: LGTM! Type-safe contest round mapping.

The combination of JagRound enum and ChallengeRoundMap type creates a type-safe relationship between contest types and their rounds, preventing invalid combinations at compile time.


230-250: LGTM! Well-documented cache properties.

The cache properties are well-documented with clear explanations of their purpose and types.


335-338: LGTM! Type-safe generic implementation.

The generic type parameter T extends ChallengeContestType with ChallengeRoundMap[T] ensures type safety between contest types and their corresponding rounds.


535-544: LGTM! Efficient caching implementation.

The caching implementation for challenge tasks is well-structured with proper type safety and cache key generation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

117-226: Consider enhancing cache error handling

While the cache implementation is robust, consider adding error handling for edge cases:

  1. Handle potential race conditions in concurrent operations
  2. Add error handling for invalid TTL or maxSize values in constructor
  3. Consider adding a method to check cache health/status
 class Cache<T> {
   constructor(
     private readonly ttl: number = CACHE_TTL,
     private readonly maxSize: number = MAX_CACHE_SIZE,
-  ) {}
+  ) {
+    if (ttl <= 0) throw new Error('TTL must be positive');
+    if (maxSize <= 0) throw new Error('Max size must be positive');
+  }

+  public get size(): number {
+    return this.cache.size;
+  }

+  public get health(): { size: number; oldestEntry: number } {
+    const oldestEntry = Math.min(...Array.from(this.cache.values()).map(e => e.timestamp));
+    return { size: this.cache.size, oldestEntry };
+  }

Line range hint 262-385: Improve error handling in concurrent fetching

While using Promise.all for concurrent fetching is efficient, if any single request fails, it will cause the entire operation to fail. Consider using Promise.allSettled to handle partial failures gracefully.

-      const [courses, pckPrelims, pckFinals, jagPrelims, jagRegionals] = await Promise.all([
+      const results = await Promise.allSettled([
         this.fetchCourseContests(),
         this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.PRELIM),
         this.fetchChallengeContests(ChallengeContestType.PCK, PckRound.FINAL),
         this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.PRELIM),
         this.fetchChallengeContests(ChallengeContestType.JAG, JagRound.REGIONAL),
-      ]);
+      ]);
+      
+      const [courses, pckPrelims, pckFinals, jagPrelims, jagRegionals] = results.map(result => 
+        result.status === 'fulfilled' ? result.value : []
+      );

388-418: Enhance error message clarity

The error message could be more specific about the validation rules. Consider creating a constant for the validation pattern and including it in the error message.

+  private static readonly SEGMENT_PATTERN = '^[a-zA-Z0-9-_]+$';
+  private static readonly SEGMENT_PATTERN_DESC = 'alphanumeric characters, hyphens, and underscores';

   private buildEndpoint(segments: string[]): string {
     // ...
     const validateSegment = (segment: string): boolean => {
       return (
         segment.length <= MAX_SEGMENT_LENGTH &&
-        /^[a-zA-Z0-9-_]+$/.test(segment) &&
+        new RegExp(AojApiClient.SEGMENT_PATTERN).test(segment) &&
         !segment.includes('..')
       );
     };
     // ...
     throw new Error(
-      `Invalid segment: ${segment}. Segments must be alphanumeric with hyphens and underscores, max length ${MAX_SEGMENT_LENGTH}`,
+      `Invalid segment: ${segment}. Segments must contain only ${AojApiClient.SEGMENT_PATTERN_DESC}, ` +
+      `be ${MAX_SEGMENT_LENGTH} characters or less, and not include path traversal sequences`,
     );

Line range hint 445-472: Reduce code duplication in logging messages

Consider extracting the logging logic into a helper method to reduce duplication and make it easier to maintain consistent logging format across the codebase.

+  private logEntityCount(
+    entity: 'tasks' | 'contests',
+    counts: { courses: number; pck: number; jag: number }
+  ): void {
+    console.info(
+      `Found AOJ ${entity} - Total: ${counts.courses + counts.pck + counts.jag} ` +
+      `(Courses: ${counts.courses}, PCK: ${counts.pck}, JAG: ${counts.jag})`
+    );
+  }

   async getTasks(): Promise<TasksForImport> {
     try {
       // ... fetch tasks ...
-      console.info(
-        `Found AOJ tasks - Total: ${tasks.length} ` +
-          `(Courses: ${courses.length}, PCK: ${pckPrelims.length + pckFinals.length}, ` +
-          `JAG: ${jagPrelims.length + jagRegionals.length})`,
-      );
+      this.logEntityCount('tasks', {
+        courses: courses.length,
+        pck: pckPrelims.length + pckFinals.length,
+        jag: jagPrelims.length + jagRegionals.length
+      });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ae1285d and af1ccae.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (1)
src/lib/clients/aizu_online_judge.ts (1)

Line range hint 33-109: Well-structured type definitions with proper type safety!

The implementation of ChallengeContestType, JagRound, and ChallengeRoundMap provides excellent type safety and maintainability. The mapped type pattern in ChallengeRoundMap ensures that only valid combinations of contest types and rounds are possible.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

117-122: Consider making cache configuration more flexible.

The cache configuration values are hardcoded. Consider making them configurable through constructor parameters.

-const CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
-const MAX_CACHE_SIZE = 50;
+const DEFAULT_CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
+const DEFAULT_MAX_CACHE_SIZE = 50;

380-386: Consider implementing cache key normalization.

The cache key construction could benefit from normalization to prevent potential collisions.

-    const cacheKey = `${contestType}_${round}`;
+    const cacheKey = `${contestType.toLowerCase()}_${round.toLowerCase()}`;

Also applies to: 611-617


435-438: Consider using structured logging.

The current logging implementation uses string concatenation. Consider using structured logging for better log aggregation and analysis.

-    console.info(
-      `Found AOJ ${entity} - Total: ${counts.courses + counts.pck + counts.jag} ` +
-        `(Courses: ${counts.courses}, PCK: ${counts.pck}, JAG: ${counts.jag})`,
-    );
+    console.info('Found AOJ entities', {
+      entity,
+      total: counts.courses + counts.pck + counts.jag,
+      breakdown: {
+        courses: counts.courses,
+        pck: counts.pck,
+        jag: counts.jag,
+      },
+    });

Line range hint 594-650: Consider implementing retry mechanism.

The fetch operations could benefit from a retry mechanism to handle temporary network issues.

Consider implementing an exponential backoff retry mechanism for API calls. Would you like me to provide an implementation example?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between af1ccae and c72c67d.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (3)
src/lib/clients/aizu_online_judge.ts (3)

33-39: LGTM! Well-structured enums for contest types and rounds.

The enums are well-defined and follow TypeScript best practices with clear naming conventions.

Also applies to: 91-97


99-109: LGTM! Type-safe mapping between contest types and rounds.

The ChallengeRoundMap type effectively ensures type safety between contest types and their corresponding rounds.


267-288: LGTM! Well-implemented caching mechanism.

The cache implementation with TTL and size limits is well-designed and properly documented.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

117-256: Consider implementing periodic cache cleanup.

While the cache implementation is solid, it could benefit from a periodic cleanup mechanism to prevent potential memory leaks from expired entries that haven't been accessed.

 class Cache<T> {
   private cache: Map<string, CacheEntry<T>> = new Map();
+  private cleanupInterval: NodeJS.Timeout;
 
   constructor(
     private readonly timeToLive: number = DEFAULT_CACHE_TTL,
     private readonly maxSize: number = DEFAULT_MAX_CACHE_SIZE,
   ) {
     if (timeToLive <= 0) {
       throw new Error('TTL must be positive');
     }
     if (maxSize <= 0) {
       throw new Error('Max size must be positive');
     }
+    this.cleanupInterval = setInterval(() => this.cleanup(), timeToLive);
   }
 
+  private cleanup(): void {
+    const now = Date.now();
+    for (const [key, entry] of this.cache.entries()) {
+      if (now - entry.timestamp > this.timeToLive) {
+        this.cache.delete(key);
+      }
+    }
+  }
+
+  dispose(): void {
+    clearInterval(this.cleanupInterval);
+    this.cache.clear();
+  }

299-321: Consider adding retry mechanism for failed fetches.

The concurrent fetching implementation handles errors well by continuing with partial results, but critical endpoints might benefit from retry logic.

+const MAX_RETRIES = 3;
+const RETRY_DELAY = 1000; // ms
+
+private async withRetry<T>(
+  operation: () => Promise<T>,
+  retries = MAX_RETRIES,
+  delay = RETRY_DELAY
+): Promise<T> {
+  try {
+    return await operation();
+  } catch (error) {
+    if (retries > 0) {
+      await new Promise(resolve => setTimeout(resolve, delay));
+      return this.withRetry(operation, retries - 1, delay * 2);
+    }
+    throw error;
+  }
+}

446-476: Enhance URL segment validation.

While the current validation is good, consider adding more restrictions to prevent potential security issues.

 const validateSegment = (segment: string): boolean => {
   return (
     segment.length <= MAX_SEGMENT_LENGTH &&
-    /^[a-zA-Z0-9][-_a-zA-Z0-9]*$/.test(segment) &&
+    /^[a-zA-Z][a-zA-Z0-9][-_a-zA-Z0-9]*$/.test(segment) &&
     !segment.includes('..')
   );
 };

518-540: Consider implementing rate limiting.

To prevent overwhelming the AOJ API, consider adding rate limiting for concurrent requests.

+import pLimit from 'p-limit';
+
+const MAX_CONCURRENT_REQUESTS = 3;
+const limit = pLimit(MAX_CONCURRENT_REQUESTS);
+
 async getTasks(): Promise<TasksForImport> {
   try {
     const results = await Promise.allSettled([
-      this.fetchCourseTasks(),
-      this.fetchChallengeTasks(ChallengeContestType.PCK, PckRound.PRELIM),
-      this.fetchChallengeTasks(ChallengeContestType.PCK, PckRound.FINAL),
-      this.fetchChallengeTasks(ChallengeContestType.JAG, JagRound.PRELIM),
-      this.fetchChallengeTasks(ChallengeContestType.JAG, JagRound.REGIONAL),
+      limit(() => this.fetchCourseTasks()),
+      limit(() => this.fetchChallengeTasks(ChallengeContestType.PCK, PckRound.PRELIM)),
+      limit(() => this.fetchChallengeTasks(ChallengeContestType.PCK, PckRound.FINAL)),
+      limit(() => this.fetchChallengeTasks(ChallengeContestType.JAG, JagRound.PRELIM)),
+      limit(() => this.fetchChallengeTasks(ChallengeContestType.JAG, JagRound.REGIONAL)),
     ]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c72c67d and c97d5a1.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🔇 Additional comments (1)
src/lib/clients/aizu_online_judge.ts (1)

Line range hint 33-109: LGTM! Well-structured type definitions.

The new enums and type definitions are well-documented and provide good type safety for contest types and rounds.

KATO-Hiro

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

33-39: LGTM! Consider enhancing enum documentation.

The implementation of ChallengeContestType, JagRound, and ChallengeRoundMap provides excellent type safety. The type mapping ensures that only valid combinations of contest types and rounds are possible.

Consider adding @deprecated or @experimental JSDoc tags to indicate the stability status of these new enums.

Also applies to: 91-97, 99-109


117-280: Enhance cache cleanup mechanism.

The cache implementation is robust with TTL and size limits. However, there are a few potential improvements:

  1. Consider using WeakMap for better memory management:
-  private cache: Map<string, CacheEntry<T>> = new Map();
+  private cache: WeakMap<string, CacheEntry<T>> = new WeakMap();
  1. Add a maximum age limit to prevent stale data:
   constructor(
     private readonly timeToLive: number = DEFAULT_CACHE_TTL,
     private readonly maxSize: number = DEFAULT_MAX_CACHE_SIZE,
+    private readonly maxAge: number = DEFAULT_MAX_AGE,
   ) {

Line range hint 332-477: Consider adding retry mechanism for failed requests.

The error handling with Promise.allSettled is good, but network requests can fail transiently.

Consider implementing a retry mechanism:

private async withRetry<T>(
  operation: () => Promise<T>,
  retries = 3,
  delay = 1000
): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    if (retries > 0) {
      await new Promise(resolve => setTimeout(resolve, delay));
      return this.withRetry(operation, retries - 1, delay * 2);
    }
    throw error;
  }
}

Line range hint 551-693: Add rate limiting for API requests.

Multiple concurrent requests could potentially overwhelm the AOJ API.

Consider implementing a rate limiter:

class RateLimiter {
  private queue: Array<() => Promise<void>> = [];
  private processing = false;

  constructor(private rateLimit: number, private interval: number) {}

  async schedule<T>(task: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          resolve(await task());
        } catch (error) {
          reject(error);
        }
      });
      this.process();
    });
  }

  private async process(): Promise<void> {
    if (this.processing) return;
    this.processing = true;
    
    while (this.queue.length > 0) {
      const batch = this.queue.splice(0, this.rateLimit);
      await Promise.all(batch.map(task => task()));
      await new Promise(resolve => setTimeout(resolve, this.interval));
    }
    
    this.processing = false;
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c97d5a1 and 7f7a9ad.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)

KATO-Hiro

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
src/lib/clients/aizu_online_judge.ts (3)

36-39: Consider using string literal types for better type safety.

The enum values could be defined as string literal types to provide better type safety and prevent potential runtime errors from invalid string values.

-enum ChallengeContestType {
-  PCK = 'pck',
-  JAG = 'jag',
-}
+type ChallengeContestType = 'pck' | 'jag';

-enum JagRound {
-  PRELIM = 'prelim',
-  REGIONAL = 'regional',
-}
+type JagRound = 'prelim' | 'regional';

Also applies to: 94-97


291-312: Consider adding cache configuration options.

The cache instances are created with default values. Consider allowing these to be configured through constructor parameters.

+interface AojApiClientConfig {
+  contestCacheTTL?: number;
+  contestCacheSize?: number;
+  taskCacheTTL?: number;
+  taskCacheSize?: number;
+}

 export class AojApiClient extends ContestSiteApiClient {
+  constructor(config?: AojApiClientConfig) {
+    super();
+    this.contestCache = new Cache<ContestsForImport>(
+      config?.contestCacheTTL,
+      config?.contestCacheSize
+    );
+    this.taskCache = new Cache<TasksForImport>(
+      config?.taskCacheTTL,
+      config?.taskCacheSize
+    );
+  }

340-347: Consider implementing retry mechanism for failed requests.

The error handling is good, but for improved reliability, consider implementing a retry mechanism for failed requests.

+private async retryOperation<T>(
+  operation: () => Promise<T>,
+  maxRetries: number = 3,
+  delay: number = 1000
+): Promise<T> {
+  for (let i = 0; i < maxRetries; i++) {
+    try {
+      return await operation();
+    } catch (error) {
+      if (i === maxRetries - 1) throw error;
+      await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
+    }
+  }
+  throw new Error('Max retries reached');
+}

 const [courses, pckPrelims, pckFinals, jagPrelims, jagRegionals] = results.map((result) => {
   if (result.status === 'rejected') {
-    console.error(`Failed to fetch contests from AOJ API:`, result.reason);
+    console.error(`Failed to fetch contests from AOJ API after retries:`, result.reason);
     return [];
   }
   return result.value;
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7f7a9ad and 2e0f95c.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)

Copy link
Collaborator Author

@KATO-Hiro KATO-Hiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@KATO-Hiro KATO-Hiro merged commit 81d80f0 into staging Nov 23, 2024
3 checks passed
@KATO-Hiro KATO-Hiro deleted the #1514 branch November 23, 2024 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AOJ: JAGの過去問をインポートできるようにしましょう

2 participants