Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
79.37% covered (warning)
79.37%
50 / 63
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserSettingService
79.37% covered (warning)
79.37%
50 / 63
71.43% covered (warning)
71.43%
5 / 7
22.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 update
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 getDetails
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
3.01
 buildWagePerHourHistory
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 getGlobalSettingsArray
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 buildUserSettings
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 loadCompanySettings
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace App\Http\Services;
4
5use App\Http\Models\Auth\User;
6use App\Http\Repositories\UserSettingRepository;
7use App\Traits\SubscriptionTrait;
8use Illuminate\Support\Facades\Cache;
9use Illuminate\Support\Facades\Config;
10
11/**
12 * Service for user settings business logic.
13 *
14 * Handles updating user settings (with wage history tracking) and building
15 * the merged settings details response (global + user + computed + company).
16 */
17class UserSettingService
18{
19    use SubscriptionTrait;
20
21    /**
22     * @param  UserSettingRepository  $userSettingRepository  Data access for settings
23     * @param  StatisticsService  $statisticsService  For wage/WPM calculations
24     * @param  WPSService  $wpsService  For user dictionary access
25     * @param  CacheInvalidationService  $cacheInvalidationService  For cache clearing
26     */
27    public function __construct(
28        private UserSettingRepository $userSettingRepository,
29        private StatisticsService $statisticsService,
30        private WPSService $wpsService,
31        private CacheInvalidationService $cacheInvalidationService
32    ) {}
33
34    /**
35     * Update user settings with wage history tracking.
36     *
37     * When wage_per_hour changes, appends the new value with a timestamp
38     * to the wage_per_hour_history array for historical tracking.
39     *
40     * @param  User  $user  The authenticated user
41     * @param  array<string, mixed>  $data  The validated settings data
42     * @return bool Whether the update was successful
43     */
44    public function update(User $user, array $data): bool
45    {
46        $userId = $user->getKey();
47
48        if (! empty($data['wage_per_hour'])) {
49            $data = $this->buildWagePerHourHistory($user, $data);
50        }
51
52        $result = $this->userSettingRepository->updateByUserId($userId, $data);
53
54        $this->cacheInvalidationService->invalidateUserSettingsCaches($userId);
55        $this->getDetails($user);
56
57        return (bool) $result;
58    }
59
60    /**
61     * Get the merged settings details for a user.
62     *
63     * Builds a complete settings response by merging:
64     * 1. Global settings (defaults)
65     * 2. User-specific settings (overrides)
66     * 3. Computed values (WPM, wage, dictionary, plan features)
67     * 4. Company settings (if user belongs to a company)
68     *
69     * Results are cached for performance.
70     *
71     * @param  User  $user  The authenticated user
72     * @return array<string, mixed> The merged settings
73     */
74    public function getDetails(User $user): array
75    {
76        $cacheKey = 'setting_details_'.$user->id;
77
78        return Cache::remember($cacheKey, Config::get('cache.expiry'), function () use ($user) {
79            $default = $this->getGlobalSettingsArray();
80            $result = $this->userSettingRepository->findOrCreateRawByUserId($user->id);
81            $result->flyperksJwt = '';
82
83            $userSettings = $this->buildUserSettings($user);
84
85            if ($user->company_id) {
86                $result->company = $this->loadCompanySettings($user->company_id);
87            }
88
89            $userSettingsStdClassToArray = json_decode(json_encode($result), true);
90            $default['features'] = array_merge($default['features'], $userSettingsStdClassToArray['features'] ?? []);
91            $default['features']['flySentenceRewrite'] = $userSettings['show_rewrite'];
92            $default['features']['flyParagraphRewrite'] = $userSettings['show_rewrite'];
93
94            $merged = array_merge($default, $userSettingsStdClassToArray, $userSettings);
95
96            if (! $userSettings['show_rewrite']) {
97                $merged['fly_rewrite'] = 'disabled';
98            }
99
100            return $merged;
101        });
102    }
103
104    /**
105     * Build the wage_per_hour_history array when wage changes.
106     *
107     * @param  User  $user  The authenticated user
108     * @param  array<string, mixed>  $data  The settings data with new wage_per_hour
109     * @return array<string, mixed> The data with updated wage_per_hour_history
110     */
111    private function buildWagePerHourHistory(User $user, array $data): array
112    {
113        $currentWagePerHour = $this->statisticsService->getWagePerHour($user, now());
114        $setting = $this->userSettingRepository->findByUserId($user->getKey());
115
116        if ($setting) {
117            $data['wage_per_hour_history'] = $setting->wage_per_hour_history ?? [];
118        }
119
120        if (count($data['wage_per_hour_history']) == 0) {
121            $data['wage_per_hour_history'] = [[
122                'wage_per_hour' => $currentWagePerHour,
123                'date' => $user->created_at->format('Y-m-d H:i:s'),
124            ]];
125        }
126
127        $data['wage_per_hour_history'] = array_merge($data['wage_per_hour_history'], [[
128            'wage_per_hour' => $data['wage_per_hour'],
129            'date' => now()->format('Y-m-d H:i:s'),
130        ]]);
131
132        return $data;
133    }
134
135    /**
136     * Get global settings as an array.
137     *
138     * @return array<string, mixed> The global settings
139     */
140    private function getGlobalSettingsArray(): array
141    {
142        $default = $this->userSettingRepository->findGlobalSettings();
143
144        return json_decode(json_encode($default), true);
145    }
146
147    /**
148     * Build computed user settings (WPM, wage, dictionary, plan flags).
149     *
150     * @param  User  $user  The authenticated user
151     * @return array<string, mixed> The computed user settings
152     */
153    private function buildUserSettings(User $user): array
154    {
155        $plan = $this->getCurrentPlan($user);
156        $showSentenceRewrite = true;
157
158        $userSettings = [
159            'words_per_minute' => $this->statisticsService->getWordsPerMinute($user),
160            'wage_per_hour' => $this->statisticsService->getWagePerHour($user, now()),
161            'user_dictionary' => $this->wpsService->getUserDictionary($user),
162            'show_rewrite' => $showSentenceRewrite,
163            'show_write_upgrade_button' => empty($user->company_id) && $plan->identifier !== 'sales-pro-monthly' && $plan->identifier !== 'sales-pro-yearly',
164            'show_upgrade_button' => empty($user->company_id) && $plan->identifier !== 'sales-pro-monthly' && $plan->identifier !== 'sales-pro-yearly',
165        ];
166
167        return $userSettings;
168    }
169
170    /**
171     * Load company settings merged with defaults.
172     *
173     * @param  string  $companyId  The company ID
174     * @return array<string, mixed> The merged company settings
175     */
176    private function loadCompanySettings(string $companyId): array
177    {
178        $settings = $this->userSettingRepository->findByCompanyId($companyId);
179        $default = $this->statisticsService->getCompanyDefaultSetting();
180
181        if (! $settings) {
182            $settings = $default;
183        } else {
184            $settings = $settings->toArray();
185        }
186
187        $settings = array_merge($default, $settings);
188
189        if (! $settings['wage_per_hour']) {
190            $settings['wage_per_hour'] = $default['wage_per_hour'];
191        }
192
193        if (! $settings['words_per_minute']) {
194            $settings['words_per_minute'] = $default['words_per_minute'];
195        }
196
197        return $settings;
198    }
199}