Skip to content

Conversation

@Boxuan-Matty-Lin
Copy link
Owner

This pull request introduces a new, robust backend for fetching and transforming historical exchange rate data, including an API endpoint, supporting library, and comprehensive tests. The main focus is on providing AUD-based historical exchange rates, handling retries for unreliable upstream data, and enabling flexible data orientations for consumers.

Key changes:

New Historical Exchange Rate API

  • Added a new Next.js API route app/api/rates/history/route.ts that serves historical exchange rate data, supports orientation by date or by currency, and applies caching headers for performance.

Core Library for Historical Data

  • Introduced lib/server/oxr_history.ts, which implements:
    • Fetching historical exchange rates with retry logic and concurrency control.
    • Conversion from USD-based rates to AUD-based rows for selected currencies.
    • Transformation utilities to present the data as either date-based rows or per-currency time series.
  • Added a utility in lib/time_utils.ts to generate lists of past UTC dates in ISO format for historical lookups.

Support and Integration

  • Extended the OXR (Open Exchange Rates) integration in lib/server/oxr.ts to support fetching historical rates for a specific date, and defined the HistoricalResponse type.
  • Updated the default currency list in lib/server/oxr_convert.ts to be mutable for compatibility with new utilities.

Testing

  • Added a thorough test suite in lib/server/__tests__/oxr_history.test.ts to validate historical data fetching, error handling, and data transformation logic.
  • Enhanced the integration test in lib/server/__tests__/oxr.integration.test.ts to cover the new historical endpoint and ensure correct data fetching from upstream. [1] [2]

References:
[1] [2] [3] [4] [5] [6] [7] [8]

@Boxuan-Matty-Lin Boxuan-Matty-Lin added documentation Improvements or additions to documentation enhancement New feature or request labels Oct 29, 2025
@github-actions
Copy link

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 64.83% 118 / 182
🔵 Statements 66.16% 131 / 198
🔵 Functions 62.96% 34 / 54
🔵 Branches 32.35% 55 / 170
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
lib/server/oxr.ts 70% 41.66% 100% 77.77%
lib/server/oxr_convert.ts 100% 100% 100% 100%
lib/server/oxr_history.ts 100% 100% 100% 100%
Generated in workflow #17 for commit 2387984 by the Vitest Coverage Report Action

@Boxuan-Matty-Lin Boxuan-Matty-Lin merged commit 7d8ba6a into dev Oct 29, 2025
6 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds historical exchange rate functionality to fetch and transform past currency data from Open Exchange Rates. The implementation includes a new time utilities module, historical data fetching with retry logic and parallel processing, and an API endpoint to expose this data.

  • Implements historical rate fetching with concurrent workers and retry mechanism
  • Adds time utilities for building UTC date ranges
  • Creates a new /api/rates/history endpoint with configurable day ranges and output formats

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
lib/time_utils.ts New utility module for ISO date formatting and generating past UTC date arrays
lib/server/oxr_history.ts Core historical data fetching logic with worker pool, retry handling, and data transformation functions
lib/server/oxr.ts Adds HistoricalResponse type and getHistorical function for fetching historical rates
lib/server/oxr_convert.ts Removes as const from DEFAULTS_CURRENCY to allow runtime mutation
lib/server/tests/oxr_history.test.ts Comprehensive unit tests for historical rate functions with mocking
lib/server/tests/oxr.integration.test.ts Adds integration test for historical endpoint and fixes formatting
app/api/rates/history/route.ts New API route handler for historical rates with caching and orientation options

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.




/** * Shape of Open Exchange Rates historical payload.
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The JSDoc comment has an extra space after the opening /**. It should be /** followed by a single space.

Suggested change
/** * Shape of Open Exchange Rates historical payload.
/** Shape of Open Exchange Rates historical payload.

Copilot uses AI. Check for mistakes.



/** * Fetches historical exchange rates for a specific date from Open Exchange Rates.
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The JSDoc comment has an extra space after the opening /**. It should be /** followed by a single space.

Suggested change
/** * Fetches historical exchange rates for a specific date from Open Exchange Rates.
/**
* Fetches historical exchange rates for a specific date from Open Exchange Rates.

Copilot uses AI. Check for mistakes.
* @returns A promise resolving to the `HistoricalResponse` payload.
*/
export async function getHistorical(dateISO: string): Promise<HistoricalResponse> {
// dateISO = YYYY-MM-DD(UTC)
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The comment contains Chinese/full-width parentheses '(' and ')' which should be replaced with standard ASCII parentheses '(' and ')'.

Suggested change
// dateISO = YYYY-MM-DDUTC
// dateISO = YYYY-MM-DD (UTC)

Copilot uses AI. Check for mistakes.
for (const t of targets) series[t] = [];

for (const row of points) {
const d = row.date; // 已是 string
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The comment is in Chinese ('已是 string' means 'already is string'). Comments should be in English for consistency with the rest of the codebase.

Suggested change
const d = row.date; // 已是 string
const d = row.date; // already a string

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +25
vi.mock("../../time_utils.ts", () => ({
buildPastDatesUTC: (...args: unknown[]) => buildPastDatesUTCMock(...args),
}));
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Duplicate mock for ../../time_utils.ts on lines 19-21 and 23-25. The second mock (with .ts extension) will override the first one. Remove the redundant mock or consolidate them.

Suggested change
vi.mock("../../time_utils.ts", () => ({
buildPastDatesUTC: (...args: unknown[]) => buildPastDatesUTCMock(...args),
}));

Copilot uses AI. Check for mistakes.
* Default currency codes returned when no targets are provided.
*/
export const DEFAULTS_CURRENCY = ["USD", "EUR", "JPY", "GBP", "CNY"] as const;
export const DEFAULTS_CURRENCY = ["USD", "EUR", "JPY", "GBP", "CNY"];
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

Removing as const makes this array mutable, which could lead to unintended modifications at runtime. If immutability is no longer required, consider adding a comment explaining why this change was necessary, or use Object.freeze() to prevent mutation while maintaining assignability.

Suggested change
export const DEFAULTS_CURRENCY = ["USD", "EUR", "JPY", "GBP", "CNY"];
export const DEFAULTS_CURRENCY = ["USD", "EUR", "JPY", "GBP", "CNY"] as const;

Copilot uses AI. Check for mistakes.

vi.mock("../oxr_convert", () => ({
rebaseToAUD: (...args: unknown[]) => rebaseToAUDMock(...args),
DEFAULTS_CURRENCY: ["USD", "EUR", "JPY", "GBP", "CNY"] as const,
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

The mocked DEFAULTS_CURRENCY uses as const while the actual implementation was changed to remove it (line 27 in oxr_convert.ts). This inconsistency could mask issues in tests. Update the mock to match the actual implementation.

Suggested change
DEFAULTS_CURRENCY: ["USD", "EUR", "JPY", "GBP", "CNY"] as const,
DEFAULTS_CURRENCY: ["USD", "EUR", "JPY", "GBP", "CNY"],

Copilot uses AI. Check for mistakes.
const audRates = rebaseToAUD(usdRates);
const row: Record<string, number | string> = { date };
for (const t of targets) if (audRates[t] != null) row[t] = audRates[t];
return row as { date: string; [code: string]: number | string };
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

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

[nitpick] The type assertion is unnecessary since row is already typed as Record<string, number | string> which is compatible with the return type. The explicit cast can be removed for cleaner code.

Suggested change
return row as { date: string; [code: string]: number | string };
return row;

Copilot uses AI. Check for mistakes.
@Boxuan-Matty-Lin Boxuan-Matty-Lin linked an issue Oct 29, 2025 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create API for History Data

2 participants