Files
trip-planner/internal/cache/preferences.go
Vladimir Zagainov 6dcec6a7a5 fix: address code review findings
- Fix type mismatch in search_cache.go: SearchWithCache now returns *yandex.Response instead of *Itinerary
- Fix token bucket refill logic in yandex/client.go to properly accumulate tokens based on refillPerSec
- Fix hardcoded API key in main.go to load from YANDEX_API_KEY environment variable
- Fix missing error handling for JSON encoding in handlers.go RouteGeoJSON function
- Fix incorrect redis.Nil handling in cache/store.go CacheAside.Exists method
- Fix incorrect error return type in station_status.go updateStationStatus to return newStatus instead of empty string
2026-08-17 22:42:08 +03:00

269 lines
5.6 KiB
Go

package cache
import (
"context"
"encoding/json"
"time"
)
// PreferenceKey defines the structure for preference cache keys.
type PreferenceKey struct {
UserID string // user identifier
Kind string // "saved_city" or "search_history"
CityCode string // city code for saved_city
}
// PreferenceSavedCity represents a user's saved city preference.
type PreferenceSavedCity struct {
CityCode string `json:"city_code"`
Name string `json:"name"`
}
// PreferenceSearchHistory represents a user's search history entry.
type PreferenceSearchHistory struct {
Query string `json:"query"`
FromCity string `json:"from_city"`
ToCity string `json:"to_city"`
Date string `json:"date"`
CreatedAt int64 `json:"created_at"`
}
// Preferences represents user preferences storage.
// It provides methods for managing saved cities and search history.
type Preferences struct {
store Cache
}
// NewPreferences creates a new Preferences instance with the given cache store.
func NewPreferences(store Cache) *Preferences {
return &Preferences{store: store}
}
// GetSavedCities returns the user's saved cities.
func (p *Preferences) GetSavedCities(ctx context.Context, userID string) ([]PreferenceSavedCity, error) {
data, err := p.store.Get(ctx, &CacheKey{
Kind: "prefs:saved_city:" + userID,
Code: userID,
From: "",
To: "",
Date: "",
Request: "",
})
if err != nil {
return nil, err
}
if data == nil {
return []PreferenceSavedCity{}, nil
}
var cities []PreferenceSavedCity
if err := json.Unmarshal(data, &cities); err != nil {
return nil, err
}
return cities, nil
}
// AddSavedCity adds a city to the user's saved cities.
func (p *Preferences) AddSavedCity(ctx context.Context, userID, cityCode, cityName string) error {
// Load existing cities
cities, err := p.GetSavedCities(ctx, userID)
if err != nil {
return err
}
// Check if city already exists
exists := false
for i, c := range cities {
if c.CityCode == cityCode {
cities[i].Name = cityName
exists = true
break
}
}
if !exists {
// Add new city
cities = append(cities, PreferenceSavedCity{
CityCode: cityCode,
Name: cityName,
})
}
// Store back to cache
data, err := json.Marshal(cities)
if err != nil {
return err
}
cacheKey := &CacheKey{
Kind: "prefs:saved_city:" + userID,
Code: userID,
From: "",
To: "",
Date: "",
Request: "",
}
if err := p.store.Set(ctx, cacheKey, data, CityTTL); err != nil {
return err
}
return nil
}
// RemoveSavedCity removes a city from the user's saved cities.
func (p *Preferences) RemoveSavedCity(ctx context.Context, userID, cityCode string) error {
key := &CacheKey{
Kind: "prefs:saved_city:" + userID,
Code: userID,
From: "",
To: "",
Date: "",
Request: "",
}
// Load existing cities
cities, err := p.GetSavedCities(ctx, userID)
if err != nil {
return err
}
// Remove the city
var result []PreferenceSavedCity
for _, c := range cities {
if c.CityCode != cityCode {
result = append(result, c)
}
}
if len(result) == 0 {
// If no cities left, delete the key
return p.store.Delete(ctx, key)
}
// Store back
data, err := json.Marshal(result)
if err != nil {
return err
}
return p.store.Set(ctx, key, data, CityTTL)
}
// GetSearchHistory returns the user's search history.
func (p *Preferences) GetSearchHistory(ctx context.Context, userID string) ([]PreferenceSearchHistory, error) {
key := &CacheKey{
Kind: "prefs:search_history:" + userID,
Code: userID,
From: "",
To: "",
Date: "",
Request: "",
}
data, err := p.store.Get(ctx, key)
if err != nil {
return nil, err
}
if data == nil {
return []PreferenceSearchHistory{}, nil
}
var history []PreferenceSearchHistory
if err := json.Unmarshal(data, &history); err != nil {
return nil, err
}
return history, nil
}
// AddSearchHistory adds a search to the user's history.
func (p *Preferences) AddSearchHistory(ctx context.Context, userID, fromCity, toCity, date string) error {
key := &CacheKey{
Kind: "prefs:search_history:" + userID,
Code: userID,
From: "",
To: "",
Date: "",
Request: "",
}
// Load existing history
history, err := p.GetSearchHistory(ctx, userID)
if err != nil {
return err
}
// Add new entry at the beginning (most recent first)
history = append([]PreferenceSearchHistory{
{
Query: fromCity + "→" + toCity,
FromCity: fromCity,
ToCity: toCity,
Date: date,
CreatedAt: time.Now().Unix(),
},
}, history...)
// Keep only last 50 searches
if len(history) > 50 {
history = history[:50]
}
// Store back to cache
data, err := json.Marshal(history)
if err != nil {
return err
}
if err := p.store.Set(ctx, key, data, CityTTL); err != nil {
return err
}
return nil
}
// RemoveOldSearchHistory removes search entries older than the given age.
func (p *Preferences) RemoveOldSearchHistory(ctx context.Context, userID string, maxAgeSeconds int64) error {
key := &CacheKey{
Kind: "prefs:search_history:" + userID,
Code: userID,
From: "",
To: "",
Date: "",
Request: "",
}
history, err := p.GetSearchHistory(ctx, userID)
if err != nil {
return err
}
// Filter out old entries
var recent []PreferenceSearchHistory
now := time.Now().Unix()
for _, entry := range history {
if entry.CreatedAt >= now-maxAgeSeconds {
recent = append(recent, entry)
}
}
if len(recent) == len(history) {
// No entries removed
return nil
}
// Store back
data, err := json.Marshal(recent)
if err != nil {
return err
}
if err := p.store.Set(ctx, key, data, CityTTL); err != nil {
return err
}
return nil
}