Files
Novault-backend/internal/service/user_preference_service.go
2026-01-25 21:59:00 +08:00

269 lines
6.9 KiB
Go

package service
import (
"encoding/json"
"errors"
"accounting-app/internal/repository"
)
// User preference service errors
var (
ErrPreferenceNotFound = errors.New("user preference not found")
)
// UserPreferenceOutput represents the output format for user preferences
type UserPreferenceOutput struct {
LastAccountID *uint `json:"last_account_id,omitempty"`
LastCategoryID *uint `json:"last_category_id,omitempty"`
FrequentAccounts []uint `json:"frequent_accounts,omitempty"`
FrequentCategories []uint `json:"frequent_categories,omitempty"`
}
// UserPreferenceService handles business logic for user preferences
type UserPreferenceService struct {
prefRepo *repository.UserPreferenceRepository
accountRepo *repository.AccountRepository
categoryRepo *repository.CategoryRepository
}
// NewUserPreferenceService creates a new UserPreferenceService instance
func NewUserPreferenceService(
prefRepo *repository.UserPreferenceRepository,
accountRepo *repository.AccountRepository,
categoryRepo *repository.CategoryRepository,
) *UserPreferenceService {
return &UserPreferenceService{
prefRepo: prefRepo,
accountRepo: accountRepo,
categoryRepo: categoryRepo,
}
}
// GetPreferences retrieves user preferences
func (s *UserPreferenceService) GetPreferences(userID uint) (*UserPreferenceOutput, error) {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return nil, err
}
output := &UserPreferenceOutput{
LastAccountID: pref.LastAccountID,
LastCategoryID: pref.LastCategoryID,
}
// Parse frequent accounts JSON
if pref.FrequentAccounts != "" {
var accounts []uint
if err := json.Unmarshal([]byte(pref.FrequentAccounts), &accounts); err == nil {
output.FrequentAccounts = accounts
}
}
// Parse frequent categories JSON
if pref.FrequentCategories != "" {
var categories []uint
if err := json.Unmarshal([]byte(pref.FrequentCategories), &categories); err == nil {
output.FrequentCategories = categories
}
}
return output, nil
}
// RecordAccountUsage records that an account was used and updates preferences
func (s *UserPreferenceService) RecordAccountUsage(userID uint, accountID uint) error {
// Verify account exists
exists, err := s.accountRepo.ExistsByID(userID, accountID)
if err != nil {
return err
}
if !exists {
return errors.New("account not found")
}
// Update last account
if err := s.prefRepo.UpdateLastAccount(userID, accountID); err != nil {
return err
}
// Update frequent accounts
return s.updateFrequentAccounts(userID, accountID)
}
// RecordCategoryUsage records that a category was used and updates preferences
func (s *UserPreferenceService) RecordCategoryUsage(userID uint, categoryID uint) error {
// Verify category exists
exists, err := s.categoryRepo.ExistsByID(userID, categoryID)
if err != nil {
return err
}
if !exists {
return errors.New("category not found")
}
// Update last category
if err := s.prefRepo.UpdateLastCategory(userID, categoryID); err != nil {
return err
}
// Update frequent categories
return s.updateFrequentCategories(userID, categoryID)
}
// RecordTransactionUsage records both account and category usage from a transaction
func (s *UserPreferenceService) RecordTransactionUsage(userID uint, accountID, categoryID uint) error {
if err := s.RecordAccountUsage(userID, accountID); err != nil {
return err
}
return s.RecordCategoryUsage(userID, categoryID)
}
// updateFrequentAccounts updates the frequent accounts list
func (s *UserPreferenceService) updateFrequentAccounts(userID uint, accountID uint) error {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return err
}
var accounts []uint
if pref.FrequentAccounts != "" {
if err := json.Unmarshal([]byte(pref.FrequentAccounts), &accounts); err != nil {
accounts = []uint{}
}
}
// Move the used account to the front (most recent)
accounts = moveToFront(accounts, accountID)
// Keep only top 10 frequent accounts
if len(accounts) > 10 {
accounts = accounts[:10]
}
// Save back
jsonBytes, err := json.Marshal(accounts)
if err != nil {
return err
}
return s.prefRepo.UpdateFrequentAccounts(userID, string(jsonBytes))
}
// updateFrequentCategories updates the frequent categories list
func (s *UserPreferenceService) updateFrequentCategories(userID uint, categoryID uint) error {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return err
}
var categories []uint
if pref.FrequentCategories != "" {
if err := json.Unmarshal([]byte(pref.FrequentCategories), &categories); err != nil {
categories = []uint{}
}
}
// Move the used category to the front (most recent)
categories = moveToFront(categories, categoryID)
// Keep only top 10 frequent categories
if len(categories) > 10 {
categories = categories[:10]
}
// Save back
jsonBytes, err := json.Marshal(categories)
if err != nil {
return err
}
return s.prefRepo.UpdateFrequentCategories(userID, string(jsonBytes))
}
// GetLastUsedAccount returns the last used account ID
func (s *UserPreferenceService) GetLastUsedAccount(userID uint) (*uint, error) {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return nil, err
}
return pref.LastAccountID, nil
}
// GetLastUsedCategory returns the last used category ID
func (s *UserPreferenceService) GetLastUsedCategory(userID uint) (*uint, error) {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return nil, err
}
return pref.LastCategoryID, nil
}
// GetFrequentAccounts returns the list of frequently used account IDs
func (s *UserPreferenceService) GetFrequentAccounts(userID uint) ([]uint, error) {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return nil, err
}
if pref.FrequentAccounts == "" {
return []uint{}, nil
}
var accounts []uint
if err := json.Unmarshal([]byte(pref.FrequentAccounts), &accounts); err != nil {
return []uint{}, nil
}
return accounts, nil
}
// GetFrequentCategories returns the list of frequently used category IDs
func (s *UserPreferenceService) GetFrequentCategories(userID uint) ([]uint, error) {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return nil, err
}
if pref.FrequentCategories == "" {
return []uint{}, nil
}
var categories []uint
if err := json.Unmarshal([]byte(pref.FrequentCategories), &categories); err != nil {
return []uint{}, nil
}
return categories, nil
}
// ClearPreferences clears all user preferences
func (s *UserPreferenceService) ClearPreferences(userID uint) error {
pref, err := s.prefRepo.GetOrCreate(userID)
if err != nil {
return err
}
pref.LastAccountID = nil
pref.LastCategoryID = nil
pref.FrequentAccounts = ""
pref.FrequentCategories = ""
return s.prefRepo.Update(pref)
}
// moveToFront moves an ID to the front of the slice, removing duplicates
func moveToFront(ids []uint, id uint) []uint {
// Remove existing occurrence
result := make([]uint, 0, len(ids)+1)
result = append(result, id)
for _, existingID := range ids {
if existingID != id {
result = append(result, existingID)
}
}
return result
}