Skip to content

♻️ Refactoring for AOJ API client (#1995) #1999

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: staging
Choose a base branch
from
Open

Conversation

KATO-Hiro
Copy link
Collaborator

@KATO-Hiro KATO-Hiro commented May 5, 2025

close #1995

Summary by CodeRabbit

  • New Features
    • Introduced a new caching system with configurable expiration and size limits for improved performance.
    • Added a new HTTP client class to handle API requests with enhanced error handling and response validation.
    • Implemented a caching strategy class to efficiently manage contest and task data retrieval with automatic caching.
    • Redesigned the Aizu Online Judge client into modular components for better separation of concerns and maintainability.
  • Tests
    • Added comprehensive tests to ensure cache reliability, including expiration, eviction, and resource cleanup.
  • Refactor
    • Updated internal structure to use a shared cache module, streamlining cache management across the application.
    • Adjusted import paths for API client classes to improve modularity and maintainability.

Copy link

coderabbitai bot commented May 5, 2025

"""

Walkthrough

The changes refactor the Aizu Online Judge API client by removing its internal cache and monolithic methods, replacing them with a modular, layered client design that composes specialized clients for courses and challenges. A new generic cache class with TTL and size limits is introduced in a separate module, alongside a caching strategy class. Comprehensive tests for the cache are added. Import paths for a shared client class are updated.

Changes

File(s) Change Summary
src/lib/clients/aizu_online_judge.ts Refactored AOJ client to remove internal cache and monolithic methods; introduced abstract base and specialized clients; composed clients in main AOJApiClient; removed local caches.
src/lib/clients/cache.ts Added generic Cache<T> class with TTL, max size, LRU eviction, health reporting, and configuration interfaces.
src/lib/clients/cache_strategy.ts Added ContestTaskCache class to encapsulate contest and task caching strategies with generic cached-or-fetch methods.
src/lib/clients/http_client.ts Added TasksApiClient interface and new HttpRequestClient class for API requests; deprecated old ContestSiteApiClient; updated type definitions.
src/lib/clients/atcoder_problems.ts Changed import path of ContestSiteApiClient from $lib/clients/common to $lib/clients/http_client.
src/test/lib/clients/cache.test.ts Added comprehensive unit tests for the new Cache<T> class covering TTL, eviction, size, health, and disposal behaviors.
src/test/lib/clients/aizu_online_judge.test.ts Updated import path of ContestSiteApiClient to $lib/clients/http_client.
src/test/lib/clients/atcoder_problems.test.ts Updated import path of ContestSiteApiClient to $lib/clients/http_client.
src/test/lib/clients/record_requests.ts Updated import path of ContestSiteApiClient to $lib/clients/http_client.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AOJApiClient
    participant CoursesClient
    participant ChallengesClient
    participant ContestTaskCache
    participant HttpRequestClient

    Client->>AOJApiClient: getContests()/getTasks()
    AOJApiClient->>CoursesClient: getContests()/getTasks()
    AOJApiClient->>ChallengesClient: getContests()/getTasks()
    CoursesClient->>ContestTaskCache: getCachedOrFetchContests()/getCachedOrFetchTasks()
    ChallengesClient->>ContestTaskCache: getCachedOrFetchContests()/getCachedOrFetchTasks()
    ContestTaskCache->>Cache: get(key)
    alt Cache hit
        Cache-->>ContestTaskCache: cached data
    else Cache miss
        ContestTaskCache->>HttpRequestClient: fetchApiWithConfig()
        HttpRequestClient-->>ContestTaskCache: fetched data
        ContestTaskCache->>Cache: set(key, data)
    end
    ContestTaskCache-->>CoursesClient/ChallengesClient: data
    CoursesClient/ChallengesClient-->>AOJApiClient: data
    AOJApiClient-->>Client: combined data
Loading

Assessment against linked issues

Objective Addressed Explanation
Refactor AOJ API client to separate and modularize cache logic (#1995)

Poem

A modular client hops in the code,
Splitting tasks on a clearer road.
Cache now fresh, and layered with care,
Fetching data with less despair.
Rabbits cheer as code takes flight,
Refactored well, the future’s bright!
🐇✨📦
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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: 2

🔭 Outside diff range comments (1)
src/lib/clients/aizu_online_judge.ts (1)

141-151: 🛠️ Refactor suggestion

Update constructor JSDoc to match implementation.

The JSDoc for the constructor mentions Cache parameters, but it actually takes an ApiClientConfig object. This appears to be a leftover from before the refactoring.

/**
 * Constructs an instance of the Aizu Online Judge client.
 *
 * @param {ApiClientConfig} [config] - Optional configuration object for the API client.
- * @param {Cache<ContestsForImport>} [config.contestCache] - Configuration for the contest cache.
- * @param {number} [config.contestCache.timeToLive] - Time to live for contest cache entries.
- * @param {number} [config.contestCache.maxSize] - Maximum size of the contest cache.
- * @param {Cache<TasksForImport>} [config.taskCache] - Configuration for the task cache.
- * @param {number} [config.taskCache.timeToLive] - Time to live for task cache entries.
- * @param {number} [config.taskCache.maxSize] - Maximum size of the task cache.
+ * @param {CacheConfig} [config.contestCache] - Configuration for the contest cache.
+ * @param {CacheConfig} [config.taskCache] - Configuration for the task cache.
 */
🧹 Nitpick comments (7)
src/lib/clients/cache.ts (5)

63-65: Document the key length restriction.

The validation enforces a maximum length of 255 characters for keys, but there's no explanation for this specific limit in the documentation.

/**
 * Sets a new entry in the cache with the specified key and data.
 * If the cache size exceeds the maximum limit, the oldest entry is removed.
 *
+ * @throws {Error} If the key is empty, not a string, or longer than 255 characters.
 * @param key - The key associated with the data to be cached.
 * @param data - The data to be cached.
 */

126-148: Add JSDoc comments to private methods.

The private methods lack JSDoc comments, unlike the public methods. Adding documentation to these methods would improve maintainability and consistency.

+/**
+ * Removes expired entries from the cache.
+ * This method is called periodically by the cleanup interval.
+ */
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);
    }
  }
}

+/**
+ * Finds the key of the oldest entry in the cache based on timestamp.
+ *
+ * @returns The key of the oldest entry, or undefined if the cache is empty.
+ */
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;
}

168-168: Add JSDoc comment for the DEFAULT_MAX_CACHE_SIZE constant.

The DEFAULT_MAX_CACHE_SIZE constant lacks a JSDoc comment, unlike the DEFAULT_CACHE_TTL constant.

/**
 * The time-to-live (TTL) for the cache, specified in milliseconds.
 * This value represents 1 hour.
 */
const DEFAULT_CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
+/**
+ * The default maximum number of entries the cache can hold.
+ * This value represents 50 entries.
+ */
const DEFAULT_MAX_CACHE_SIZE = 50;

177-180: Consider exporting CacheConfig interface.

The CacheConfig interface is used in the exported ApiClientConfig, but it's not exported itself. Exporting it would make it reusable for other modules that need to configure the Cache.

-interface CacheConfig {
+export interface CacheConfig {
  timeToLive?: number;
  maxSize?: number;
}

189-192: Consider moving ApiClientConfig to a more specific location.

The ApiClientConfig interface is tightly coupled with API client functionality, but it's in a generic cache module. Consider moving it to a more specific client-related file.

src/test/lib/clients/cache.test.ts (2)

15-30: Add test for TTL = 0 in constructor validation.

The current tests check negative TTL values, but not zero, which should also throw an error according to the implementation.

test('expects to throw error if TTL is not positive', () => {
  expect(() => new Cache<string>(-1)).toThrow('TTL must be positive');
  expect(() => new Cache<string>(-2)).toThrow('TTL must be positive');
+ expect(() => new Cache<string>(0)).toThrow('TTL must be positive');
});

191-205: Add test for health information when the cache is empty.

The current test validates health information for a non-empty cache, but it would be valuable to test the edge case of an empty cache to ensure it doesn't crash.

describe('health', () => {
  test('expects to report correct health information', () => {
    const cache = new Cache<string>();

    vi.setSystemTime(new Date('2023-01-01T00:00:00.000Z'));
    cache.set('key1', 'value1');

    vi.setSystemTime(new Date('2023-01-01T00:00:01.000Z'));
    cache.set('key2', 'value2');

    const health = cache.health;
    expect(health.size).toBe(2);
    expect(health.oldestEntry).toBe(new Date('2023-01-01T00:00:00.000Z').getTime());
  });
  
+ test('expects to handle empty cache health information', () => {
+   const cache = new Cache<string>();
+   const health = cache.health;
+   expect(health.size).toBe(0);
+   // The behavior for oldestEntry depends on your implementation
+ });
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4d5fbe and aec091e.

📒 Files selected for processing (3)
  • src/lib/clients/aizu_online_judge.ts (1 hunks)
  • src/lib/clients/cache.ts (1 hunks)
  • src/test/lib/clients/cache.test.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/test/lib/clients/cache.test.ts (1)
src/lib/clients/cache.ts (2)
  • Cache (8-149)
  • health (48-53)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: preview
🔇 Additional comments (8)
src/lib/clients/cache.ts (2)

1-7: Excellent documentation for the Cache class!

The initial class documentation is clear, comprehensive, and explains the purpose and behavior of the cache very well.


8-30: Well-structured class constructor with proper validation.

Good job validating the input parameters and setting sensible defaults. The setup of the cleanup interval is appropriate.

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

3-3: Good job refactoring the cache implementation.

Extracting the cache implementation into a separate reusable module improves code organization and maintainability. The import statement is clean and specific.


152-163: Well-implemented cache initialization in the constructor.

The constructor properly initializes the cache instances with the provided configuration, making good use of the new generic Cache class.

src/test/lib/clients/cache.test.ts (4)

4-14: Well-structured test suite with proper setup and teardown.

The test suite is organized logically with good use of Vitest's testing features. The setup with fake timers is appropriate for testing time-dependent cache behavior.


97-115: Excellent test coverage for cache eviction behavior.

The tests thoroughly verify that the cache properly evicts the oldest entry when the maximum size is reached.


229-246: Great test for automatic cleanup of expired entries.

The test verifies an important aspect of the cache's behavior - the automatic cleanup of expired entries. This ensures the cache won't grow unbounded with expired entries.


247-259: Well-implemented test for the dispose method.

The test properly verifies that the dispose method clears the cleanup interval and empties the cache, which is essential for preventing memory leaks.

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

♻️ Duplicate comments (1)
src/lib/clients/cache.ts (1)

106-110: Update JSDoc comment in the dispose method.

The JSDoc comment still refers to "client" which appears to be a remnant from the original implementation. This should be updated to refer to the "Cache instance" for clarity and consistency.

/**
 * Disposes of resources used by the cache instance.
 *
 * This method clears the interval used for cleanup and clears the cache.
- * It should be called when the client is no longer needed to prevent memory leaks.
+ * It should be called when the cache instance is no longer needed to prevent memory leaks.
 */
🧹 Nitpick comments (2)
src/lib/clients/cache.ts (2)

151-163: Consider optimizing the findOldestEntry method.

While the current implementation works well, for very large caches, you might want to consider maintaining a separate data structure (like a min-heap) to track entry age more efficiently. However, given the default max size of 50, the current implementation is perfectly adequate.


1-212: Consider adding a 'has' method to check for key existence.

The current implementation doesn't provide a way to check if a key exists in the cache without potentially removing expired entries. Adding a has method that checks existence without side effects could be useful.

/**
 * Checks if a key exists in the cache without removing expired entries.
 * 
 * @param key - The key to check.
 * @returns True if the key exists in the cache, false otherwise.
 */
has(key: string): boolean {
  return this.cache.has(key);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aec091e and c46c68b.

📒 Files selected for processing (3)
  • src/lib/clients/aizu_online_judge.ts (2 hunks)
  • src/lib/clients/cache.ts (1 hunks)
  • src/test/lib/clients/cache.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/clients/aizu_online_judge.ts
  • src/test/lib/clients/cache.test.ts
🔇 Additional comments (7)
src/lib/clients/cache.ts (7)

1-7: Clean and comprehensive JSDoc for the Cache class.

The documentation clearly explains the purpose and functionality of the Cache class, including its automatic cache invalidation and eviction policies. This helps users understand how to use the class effectively.


18-30: Constructor validation looks good.

The constructor properly validates that both timeToLive and maxSize are positive values, which prevents potential issues with invalid configurations. The initialization of the cleanup interval is also appropriate.


48-57: Good handling of empty cache case in the health getter.

The implementation correctly handles the case when the cache is empty by returning 0 for both size and oldestEntry, preventing the potential issue of returning Infinity for oldestEntry.


68-82: The set method implementation is solid.

The method properly validates the key, handles cache eviction when size limit is reached, and updates entries with the current timestamp. The key validation checks for empty strings, non-string values, and excessively long keys, which is a good security practice.


90-103: The get method effectively handles expired entries.

The implementation correctly removes entries that have exceeded their TTL when accessed, which helps maintain cache integrity. The method returns undefined for both missing and expired entries, which is a clean design.


136-144: Efficient cleanup implementation.

The cleanup method effectively removes expired entries by iterating through the cache and comparing timestamps against the TTL. This implementation is clean and straightforward.


189-211: Well-designed interfaces for cache configuration.

The CacheConfig and ApiClientConfig interfaces provide a clean way to configure caching behavior with optional parameters. This design makes the cache system flexible and easy to customize for different use cases.

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

🧹 Nitpick comments (3)
src/lib/clients/http_client.ts (2)

32-47: Consider aligning error message content with the existing implementation.

While the error handling is thorough, there's an inconsistency in error reporting between the new and old implementations. The new implementation includes the full URL in the error message (line 40), but refers to just the endpoint in the catch block (line 45), while the old implementation consistently refers to the endpoint.

-        throw new Error(`${errorMessage}. Response validation failed for ${url}`);
+        throw new Error(`${errorMessage}. Response validation failed for ${endpoint}`);

115-120: Consider updating the FetchAPIConfig type to match the new implementation.

The FetchAPIConfig type still includes the baseApiUrl property, but the new HttpRequestClient.fetchApiWithConfig method doesn't use it directly in its config parameter, instead using the base URL from the constructor. This could lead to confusion.

export type FetchAPIConfig<T> = {
-  baseApiUrl: string;
  endpoint: string;
  errorMessage: string;
  validateResponse?: (data: T) => boolean;
};
src/lib/clients/cache_strategy.ts (1)

43-47: Consider using structured logging instead of console statements.

The class uses console.log for logging cache hits, which might not be suitable for a production environment. Consider implementing a configurable logging mechanism that can be adjusted based on the environment.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d2be67 and 451c7fc.

📒 Files selected for processing (2)
  • src/lib/clients/cache_strategy.ts (1 hunks)
  • src/lib/clients/http_client.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/lib/clients/cache_strategy.ts (2)
src/lib/clients/cache.ts (1)
  • Cache (8-164)
src/lib/types/contest.ts (1)
  • ContestsForImport (21-21)
src/lib/clients/http_client.ts (1)
src/lib/types/contest.ts (1)
  • ContestsForImport (21-21)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: preview
🔇 Additional comments (5)
src/lib/clients/http_client.ts (3)

5-48: Well-structured HTTP client implementation.

The introduction of HttpRequestClient provides a more modular approach to making API requests with a configurable base URL. The implementation cleanly handles URL construction, error handling, and response validation.


50-58: Good interface design for API clients.

The TasksApiClient interface provides a clear contract for API clients with appropriate generic type parameters, which will make implementing and testing clients easier.


60-62: Appropriately marked deprecated class.

Properly marking the ContestSiteApiClient as deprecated with the @deprecated JSDoc tag helps users understand that they should migrate to the new implementation.

src/lib/clients/cache_strategy.ts (2)

1-20: Good separation of concerns for caching strategy.

The ContestTaskCache class effectively separates caching logic from data fetching, taking advantage of the generic Cache class. The constructor cleanly accepts separate caches for different data types.


62-80: Convenience methods improve readability.

The getCachedOrFetchContests and getCachedOrFetchTasks methods provide a nice abstraction over the generic method, making the client code more readable and type-safe.

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 comments (1)
src/lib/clients/http_client.ts (1)

84-102: ⚠️ Potential issue

ContestSiteApiClient.fetchApiWithConfig now accepts an optional baseApiUrl but still requires it

baseApiUrl was made optional to deprecate the class, yet new URL(...)
is invoked unconditionally. Passing undefined will throw a
TypeError at runtime.
Either keep the parameter mandatory or provide a fallback/error guard:

if (!baseApiUrl) {
  throw new Error('baseApiUrl is required when using ContestSiteApiClient');
}
♻️ Duplicate comments (1)
src/lib/clients/aizu_online_judge.ts (1)

467-507: Same logging concern as above applies to challenge-contest metrics; consider replacing console.log with an injectable logger or removing it entirely.

🧹 Nitpick comments (3)
src/lib/clients/cache.ts (1)

22-30: Prefer using the instance property this.timeToLive for clarity

Inside the constructor you validate timeToLive and pass it to
setInterval().
Because the parameter name shadows the class property generated by the
parameter-property syntax, readers may wonder which value is being used.
Switching to this.timeToLive removes any ambiguity and keeps the body
consistent with the rest of the class.

-if (timeToLive <= 0) {
+if (this.timeToLive <= 0) {
   throw new Error('TTL must be positive');
 }
 ...
-this.cleanupInterval = setInterval(() => this.cleanup(), timeToLive);
+this.cleanupInterval = setInterval(() => this.cleanup(), this.timeToLive);
src/lib/clients/http_client.ts (1)

46-56: fetchApiWithConfig swallows original error details

Wrapping the caught error in a new Error string-interpolates the
original object, which frequently yields [object Object], losing the
stack trace and message. Preserve the original error as cause
(ES2022) or append its message explicitly:

} catch (error) {
-  throw new Error(`Failed to fetch from ${endpoint}: ${error}`);
+  throw new Error(`Failed to fetch from ${endpoint}: ${error instanceof Error ? error.message : String(error)}`, { cause: error as Error });
}
src/lib/clients/aizu_online_judge.ts (1)

377-381: Avoid console.log in library code

Logging inside a reusable client leaks implementation details and
creates noisy stdout for consumers. Prefer injecting a logger or
wrapping calls behind a debug flag.

-console.log(`Found AOJ course: ${coursesForContest.length} contests.`);
+// this.logger?.debug(`Found AOJ course: ${coursesForContest.length} contests.`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 451c7fc and 3df6220.

📒 Files selected for processing (3)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
  • src/lib/clients/cache.ts (1 hunks)
  • src/lib/clients/http_client.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: preview

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

🧹 Nitpick comments (2)
src/lib/clients/aizu_online_judge.ts (2)

377-378: Consistent use of debug logging

The code now consistently uses console.debug for logging counts, which is an improvement over the previous implementation. Consider using a structured logging library in the future for more advanced log filtering and formatting capabilities.

Also applies to: 399-400, 466-469, 506-507


483-491: Minor duplication in API fetch logic

There's some duplication between the API fetching logic in AojCoursesApiClient.getTasks and AojChallengesApiClient.getTasks. Consider extracting common patterns to the base class if this pattern continues to repeat in future implementations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3df6220 and b52adfd.

📒 Files selected for processing (3)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
  • src/lib/clients/cache.ts (1 hunks)
  • src/lib/clients/http_client.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/clients/http_client.ts
  • src/lib/clients/cache.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/lib/clients/aizu_online_judge.ts (5)
src/lib/clients/cache.ts (2)
  • Cache (8-188)
  • size (37-39)
src/lib/types/contest.ts (2)
  • ContestsForImport (21-21)
  • ContestForImport (13-19)
src/lib/clients/cache_strategy.ts (1)
  • ContestTaskCache (10-81)
src/lib/clients/http_client.ts (2)
  • HttpRequestClient (21-61)
  • TasksApiClient (10-13)
src/lib/constants/urls.ts (1)
  • AOJ_API_BASE_URL (9-9)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: preview
🔇 Additional comments (8)
src/lib/clients/aizu_online_judge.ts (8)

1-10: Well-structured imports with clear organization

The imports have been updated to properly support the new modular architecture, with a clear separation between client components, caching mechanisms, and API constants.


33-70: Strong typing improvements for challenge parameters

The addition of strongly-typed parameters for challenge contests enhances type safety and code self-documentation. The use of mapped types to associate contest types with their corresponding rounds is particularly elegant.


132-163: Good refactoring to a layered client design

The client has been properly refactored to use composition instead of direct implementation. The constructor now correctly initializes caches and specialized clients, maintaining a clean separation of concerns.


173-198: Improved concurrency handling for contest retrieval

The getContests method effectively delegates to specialized clients while maintaining robust error handling through Promise.allSettled. This approach correctly handles failures in individual API calls without breaking the entire operation.


216-241: Consistent implementation pattern between getContests and getTasks

The getTasks method follows the same robust pattern as getContests, providing consistency in the codebase. The error handling and result processing are well implemented.


244-346: Excellent abstract base class implementation

The abstract base class provides a solid foundation with appropriate abstractions. The protected utility methods for endpoint building and data mapping promote code reuse while maintaining encapsulation.

The JSDoc comments are comprehensive and include examples, which is excellent for maintainability.


348-425: Well-implemented courses API client

The AojCoursesApiClient correctly implements the abstract base class, with proper caching and error handling. The getCourseName method is well preserved from the previous implementation.


427-513: Comprehensive challenges API client implementation

The AojChallengesApiClient handles the more complex scenario of parameterized requests correctly. The caching strategy with dynamic cache keys based on contest type and round is well designed.

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

🧹 Nitpick comments (4)
src/lib/clients/aizu_online_judge.ts (4)

71-74: Consider improving type safety for API response

The largeCl property is typed as Record<string, unknown>, which is very generic. If the structure of this object is known, consider creating a more specific type to improve type safety and code clarity.

-  readonly largeCl: Record<string, unknown>;
+  readonly largeCl: {
+    // Add specific properties here based on the actual structure
+  };

363-383: Consider extracting common pattern from fetch methods

The getContests and getTasks methods in both specialized clients follow a similar pattern of using the cache strategy and making API requests. Consider extracting this pattern to a protected method in the base class to reduce duplication.

 // In AojTasksApiClientBase
+protected async fetchWithCache<T>(
+  cacheKey: string,
+  fetchFn: () => Promise<T>,
+  isTasks: boolean
+): Promise<T> {
+  return isTasks 
+    ? await this.cache.getCachedOrFetchTasks(cacheKey, fetchFn) 
+    : await this.cache.getCachedOrFetchContests(cacheKey, fetchFn);
+}

476-512: Consider using a shared method for fetching challenge data

The getTasks method in AojChallengesApiClient duplicates some of the logic from getContests, such as generating the cache key and fetching from the API. Consider extracting a shared method to handle these common operations.

+private async fetchChallengeData<T>(
+  params: ChallengeParams,
+  isTasks: boolean,
+  mapFn: (data: AOJChallengeContestAPI) => T
+): Promise<T> {
+  const { contestType, round } = params;
+  const cacheKey = this.getCacheKey(contestType, round);
+  const contestTypeLabel = this.getContestTypeLabel(contestType);
+  
+  const fetchFunction = async () => {
+    const results = await this.httpClient.fetchApiWithConfig<AOJChallengeContestAPI>({
+      endpoint: this.buildEndpoint(['challenges', 'cl', contestType, round]),
+      errorMessage: `Failed to fetch ${contestTypeLabel} ${round} ${isTasks ? 'tasks' : 'contests'} from AOJ API`,
+      validateResponse: (data) =>
+        'contests' in data && Array.isArray(data.contests) && data.contests.length > 0,
+    });
+    
+    return mapFn(results);
+  };
+  
+  return isTasks 
+    ? await this.cache.getCachedOrFetchTasks(cacheKey, fetchFunction)
+    : await this.cache.getCachedOrFetchContests(cacheKey, fetchFunction);
+}

536-538: Consider simplifying the getContestTypeLabel method

The getContestTypeLabel method simply converts the contest type to uppercase. Since this is a simple operation used in only a few places, consider inlining it instead of having a separate method.

-private getContestTypeLabel(contestType: ChallengeContestType): string {
-  return contestType.toUpperCase();
-}

// Then replace calls with:
const contestTypeLabel = contestType.toUpperCase();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 292a73f and 6cbbf80.

📒 Files selected for processing (1)
  • src/lib/clients/aizu_online_judge.ts (10 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/lib/clients/aizu_online_judge.ts (5)
src/lib/clients/cache.ts (3)
  • ApiClientConfig (232-235)
  • Cache (8-188)
  • size (37-39)
src/lib/types/contest.ts (2)
  • ContestsForImport (21-21)
  • ContestForImport (13-19)
src/lib/clients/cache_strategy.ts (1)
  • ContestTaskCache (10-81)
src/lib/clients/http_client.ts (2)
  • HttpRequestClient (21-61)
  • TasksApiClient (10-13)
src/lib/constants/urls.ts (1)
  • AOJ_API_BASE_URL (9-9)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: preview
🔇 Additional comments (13)
src/lib/clients/aizu_online_judge.ts (13)

1-4: Good refactoring of imports

The imports have been properly updated to reflect the new modular architecture, replacing the direct use of ContestSiteApiClient with the more specialized HttpRequestClient and introducing the new caching components.


9-10: Good use of constants

Moving the base URL to a constants file improves maintainability by centralizing configuration values.


33-42: Well-structured type definitions with proper documentation

The new type definitions for challenge parameters are well-organized and thoroughly documented with JSDoc comments. The use of type mapping in ChallengeRoundMap to associate contest types with their specific round types is a good application of TypeScript's type system.

Also applies to: 49-70


132-135: Good implementation of composition pattern

Transitioning from inheritance to composition by using specialized client classes is a solid architectural improvement that enhances separation of concerns and modularity.


143-163: Well-structured constructor with clear dependency initialization

The constructor properly initializes and injects dependencies into the specialized clients. This approach improves testability and follows dependency injection principles.


173-198: Effective use of Promise.allSettled for parallel fetching

The method appropriately uses Promise.allSettled to fetch contests from multiple sources concurrently, handling errors at both the individual promise level and the overall method level.


216-241: Consistent implementation of getTasks with good error handling

The implementation of getTasks follows the same pattern as getContests, maintaining consistency in the codebase while providing proper error handling at multiple levels.


244-271: Well-designed abstract base class with comprehensive documentation

The AojTasksApiClientBase abstract class is well-documented with clear examples and provides a solid foundation for the concrete implementations. The use of a generic type parameter for the API method parameters is a good practice.


282-306: Robust URL endpoint construction with security validation

The buildEndpoint method includes thorough validation of URL segments to prevent potential security issues. The use of regular expressions to validate segments and the encoding of segments are good security practices.


320-328: Clear mapping functions with well-documented limitations

The mapping functions are concise and include comments explaining the limitations where data is not available from the API. The mapToTask function handles the mapping of problem IDs to task IDs appropriately.

Also applies to: 337-345


377-377: Consistent logging with descriptive messages

The use of console.debug for logging found contests and tasks is consistent across the different client implementations, which is good for maintainability. The messages include the count of items found, making them useful for debugging.

Also applies to: 399-399, 467-467, 506-506


441-474: Well-implemented challenge contest fetching with proper caching

The getContests method in AojChallengesApiClient correctly handles the challenge parameters, generates appropriate cache keys, and uses the cache strategy effectively.


522-527: Good cache key generation

The getCacheKey method creates meaningful and unique cache keys by combining the contest type and round, ensuring that cache entries are properly separated.

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.

問題情報をAPIから取得する処理をリファクタリングしましょう
1 participant