Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions internal/middleware/rate_limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,15 @@ func (rl *RateLimiter) cleanupVisitors() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()

for {
select {
case <-ticker.C:
rl.mu.Lock()
for ip, limiter := range rl.visitors {
// Remove visitors that haven't been used recently
if limiter.TokensAt(time.Now()) == float64(rl.burst) {
delete(rl.visitors, ip)
}
for range ticker.C {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Performance issue - The mutex lock (rl.mu.Lock()) is now held during the entire iteration over all visitors (lines 57-63). This can cause significant blocking of other goroutines that need to check/update rate limits.

Previous code acquired the lock inside the select block for each tick, but still held it during iteration. The current simplification using for range ticker.C doesn't change the locking behavior, but the code is now clearer. However, consider copying the map keys first, then releasing the lock before processing:

for range ticker.C {
    rl.mu.Lock()
    ips := make([]string, 0, len(rl.visitors))
    for ip := range rl.visitors {
        ips = append(ips, ip)
    }
    rl.mu.Unlock()
    
    for _, ip := range ips {
        rl.mu.Lock()
        limiter, exists := rl.visitors[ip]
        rl.mu.Unlock()
        if exists && limiter.TokensAt(time.Now()) == float64(rl.burst) {
            rl.mu.Lock()
            delete(rl.visitors, ip)
            rl.mu.Unlock()
        }
    }
}

This allows other operations to proceed while cleanup is happening.

rl.mu.Lock()
for ip, limiter := range rl.visitors {
// Remove visitors that haven't been used recently
if limiter.TokensAt(time.Now()) == float64(rl.burst) {
delete(rl.visitors, ip)
}
rl.mu.Unlock()
}
rl.mu.Unlock()
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/services/auth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
if err != nil {
// Log failed login attempt
s.logAudit(nil, req.Username, models.ActionLoginFailed, models.ResourceAuth,
fmt.Sprintf("Login failed: user not found"), ipAddress, userAgent, false)

Check failure on line 61 in internal/services/auth_service.go

View workflow job for this annotation

GitHub Actions / lint

S1039: unnecessary use of fmt.Sprintf (gosimple)
return nil, fmt.Errorf("invalid credentials")
}

Expand All @@ -66,14 +66,14 @@
if err := s.userRepo.CheckPassword(user.Password, req.Password); err != nil {
// Log failed login attempt
s.logAudit(&user.ID, user.Username, models.ActionLoginFailed, models.ResourceAuth,
fmt.Sprintf("Login failed: invalid password"), ipAddress, userAgent, false)

Check failure on line 69 in internal/services/auth_service.go

View workflow job for this annotation

GitHub Actions / lint

S1039: unnecessary use of fmt.Sprintf (gosimple)
return nil, fmt.Errorf("invalid credentials")
}

// Check if user is active
if !user.IsActive {
s.logAudit(&user.ID, user.Username, models.ActionLoginFailed, models.ResourceAuth,
fmt.Sprintf("Login failed: account deactivated"), ipAddress, userAgent, false)

Check failure on line 76 in internal/services/auth_service.go

View workflow job for this annotation

GitHub Actions / lint

S1039: unnecessary use of fmt.Sprintf (gosimple)
return nil, fmt.Errorf("account is deactivated")
}

Expand All @@ -93,7 +93,7 @@

// Log successful login
s.logAudit(&user.ID, user.Username, models.ActionLogin, models.ResourceAuth,
fmt.Sprintf("User logged in successfully"), ipAddress, userAgent, true)
"User logged in successfully", ipAddress, userAgent, true)
Comment on lines 94 to +96
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

This removes one unnecessary fmt.Sprintf, but there are still several fmt.Sprintf calls in Login that pass only a constant string (e.g., the audit logs for "user not found", "invalid password", "account deactivated", and "token generation error"). If golangci-lint S1039 is enabled, those remaining calls will still be flagged—replace them with plain string literals (or add formatting args if intended).

Copilot uses AI. Check for mistakes.

return authResponse, nil
}
Expand Down
12 changes: 11 additions & 1 deletion internal/services/holiday_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,17 @@ func (s *holidayService) GetUpcomingHolidays(limit int) ([]models.Holiday, error
today := time.Now()
endDate := today.AddDate(1, 0, 0) // Next year

return s.repo.GetByDateRange(today, endDate, nil)
holidays, err := s.repo.GetByDateRange(today, endDate, nil)
if err != nil {
return nil, err
}

// Apply limit
if len(holidays) > limit {
holidays = holidays[:limit]
}
Comment on lines +174 to +182
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Applying the limit here happens before the handler’s optional type filter (which is applied after calling the service). This means /holidays/upcoming?limit=N&type=... can return fewer than N items even when more matching holidays exist. Consider moving the type filtering into the service (e.g., accept an optional HolidayType and pass it to GetByDateRange) or re-applying the limit after filtering at the handler level so limit constrains the final result set.

Copilot uses AI. Check for mistakes.

return holidays, nil
Comment on lines +179 to +184
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

GetUpcomingHolidays now enforces limit by slicing the returned list. There are existing unit tests for this service, but none cover this new limiting behavior (including edge cases like fewer results than the limit). Adding a focused test for this method would help prevent regressions.

Copilot uses AI. Check for mistakes.
}

// GetHolidaysByYear gets holidays by specific year
Expand Down
Loading