Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.80% covered (success)
98.80%
82 / 83
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
GlobalSettingsService
98.80% covered (success)
98.80%
82 / 83
85.71% covered (warning)
85.71%
6 / 7
12
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
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 create
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 update
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 delete
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getDefaults
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Http\Services;
4
5use App\Http\Models\FlyCutUsage;
6use App\Http\Models\Setting;
7use App\Http\Repositories\GlobalSettingsRepository;
8
9/**
10 * Service for global settings business logic.
11 *
12 * This service handles all business logic related to global settings,
13 * coordinating with the repository for data access. Global settings is
14 * a single record that contains application-wide configuration defaults.
15 */
16class GlobalSettingsService
17{
18    public function __construct(
19        private GlobalSettingsRepository $globalSettingsRepository
20    ) {}
21
22    /**
23     * Get the global settings.
24     *
25     * @return Setting|null The global settings or null if not found
26     */
27    public function get(): ?Setting
28    {
29        return $this->globalSettingsRepository->find();
30    }
31
32    /**
33     * Check if global settings exist.
34     *
35     * @return bool True if global settings exist
36     */
37    public function exists(): bool
38    {
39        return $this->globalSettingsRepository->exists();
40    }
41
42    /**
43     * Create global settings with defaults merged with provided data.
44     *
45     * If global settings already exist, they will be deleted and recreated.
46     *
47     * @param  array<string, mixed>  $data  Optional custom data to merge with defaults
48     * @return Setting The created settings
49     */
50    public function create(array $data = []): Setting
51    {
52        // Delete existing global settings if they exist
53        $existing = $this->globalSettingsRepository->find();
54        if ($existing) {
55            $this->globalSettingsRepository->delete($existing);
56        }
57
58        // Merge provided data with defaults
59        $settingsData = array_merge($this->getDefaults(), $data);
60
61        return $this->globalSettingsRepository->create($settingsData);
62    }
63
64    /**
65     * Update the global settings.
66     *
67     * Supports partial updates. For nested objects like 'features',
68     * provided values are merged with existing values.
69     *
70     * @param  array<string, mixed>  $data  The update data
71     * @return Setting|null The updated settings or null if not found
72     */
73    public function update(array $data): ?Setting
74    {
75        $setting = $this->globalSettingsRepository->find();
76
77        if (! $setting) {
78            return null;
79        }
80
81        // Handle partial update for features (merge with existing)
82        if (isset($data['features']) && is_array($setting->features)) {
83            $data['features'] = array_merge($setting->features, $data['features']);
84        }
85
86        return $this->globalSettingsRepository->update($setting, $data);
87    }
88
89    /**
90     * Delete the global settings.
91     *
92     * @return bool True if deleted successfully, false if not found
93     */
94    public function delete(): bool
95    {
96        $setting = $this->globalSettingsRepository->find();
97
98        if (! $setting) {
99            return false;
100        }
101
102        return $this->globalSettingsRepository->delete($setting);
103    }
104
105    /**
106     * Get the default global settings values.
107     *
108     * @return array<string, mixed> The default settings
109     */
110    private function getDefaults(): array
111    {
112        return [
113            'remove_grammar_on_lose_focus' => false,
114            'wage_per_hour' => FlyCutUsage::WAGE_PER_HOUR,
115            'words_per_minute' => FlyCutUsage::WORDS_PER_MINUTE,
116            'typing_style' => 'all',
117            'typing_speed' => 45,
118            'fly_rewrite' => 'enabled',
119            'fly_grammar' => 'enabled',
120            'fly_grammar_default_language' => 'en_US',
121            'features' => [
122                'flyCuts' => true,
123                'flyGrammar' => true,
124                'gmailPlugin' => true,
125                'linkedinPlugin' => true,
126                'flyPosts' => true,
127                'flyEngage' => true,
128                'outlookPlugin' => true,
129                'developerMode' => false,
130            ],
131            'playlists' => [
132                'FE' => [
133                    'title' => 'FlyEngage AI',
134                    'link' => 'https://www.youtube.com/embed/videoseries?si=qxFJZaQEckCCghkI&list=PLNswoLQcNQEIDimmUYa05VHvuYIRsh-HX&enablejsapi=1',
135                    'description' => 'How to Write a Comment on LinkedIn with AI using FlyEngage AI in under 30 Seconds!',
136                ],
137                'FP' => [
138                    'title' => 'FlyPosts AI',
139                    'link' => 'https://www.youtube.com/embed/3ycEO-hPkZo?si=g58yHWwzJi2bvlFP;list=PLNswoLQcNQEKFpMBiotZ45M5uzfEZbTLx&enablejsapi=1',
140                    'description' => 'How to Leverage FlyPosts AI - an AI Social Post Generator',
141                ],
142            ],
143            'ignoredInputTypes' => [
144                'password', 'checkbox', 'radio', 'file', 'button', 'submit', 'reset',
145                'select-one', 'select-multiple', 'range', 'image', 'email', 'number',
146                'hidden', 'time', 'url', 'week', 'tel', 'search', 'month', 'color',
147                'datetime-local', 'date', 'datetime',
148            ],
149            'blackListClasses' => [
150                'appsElementsSidekickPromptInput', 'fly-grammar-ignore', 'wsc-ignore',
151                'disabled', 'hidden', 'ql-clipboard', 'fly-rewrite-ignore', 'fly-msg-ignore',
152            ],
153            'blackListIds' => [':11s'],
154            'blackListAttributes' => [
155                ['name' => 'aria-label', 'value' => 'To recipients'],
156            ],
157            'blackListDomainRules' => [
158                [
159                    'domain' => 'calendar.google.com',
160                    'blackListClasses' => [],
161                    'blackListIds' => [],
162                    'blackListAttributes' => [
163                        ['name' => 'placeholder', 'value' => 'hh:mm'],
164                        ['name' => 'aria-label', 'value' => 'Start date'],
165                        ['name' => 'aria-label', 'value' => 'Guests'],
166                        ['name' => 'placeholder', 'value' => 'Add location'],
167                    ],
168                ],
169                [
170                    'domain' => 'linkedin.com',
171                    'blackListClasses' => ['search-global-typeahead__input', 'artdeco-typeahead__input'],
172                    'blackListIds' => [],
173                    'blackListAttributes' => [],
174                ],
175            ],
176        ];
177    }
178}