feat: v0.4.4 - Enhanced Currency Support & Renewal Date Fixes #46

Merged
bscott merged 12 commits from v0.4.4 into main 2025-09-26 17:02:56 -04:00
bscott commented 2025-09-14 15:51:27 -04:00 (Migrated from github.com)

🎯 Overview

This release introduces comprehensive currency support and fixes critical renewal date calculation issues, along with extensive testing improvements.

New Features

🌍 Currency Conversion System

  • Real-time Exchange Rates: Integration with Fixer.io API (completely optional)
  • Multi-Currency Support: USD, EUR, GBP, JPY, RUB, SEK, PLN, INR
  • Currency Settings Page: Configure preferred base currency
  • Smart Cross-Rate Calculations: Automatic EUR routing for free API limitations

📅 Enhanced Date Handling

  • Robust Renewal Calculations: Fixed core issues with date calculation
  • Month-End Edge Cases: Proper handling of Jan 31, Jul 31 edge cases
  • Leap Year Support: Accurate Feb 29 date arithmetic
  • Versioned System: V1/V2 calculation methods with safe migration path

🛠️ Migration & Safety Tools

  • Command-Line Migration Utility: Safe V1→V2 date calculation upgrades
  • Comprehensive Audit Logging: Track all date calculation changes
  • Rollback Capability: Emergency rollback to V1 calculations
  • Dry-Run Mode: Test migrations without data changes

🐛 Bug Fixes

  • Renewal Date Calculation: Fixed issue where dates weren't calculated for new subscriptions
  • GORM Hooks: Proper BeforeCreate/BeforeUpdate execution for date calculations
  • API Response Format: Fixed endpoints returning HTML instead of JSON
  • Schedule Changes: Proper renewal date recalculation when editing schedules

🧪 Testing Improvements

  • Test Coverage: Increased from 39.7% to 55.6%
  • Comprehensive Test Suite: 90+ new test cases covering edge cases
  • Model Tests: Complete coverage of subscription date logic
  • Service Tests: Currency conversion integration testing
  • Edge Case Validation: Month-end dates, leap years, timezone consistency

🔧 Technical Enhancements

  • Carbon Date Library: Integrated robust date arithmetic (AddMonthsNoOverflow, AddYearsNoOverflow)
  • Database Migrations: Enhanced schema update system
  • Performance Optimizations: Improved database queries and caching
  • Error Handling: Better validation and error reporting

🔑 Important Notes

Currency API is Completely Optional

  • SubTrackr works fully without any external dependencies
  • Fixer.io API only adds real-time conversion features
  • Basic multi-currency support works without API key

Database Changes

  • New exchange_rates table (auto-created)
  • New date_calculation_version column (default: 1)
  • All existing data remains unchanged and fully compatible
  • Backup recommended before upgrading

🙏 Thanks to Contributors

  • @amitsingh-007 for contributing INR currency support in PR #45

📊 Validation Results

Real-World Testing:

  • Jan 31 → Sep 30 (month-end preservation)
  • Feb 29 → Mar 1 (leap year handling)
  • July 31 → Sep 30 (current date context)
  • Schedule changes (Monthly ↔ Annual)
  • Multi-currency subscriptions (USD, EUR)

Unit Tests: All 17 test suites passing

  • Edge case date calculations
  • Currency conversion logic
  • GORM hooks and database operations
  • Migration safety utilities

🚀 Ready for Production

This release has been thoroughly tested with comprehensive edge case validation and is ready for production deployment.


Full Changelog: https://github.com/bscott/subtrackr/compare/v0.4.3...v0.4.4

## 🎯 Overview This release introduces comprehensive currency support and fixes critical renewal date calculation issues, along with extensive testing improvements. ## ✨ New Features ### 🌍 **Currency Conversion System** - **Real-time Exchange Rates**: Integration with Fixer.io API (completely optional) - **Multi-Currency Support**: USD, EUR, GBP, JPY, RUB, SEK, PLN, INR - **Currency Settings Page**: Configure preferred base currency - **Smart Cross-Rate Calculations**: Automatic EUR routing for free API limitations ### 📅 **Enhanced Date Handling** - **Robust Renewal Calculations**: Fixed core issues with date calculation - **Month-End Edge Cases**: Proper handling of Jan 31, Jul 31 edge cases - **Leap Year Support**: Accurate Feb 29 date arithmetic - **Versioned System**: V1/V2 calculation methods with safe migration path ### 🛠️ **Migration & Safety Tools** - **Command-Line Migration Utility**: Safe V1→V2 date calculation upgrades - **Comprehensive Audit Logging**: Track all date calculation changes - **Rollback Capability**: Emergency rollback to V1 calculations - **Dry-Run Mode**: Test migrations without data changes ## 🐛 Bug Fixes - **Renewal Date Calculation**: Fixed issue where dates weren't calculated for new subscriptions - **GORM Hooks**: Proper BeforeCreate/BeforeUpdate execution for date calculations - **API Response Format**: Fixed endpoints returning HTML instead of JSON - **Schedule Changes**: Proper renewal date recalculation when editing schedules ## 🧪 Testing Improvements - **Test Coverage**: Increased from 39.7% to 55.6% - **Comprehensive Test Suite**: 90+ new test cases covering edge cases - **Model Tests**: Complete coverage of subscription date logic - **Service Tests**: Currency conversion integration testing - **Edge Case Validation**: Month-end dates, leap years, timezone consistency ## 🔧 Technical Enhancements - **Carbon Date Library**: Integrated robust date arithmetic (`AddMonthsNoOverflow`, `AddYearsNoOverflow`) - **Database Migrations**: Enhanced schema update system - **Performance Optimizations**: Improved database queries and caching - **Error Handling**: Better validation and error reporting ## 🔑 **Important Notes** ### **Currency API is Completely Optional** - SubTrackr works fully without any external dependencies - Fixer.io API only adds real-time conversion features - Basic multi-currency support works without API key ### **Database Changes** - New `exchange_rates` table (auto-created) - New `date_calculation_version` column (default: 1) - All existing data remains unchanged and fully compatible - **Backup recommended** before upgrading ## 🙏 **Thanks to Contributors** - @amitsingh-007 for contributing INR currency support in PR #45 ## 📊 **Validation Results** ### **Real-World Testing**: - ✅ Jan 31 → Sep 30 (month-end preservation) - ✅ Feb 29 → Mar 1 (leap year handling) - ✅ July 31 → Sep 30 (current date context) - ✅ Schedule changes (Monthly ↔ Annual) - ✅ Multi-currency subscriptions (USD, EUR) ### **Unit Tests**: All 17 test suites passing - Edge case date calculations - Currency conversion logic - GORM hooks and database operations - Migration safety utilities ## 🚀 **Ready for Production** This release has been thoroughly tested with comprehensive edge case validation and is ready for production deployment. --- **Full Changelog**: https://github.com/bscott/subtrackr/compare/v0.4.3...v0.4.4
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 15:55:30 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

This release introduces comprehensive currency support with optional real-time exchange rates and fixes critical renewal date calculation issues. The PR adds multi-currency support for 8 currencies (USD, EUR, GBP, JPY, RUB, SEK, PLN, INR), implements a versioned date calculation system using the Carbon library for better edge case handling, and provides migration tools for safe upgrades.

Key Changes:

  • Multi-Currency System: Added currency conversion support with optional Fixer.io API integration and smart cross-rate calculations
  • Enhanced Date Calculations: Implemented versioned date calculation system (V1/V2) with Carbon library for robust month-end and leap year handling
  • Migration Infrastructure: Added comprehensive audit logging, migration utilities, and rollback capabilities for safe date calculation upgrades

Reviewed Changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
templates/subscription-list.html Added currency conversion display with original/converted amounts
templates/subscription-form.html Added currency selection dropdown and JavaScript renewal date calculator
templates/settings.html Added INR currency option to settings
templates/dashboard.html Added currency conversion display for dashboard subscriptions
internal/service/settings.go Added INR support to valid currencies and currency symbol mapping
internal/service/currency_integration_test.go Added comprehensive integration tests for currency service functionality
internal/service/currency.go New currency service with Fixer.io API integration and cross-rate calculations
internal/repository/subscription.go Enhanced subscription update logic with currency field support
internal/repository/exchange_rate.go New repository for exchange rate data management
internal/models/subscription_test.go Extensive test suite for subscription model date calculations and edge cases
internal/models/subscription.go Enhanced subscription model with currency fields and versioned date calculation
internal/models/exchange_rate_test.go Tests for exchange rate staleness detection
internal/models/exchange_rate.go New exchange rate model with staleness checking
internal/models/date_migration_audit_test.go Tests for date migration safety and audit functionality
internal/models/date_migration_audit.go New audit system for tracking date calculation migrations
internal/handlers/subscription.go Enhanced subscription handlers with currency conversion support
internal/database/migrations.go Added currency and date calculation versioning migrations
go.mod Added Carbon date library and testing dependencies
cmd/server/main.go Integrated currency service into application setup
cmd/migrate-dates/main.go New command-line utility for safe date calculation migrations
README.md Updated documentation with currency support information
Comments suppressed due to low confidence (1)

templates/subscription-form.html:1

  • Line 224 is a duplicate of line 223. This console.log statement should be removed to avoid redundant logging.
<div class="p-6">

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview This release introduces comprehensive currency support with optional real-time exchange rates and fixes critical renewal date calculation issues. The PR adds multi-currency support for 8 currencies (USD, EUR, GBP, JPY, RUB, SEK, PLN, INR), implements a versioned date calculation system using the Carbon library for better edge case handling, and provides migration tools for safe upgrades. ### Key Changes: - **Multi-Currency System**: Added currency conversion support with optional Fixer.io API integration and smart cross-rate calculations - **Enhanced Date Calculations**: Implemented versioned date calculation system (V1/V2) with Carbon library for robust month-end and leap year handling - **Migration Infrastructure**: Added comprehensive audit logging, migration utilities, and rollback capabilities for safe date calculation upgrades ### Reviewed Changes Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | templates/subscription-list.html | Added currency conversion display with original/converted amounts | | templates/subscription-form.html | Added currency selection dropdown and JavaScript renewal date calculator | | templates/settings.html | Added INR currency option to settings | | templates/dashboard.html | Added currency conversion display for dashboard subscriptions | | internal/service/settings.go | Added INR support to valid currencies and currency symbol mapping | | internal/service/currency_integration_test.go | Added comprehensive integration tests for currency service functionality | | internal/service/currency.go | New currency service with Fixer.io API integration and cross-rate calculations | | internal/repository/subscription.go | Enhanced subscription update logic with currency field support | | internal/repository/exchange_rate.go | New repository for exchange rate data management | | internal/models/subscription_test.go | Extensive test suite for subscription model date calculations and edge cases | | internal/models/subscription.go | Enhanced subscription model with currency fields and versioned date calculation | | internal/models/exchange_rate_test.go | Tests for exchange rate staleness detection | | internal/models/exchange_rate.go | New exchange rate model with staleness checking | | internal/models/date_migration_audit_test.go | Tests for date migration safety and audit functionality | | internal/models/date_migration_audit.go | New audit system for tracking date calculation migrations | | internal/handlers/subscription.go | Enhanced subscription handlers with currency conversion support | | internal/database/migrations.go | Added currency and date calculation versioning migrations | | go.mod | Added Carbon date library and testing dependencies | | cmd/server/main.go | Integrated currency service into application setup | | cmd/migrate-dates/main.go | New command-line utility for safe date calculation migrations | | README.md | Updated documentation with currency support information | </details> <details> <summary>Comments suppressed due to low confidence (1)</summary> **templates/subscription-form.html:1** * Line 224 is a duplicate of line 223. This console.log statement should be removed to avoid redundant logging. ``` <div class="p-6"> ``` </details> --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -94,0 +284,4 @@
targetDay := startDay
if startDay > lastDay {
targetDay = lastDay
}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 15:55:30 -04:00

This complex manual date arithmetic for handling month-end dates duplicates logic that could be simplified. Consider extracting this logic into a helper function since it's repeated in the default case (lines 257-286) and could benefit from unit testing in isolation.

This complex manual date arithmetic for handling month-end dates duplicates logic that could be simplified. Consider extracting this logic into a helper function since it's repeated in the default case (lines 257-286) and could benefit from unit testing in isolation.
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 15:55:29 -04:00

The API URL uses HTTP instead of HTTPS, which exposes the API key in plain text during transmission. This should be changed to 'https://data.fixer.io/api/latest' to ensure secure transmission of the API key.

	url := fmt.Sprintf("https://data.fixer.io/api/latest?access_key=%s&base=EUR&symbols=%s",
The API URL uses HTTP instead of HTTPS, which exposes the API key in plain text during transmission. This should be changed to 'https://data.fixer.io/api/latest' to ensure secure transmission of the API key. ```suggestion url := fmt.Sprintf("https://data.fixer.io/api/latest?access_key=%s&base=EUR&symbols=%s", ```
@ -158,4 +174,75 @@
</button>
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 15:55:29 -04:00

Using setMonth() can cause issues with month-end dates. For example, if today is January 31st and you add 1 month, JavaScript will overflow to March 3rd instead of February 28th. This doesn't match the robust date handling implemented in the backend Carbon library.

        case 'Monthly': {
            // Robust month addition: handle month-end dates
            const targetMonth = today.getMonth() + 1;
            const targetYear = today.getFullYear() + (targetMonth > 11 ? 1 : 0);
            const normalizedMonth = targetMonth % 12;
            // Get last day of target month
            const lastDayOfTargetMonth = new Date(targetYear, normalizedMonth + 1, 0).getDate();
            const day = Math.min(today.getDate(), lastDayOfTargetMonth);
            renewalDate = new Date(targetYear, normalizedMonth, day);
            break;
        }
Using setMonth() can cause issues with month-end dates. For example, if today is January 31st and you add 1 month, JavaScript will overflow to March 3rd instead of February 28th. This doesn't match the robust date handling implemented in the backend Carbon library. ```suggestion case 'Monthly': { // Robust month addition: handle month-end dates const targetMonth = today.getMonth() + 1; const targetYear = today.getFullYear() + (targetMonth > 11 ? 1 : 0); const normalizedMonth = targetMonth % 12; // Get last day of target month const lastDayOfTargetMonth = new Date(targetYear, normalizedMonth + 1, 0).getDate(); const day = Math.min(today.getDate(), lastDayOfTargetMonth); renewalDate = new Date(targetYear, normalizedMonth, day); break; } ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:01:12 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -73,24 +75,274 @@ func (s *Subscription) IsHighCost() bool {
func (s *Subscription) BeforeCreate(tx *gorm.DB) error {
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:01:12 -04:00

The comment should specify what 'backward compatibility' means in this context - that existing subscriptions will continue using the original date calculation logic unless explicitly migrated to V2.

		// Default to V1 for backward compatibility:
		// Existing subscriptions will continue using the original date calculation logic (V1)
		// unless explicitly migrated to V2 by setting DateCalculationVersion = 2.
The comment should specify what 'backward compatibility' means in this context - that existing subscriptions will continue using the original date calculation logic unless explicitly migrated to V2. ```suggestion // Default to V1 for backward compatibility: // Existing subscriptions will continue using the original date calculation logic (V1) // unless explicitly migrated to V2 by setting DateCalculationVersion = 2. ```
@ -0,0 +1,982 @@
package models
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:01:12 -04:00

This assertion will fail for month-end dates like January 31st when the renewal falls in February (28/29 days). The test should account for month-end adjustments in date calculations.

					// Should be on the same day of month as start date, unless start date is month-end
					startYear, startMonth, _ := tt.startDate.Date()
					renewalYear, renewalMonth, _ := sub.RenewalDate.Date()
					startLastDay := time.Date(startYear, startMonth+1, 0, 0, 0, 0, 0, tt.startDate.Location()).Day()
					renewalLastDay := time.Date(renewalYear, renewalMonth+1, 0, 0, 0, 0, 0, sub.RenewalDate.Location()).Day()
					if tt.startDate.Day() == startLastDay {
						assert.Equal(t, renewalLastDay, sub.RenewalDate.Day(), "Renewal date should be last day of month if start date was")
					} else {
						assert.Equal(t, tt.startDate.Day(), sub.RenewalDate.Day())
					}
This assertion will fail for month-end dates like January 31st when the renewal falls in February (28/29 days). The test should account for month-end adjustments in date calculations. ```suggestion // Should be on the same day of month as start date, unless start date is month-end startYear, startMonth, _ := tt.startDate.Date() renewalYear, renewalMonth, _ := sub.RenewalDate.Date() startLastDay := time.Date(startYear, startMonth+1, 0, 0, 0, 0, 0, tt.startDate.Location()).Day() renewalLastDay := time.Date(renewalYear, renewalMonth+1, 0, 0, 0, 0, 0, sub.RenewalDate.Location()).Day() if tt.startDate.Day() == startLastDay { assert.Equal(t, renewalLastDay, sub.RenewalDate.Day(), "Renewal date should be last day of month if start date was") } else { assert.Equal(t, tt.startDate.Day(), sub.RenewalDate.Day()) } ```
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:01:12 -04:00

The supported currencies list is hardcoded as a string. Consider extracting this to a constant or configuration to improve maintainability and consistency with the settings service validation.

The supported currencies list is hardcoded as a string. Consider extracting this to a constant or configuration to improve maintainability and consistency with the settings service validation.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:05:45 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

internal/service/currency.go:1

  • The supported currencies are hardcoded as a string constant within the function. This creates a magic string that should be defined as a package-level constant or made configurable to improve maintainability.
package service

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments. <details> <summary>Comments suppressed due to low confidence (1)</summary> **internal/service/currency.go:1** * The supported currencies are hardcoded as a string constant within the function. This creates a magic string that should be defined as a package-level constant or made configurable to improve maintainability. ``` package service ``` </details> --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -262,6 +346,7 @@ func (h *SubscriptionHandler) UpdateSubscription(c *gin.Context) {
}
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:05:44 -04:00

The handler fetches the existing subscription to check for schedule changes, but this adds an extra database query to every update operation. Consider optimizing this by checking for schedule changes in the service layer or using a more efficient approach.

	// Always parse renewal date if provided; let service/model layer handle schedule change logic
	if renewalDateStr := c.PostForm("renewal_date"); renewalDateStr != "" {
		if renewalDate, err := time.Parse("2006-01-02", renewalDateStr); err == nil {
			subscription.RenewalDate = &renewalDate
The handler fetches the existing subscription to check for schedule changes, but this adds an extra database query to every update operation. Consider optimizing this by checking for schedule changes in the service layer or using a more efficient approach. ```suggestion // Always parse renewal date if provided; let service/model layer handle schedule change logic if renewalDateStr := c.PostForm("renewal_date"); renewalDateStr != "" { if renewalDate, err := time.Parse("2006-01-02", renewalDateStr); err == nil { subscription.RenewalDate = &renewalDate ```
@ -73,24 +75,274 @@ func (s *Subscription) IsHighCost() bool {
func (s *Subscription) BeforeCreate(tx *gorm.DB) error {
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:05:44 -04:00

The long comment within the switch statement breaks readability. Consider moving this explanation to the function documentation or using a shorter inline comment.

// calculateNextRenewalDate calculates the next renewal date based on schedule and version.
// By default, subscriptions use the original date calculation logic (V1) for backward compatibility.
// Existing subscriptions will continue using V1 unless explicitly migrated to V2 by setting DateCalculationVersion = 2.
func (s *Subscription) calculateNextRenewalDate() {
	// Use versioned calculation approach
	switch s.DateCalculationVersion {
	case 2:
		s.calculateNextRenewalDateV2()
	default:
		// Use V1 logic for backward compatibility
The long comment within the switch statement breaks readability. Consider moving this explanation to the function documentation or using a shorter inline comment. ```suggestion // calculateNextRenewalDate calculates the next renewal date based on schedule and version. // By default, subscriptions use the original date calculation logic (V1) for backward compatibility. // Existing subscriptions will continue using V1 unless explicitly migrated to V2 by setting DateCalculationVersion = 2. func (s *Subscription) calculateNextRenewalDate() { // Use versioned calculation approach switch s.DateCalculationVersion { case 2: s.calculateNextRenewalDateV2() default: // Use V1 logic for backward compatibility ```
@ -39,16 +39,16 @@ func (r *SubscriptionRepository) Create(subscription *models.Subscription) (*mod
err := r.db.Transaction(func(tx *gorm.DB) error {
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:05:44 -04:00

The SQL INSERT statement uses positional parameters (?) which makes it fragile and hard to maintain. Consider using named parameters or splitting this into smaller, more manageable parts.

The SQL INSERT statement uses positional parameters (?) which makes it fragile and hard to maintain. Consider using named parameters or splitting this into smaller, more manageable parts.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:10:01 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:10:00 -04:00

The HTTP client lacks timeout configuration, which could lead to indefinite blocking. Consider using http.Client with a reasonable timeout or context.WithTimeout for API calls.

	client := &http.Client{
		Timeout: 10 * time.Second,
	}
	resp, err := client.Get(url)
The HTTP client lacks timeout configuration, which could lead to indefinite blocking. Consider using http.Client with a reasonable timeout or context.WithTimeout for API calls. ```suggestion client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:10:01 -04:00

Using fmt.Printf for logging in production code is not ideal. Consider using a proper logging library like log.Printf or a structured logger for consistent log formatting and levels.

Using fmt.Printf for logging in production code is not ideal. Consider using a proper logging library like log.Printf or a structured logger for consistent log formatting and levels.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:15:49 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:15:49 -04:00

The supported currency symbols are hardcoded in multiple places (here and in settings.go). Consider extracting this to a shared constant or configuration to avoid duplication and ensure consistency.

	"subtrackr/internal/settings"
)

// Supported currencies for exchange rate fetching
// Use settings.SupportedCurrencies for consistency
The supported currency symbols are hardcoded in multiple places (here and in settings.go). Consider extracting this to a shared constant or configuration to avoid duplication and ensure consistency. ```suggestion "subtrackr/internal/settings" ) // Supported currencies for exchange rate fetching // Use settings.SupportedCurrencies for consistency ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:21:03 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -14,15 +14,82 @@ import (
"github.com/gin-gonic/gin"
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:21:03 -04:00

[nitpick] The comment mentions avoiding pointer to loop variable issue, but this pattern is correct for Go 1.22+. Consider updating the comment to reflect that this creates a copy for modification rather than addressing a loop variable issue.

		// Create a copy of the subscription for modification; this pattern is correct for Go 1.22+
[nitpick] The comment mentions avoiding pointer to loop variable issue, but this pattern is correct for Go 1.22+. Consider updating the comment to reflect that this creates a copy for modification rather than addressing a loop variable issue. ```suggestion // Create a copy of the subscription for modification; this pattern is correct for Go 1.22+ ```
@ -94,0 +180,4 @@
// Default to monthly
current := start.Copy()
for current.Lte(now) {
current = current.AddMonthsNoOverflow(1)
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:21:03 -04:00

[nitpick] The versioned calculation approach should document which scenarios trigger V2 vs V1 logic. Consider adding a comment explaining when DateCalculationVersion would be 2 vs default/1.

[nitpick] The versioned calculation approach should document which scenarios trigger V2 vs V1 logic. Consider adding a comment explaining when DateCalculationVersion would be 2 vs default/1.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:22:56 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

internal/models/subscription_test.go:1

  • The comment about Go 1.22+ compatibility is outdated since this pattern has been valid in Go for much longer. The comment should be removed or corrected.
package models

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment. <details> <summary>Comments suppressed due to low confidence (1)</summary> **internal/models/subscription_test.go:1** * The comment about Go 1.22+ compatibility is outdated since this pattern has been valid in Go for much longer. The comment should be removed or corrected. ``` package models ``` </details> --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -0,0 +1,167 @@
package main
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:22:56 -04:00

Consider using v2Date.Sub(*v1Date).Truncate(24*time.Hour).Hours()/24 to get more accurate day differences instead of floating-point division which may introduce precision errors.

			diff := v2Date.Sub(*v1Date).Truncate(24*time.Hour).Hours() / 24
Consider using `v2Date.Sub(*v1Date).Truncate(24*time.Hour).Hours()/24` to get more accurate day differences instead of floating-point division which may introduce precision errors. ```suggestion diff := v2Date.Sub(*v1Date).Truncate(24*time.Hour).Hours() / 24 ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:24:13 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -0,0 +119,4 @@
v2Str = v2Date.Format("2006-01-02")
}
if v1Date != nil && v2Date != nil {
diff := v2Date.Sub(*v1Date).Truncate(24*time.Hour).Hours() / 24
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:24:13 -04:00

Missing import for the time package. The code uses time.Hour but time is not imported in the file.

Missing import for the `time` package. The code uses `time.Hour` but `time` is not imported in the file.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:30:43 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -49,1 +46,4 @@
subscription.Name, subscription.Cost, subscription.Schedule,
subscription.Status, subscription.CategoryID, category.Name, subscription.OriginalCurrency,
subscription.PaymentMethod, subscription.Account,
subscription.StartDate, subscription.RenewalDate,
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:30:43 -04:00

There's a mismatch between the number of columns (7 in the INSERT statement) and the number of placeholders (17). The VALUES clause has too many placeholders for the columns specified.

There's a mismatch between the number of columns (7 in the INSERT statement) and the number of placeholders (17). The VALUES clause has too many placeholders for the columns specified.
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:30:43 -04:00

The HTTP client should validate SSL certificates and handle potential malicious redirects. Consider using a more restrictive HTTP client configuration or validating the response URL matches the expected domain.

The HTTP client should validate SSL certificates and handle potential malicious redirects. Consider using a more restrictive HTTP client configuration or validating the response URL matches the expected domain.
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:41:48 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:41:48 -04:00

The comment 'Fetch all supported currencies' is misleading. The function is called with 'USD' as targetCurrency but fetches rates for all supported currencies with EUR as base. Consider updating the comment to reflect the actual behavior.

		_, err := s.fetchAndCacheRates(base, "USD") // Fetch rates for all supported currencies with base currency 'base'
The comment 'Fetch all supported currencies' is misleading. The function is called with 'USD' as targetCurrency but fetches rates for all supported currencies with EUR as base. Consider updating the comment to reflect the actual behavior. ```suggestion _, err := s.fetchAndCacheRates(base, "USD") // Fetch rates for all supported currencies with base currency 'base' ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:52:29 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 23 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 23 changed files in this pull request and generated 2 comments. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:52:29 -04:00

The comment '// (Line removed)' on the deleted line indicates incomplete cleanup. This creates confusion about what was actually removed and should be cleaned up.

The comment '// (Line removed)' on the deleted line indicates incomplete cleanup. This creates confusion about what was actually removed and should be cleaned up.
@ -0,0 +1,208 @@
package service
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:52:29 -04:00

The comment states 'Fetch rates for all supported currencies with base currency' but the function is called with 'USD' as the target currency for all base currencies. This doesn't actually fetch rates for all supported currencies - it only fetches the base-to-USD rate.

		for _, target := range SupportedCurrencies {
			if target == base {
				continue
			}
			_, err := s.fetchAndCacheRates(base, target) // Fetch rate for base->target
			if err != nil {
				return fmt.Errorf("failed to refresh rates for %s to %s: %w", base, target, err)
			}
The comment states 'Fetch rates for all supported currencies with base currency' but the function is called with 'USD' as the target currency for all base currencies. This doesn't actually fetch rates for all supported currencies - it only fetches the base-to-USD rate. ```suggestion for _, target := range SupportedCurrencies { if target == base { continue } _, err := s.fetchAndCacheRates(base, target) // Fetch rate for base->target if err != nil { return fmt.Errorf("failed to refresh rates for %s to %s: %w", base, target, err) } ```
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2025-09-14 16:56:17 -04:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment

Pull Request Overview

Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

## Pull Request Overview Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment. --- <sub>**Tip:** Customize your code reviews with copilot-instructions.md. <a href="/bscott/subtrackr/new/main/.github?filename=copilot-instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Create the file</a> or <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">learn how to get started</a>.</sub>
@ -0,0 +19,4 @@
// supportedCurrencySymbols returns the currencies as a comma-separated string for API calls
func supportedCurrencySymbols() string {
return strings.Join(SupportedCurrencies, ",")
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2025-09-14 16:56:17 -04:00

The SupportedCurrencies variable is defined in the service package but is also used in the settings service. Consider moving this to a shared package or constants file to avoid potential import cycles and improve maintainability.

	"subtrackr/internal/constants"
)

// SupportedCurrencies is now defined in the constants package.


// supportedCurrencySymbols returns the currencies as a comma-separated string for API calls
func supportedCurrencySymbols() string {
	return strings.Join(constants.SupportedCurrencies, ",")
The SupportedCurrencies variable is defined in the service package but is also used in the settings service. Consider moving this to a shared package or constants file to avoid potential import cycles and improve maintainability. ```suggestion "subtrackr/internal/constants" ) // SupportedCurrencies is now defined in the constants package. // supportedCurrencySymbols returns the currencies as a comma-separated string for API calls func supportedCurrencySymbols() string { return strings.Join(constants.SupportedCurrencies, ",") ```
bscott commented 2025-09-26 15:32:25 -04:00 (Migrated from github.com)

@CyberL1 @amitsingh-007 Would you mind if you have the time to review this PR, before I merge. Just want some human eyes instead of an automated code review at this time, quite a bit of change in this one, I tested locally a few times with optimizations coming later. This PR should resolve #43 & #44

@CyberL1 @amitsingh-007 Would you mind if you have the time to review this PR, before I merge. Just want some human eyes instead of an automated code review at this time, quite a bit of change in this one, I tested locally a few times with optimizations coming later. This PR should resolve #43 & #44
amitsingh-007 (Migrated from github.com) approved these changes 2025-09-26 15:38:11 -04:00
amitsingh-007 (Migrated from github.com) left a comment

Reviewed INR currency support. LGTM

Reviewed INR currency support. LGTM
CyberL1 (Migrated from github.com) approved these changes 2025-09-26 16:37:15 -04:00
CyberL1 (Migrated from github.com) left a comment

Renewal date updates as requested

Renewal date updates as requested
bscott commented 2025-09-26 17:02:31 -04:00 (Migrated from github.com)

Thank you both, you rock!

Thank you both, you rock!
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bscott/subtrackr!46
No description provided.