• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mattiabasone / minepic / 4104849234

pending completion
4104849234

push

github

GitHub
Upgrade and cleanup (#21)

32 of 32 new or added lines in 8 files covered. (100.0%)

745 of 865 relevant lines covered (86.13%)

4.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.29
/app/Resolvers/UuidResolver.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Minepic\Resolvers;
6

7
use Illuminate\Contracts\Events\Dispatcher;
8
use Log;
9
use Minepic\Cache\UserNotFoundCache;
10
use Minepic\Events\Account\AccountCreatedEvent;
11
use Minepic\Events\Account\UsernameChangeEvent;
12
use Minepic\Helpers\Storage\Files\SkinsStorage;
13
use Minepic\Minecraft\MojangAccount;
14
use Minepic\Minecraft\MojangClient;
15
use Minepic\Models\Account;
16

17
/**
18
 * TODO: This class must be refactored. It should return a "resolution" instead of modify its parameters.
19
 */
20
class UuidResolver
21
{
22
    /**
23
     * Requested string.
24
     */
25
    private string $request;
26

27
    private ?string $uuid = null;
28
    /**
29
     * Userdata from/to DB.
30
     */
31
    private ?Account $account;
32
    /**
33
     * User data has been updated?
34
     */
35
    private bool $dataUpdated = false;
36
    /**
37
     * Set force update.
38
     */
39
    private bool $forceUpdate = false;
40

41
    public function __construct(
42
        private readonly MojangClient $mojangClient,
43
        private readonly Dispatcher $eventDispatcher
44
    ) {
45
    }
24✔
46

47
    public function getUuid(): ?string
48
    {
49
        return $this->uuid;
9✔
50
    }
51

52
    /**
53
     * Return loaded user data.
54
     */
55
    public function getAccount(): Account
56
    {
57
        return $this->account ?? new Account();
12✔
58
    }
59

60
    /**
61
     * Insert user data in database.
62
     **
63
     * @throws \Throwable
64
     */
65
    public function insertNewUuid(): bool
66
    {
67
        if ($this->request === '' || UserNotFoundCache::has($this->request)) {
6✔
68
            return false;
2✔
69
        }
70

71
        $mojangAccount = $this->getFullUserdataApi();
4✔
72
        if ($mojangAccount instanceof MojangAccount) {
4✔
73
            $this->account = Account::create([
2✔
74
                'username' => $mojangAccount->getUsername(),
2✔
75
                'uuid' => $mojangAccount->getUuid(),
2✔
76
                'skin' => $mojangAccount->getSkin(),
2✔
77
                'cape' => $mojangAccount->getCape(),
2✔
78
            ]);
2✔
79

80
            $this->saveRemoteSkin();
2✔
81

82
            $this->uuid = $this->account->uuid;
2✔
83
            $this->eventDispatcher->dispatch(new AccountCreatedEvent($this->account));
2✔
84

85
            return true;
2✔
86
        }
87

88
        UserNotFoundCache::add($this->request);
2✔
89

90
        return false;
2✔
91
    }
92

93
    /**
94
     * Check requested string and initialize objects.
95
     */
96
    public function resolve(?string $uuid): bool
97
    {
98
        $this->dataUpdated = false;
15✔
99
        $this->request = $uuid ?? '';
15✔
100

101
        if ($uuid === null) {
15✔
102
            \Log::debug('UUID is null');
×
103

104
            return false;
×
105
        }
106

107
        if ($this->initializeUuidRequest()) {
15✔
108
            return true;
11✔
109
        }
110

111
        $this->setFailedRequest('Account not found');
4✔
112

113
        return false;
4✔
114
    }
115

116
    /**
117
     * Return if data has been updated.
118
     */
119
    public function userDataUpdated(): bool
120
    {
121
        return $this->dataUpdated;
×
122
    }
123

124
    public function saveRemoteSkin(): bool
125
    {
126
        if ($this->account instanceof Account === false) {
3✔
127
            return false;
×
128
        }
129

130
        if (!empty($this->account->skin) && $this->account->skin !== '') {
3✔
131
            try {
132
                $skinData = $this->mojangClient->getSkin($this->account->skin);
3✔
133

134
                return SkinsStorage::save($this->account->uuid, $skinData);
3✔
135
            } catch (\Exception $e) {
×
136
                \Log::error($e->getTraceAsString());
×
137
            }
138
        }
139

140
        return SkinsStorage::copyAsSteve($this->account->uuid);
×
141
    }
142

143
    /**
144
     * Set force update.
145
     */
146
    public function setForceUpdate(bool $forceUpdate): void
147
    {
148
        $this->forceUpdate = $forceUpdate;
1✔
149
    }
150

151
    /**
152
     * Check if cache is still valid.
153
     */
154
    private function checkDbCache(): bool
155
    {
156
        $accountUpdatedAtTimestamp = (int) ($this->account->updated_at->timestamp ?? 0);
9✔
157

158
        return (time() - $accountUpdatedAtTimestamp) < (int) env('USERDATA_CACHE_TIME');
9✔
159
    }
160

161
    /**
162
     * Check if an UUID is in the database.
163
     */
164
    private function requestedUuidInDb(): bool
165
    {
166
        $this->account = Account::query()
15✔
167
            ->whereUuid($this->request)
15✔
168
            ->first();
15✔
169

170
        if ($this->account === null) {
15✔
171
            return false;
6✔
172
        }
173

174
        $this->uuid = $this->account->uuid;
9✔
175

176
        return true;
9✔
177
    }
178

179
    /**
180
     * Update current user fail count.
181
     */
182
    private function updateUserFailUpdate(): void
183
    {
184
        if (isset($this->account->uuid)) {
×
185
            ++$this->account->fail_count;
×
186

187
            $this->account->save();
×
188
        }
189
    }
190

191
    /**
192
     * Update db user data.
193
     */
194
    private function updateDbUser(): void
195
    {
196
        if (
197
            $this->account instanceof Account &&
×
198
            isset($this->account->username) &&
×
199
            $this->account->uuid !== ''
×
200
        ) {
201
            // Get data from API
202
            $mojangAccount = $this->getFullUserdataApi();
×
203
            if ($mojangAccount instanceof MojangAccount) {
×
204
                $previousUsername = $this->account->username;
×
205
                // Update database
206
                $this->account->username = $mojangAccount->getUsername();
×
207
                $this->account->skin = $mojangAccount->getSkin() ?? '';
×
208
                $this->account->cape = $mojangAccount->getCape() ?? '';
×
209
                $this->account->fail_count = 0;
×
210
                $this->account->save();
×
211

212
                $this->account->refresh();
×
213

214
                // Update skin
215
                $this->saveRemoteSkin();
×
216
                $this->logUsernameChange($this->account, $previousUsername);
×
217

218
                $this->dataUpdated = true;
×
219

220
                return;
×
221
            }
222

223
            $this->updateUserFailUpdate();
×
224

225
            if (!SkinsStorage::exists($this->account->uuid)) {
×
226
                SkinsStorage::copyAsSteve($this->account->uuid);
×
227
            }
228
        }
229
        $this->dataUpdated = false;
×
230
    }
231

232
    /**
233
     * Log the username change.
234
     *
235
     * @param Account $account User Account
236
     * @param string $previousUsername Previous username
237
     */
238
    private function logUsernameChange(Account $account, string $previousUsername): void
239
    {
240
        if ($account->username !== $previousUsername && $previousUsername !== '') {
×
241
            $this->eventDispatcher->dispatch(
×
242
                new UsernameChangeEvent($account->uuid, $previousUsername, $account->username)
×
243
            );
×
244
        }
245
    }
246

247
    /**
248
     * Get userdata from Mojang/Minecraft API.
249
     */
250
    private function getFullUserdataApi(): ?MojangAccount
251
    {
252
        try {
253
            return $this->mojangClient->getUuidInfo($this->request);
4✔
254
        } catch (\Throwable $e) {
2✔
255
            \Log::error($e->getTraceAsString(), ['request' => $this->request]);
2✔
256

257
            return null;
2✔
258
        }
259
    }
260

261
    /**
262
     * Can I exec force update?
263
     */
264
    private function forceUpdatePossible(): bool
265
    {
266
        return $this->forceUpdate &&
9✔
267
            ((time() - (int) $this->account->updated_at->timestamp) > (int) env('MIN_USERDATA_UPDATE_INTERVAL'));
9✔
268
    }
269

270
    private function initializeUuidRequest(): bool
271
    {
272
        if ($this->requestedUuidInDb()) {
15✔
273
            // Check if UUID is in my database
274
            // Data cache still valid?
275
            if (!$this->checkDbCache() || $this->forceUpdatePossible()) {
9✔
276
                \Log::debug('Refreshing User DB Data');
×
277
                // Nope, updating data
278
                $this->updateDbUser();
×
279
            }
280

281
            if (!SkinsStorage::exists($this->account->uuid)) {
9✔
282
                $this->saveRemoteSkin();
1✔
283
            }
284

285
            return true;
9✔
286
        }
287

288
        if ($this->insertNewUuid()) {
6✔
289
            return true;
2✔
290
        }
291

292
        return false;
4✔
293
    }
294

295
    /**
296
     * Set failed request.
297
     */
298
    private function setFailedRequest(string $errorMessage = ''): void
299
    {
300
        \Log::notice($errorMessage, ['request' => $this->request]);
4✔
301
        $this->account = null;
4✔
302
        $this->request = '';
4✔
303
    }
304
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc