Files
trafficpumper/app/Services/EarnTaskResultService.php
T

90 lines
4.0 KiB
PHP
Raw Normal View History

2026-08-29 15:53:53 +03:00
<?php
namespace App\Services;
use App\Models\EarnApiToken;
use App\Models\EarnTaskAssignment;
use App\Models\EarnTaskResult;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class EarnTaskResultService
{
/**
* Sonuç işleme olaylarını mevcut güvenli Earn access logger ile kaydeder.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function __construct(private readonly EarnApiAccessLogger $logs) {}
/**
* Doğrulanmış token ve cihazdan gelen sonuçları sunucudaki atamaya göre idempotent kaydeder.
*
* @param array<int, array<string, mixed>> $items Doğrulanmış görev sonucu nesneleri.
* @return array{received: int, accepted: int, rejected: int, results: array<int, array<string, mixed>>}
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function store(Request $request, array $items): array
{
/**
* @var EarnApiToken $token Middleware tarafından doğrulanmış API anahtarı kaydı.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
$token = $request->attributes->get('earn_api_token');
$deviceHash = (string) $request->attributes->get('earn_device_hash');
$results = [];
$accepted = 0;
foreach ($items as $item) {
// Atama doğrulaması ile ilk sonuç kaydını aynı kilitli işlemde tutar.
// Yazar: Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
$outcome = DB::transaction(function () use ($item, $token, $deviceHash): array {
$assignment = EarnTaskAssignment::query()->where('task_id', $item['task_id'])->lockForUpdate()->first();
if (! $assignment instanceof EarnTaskAssignment
|| $assignment->personal_access_token_id !== $token->id
|| ! hash_equals($assignment->device_id_hash, $deviceHash)) {
return ['accepted' => false, 'code' => 'TASK_NOT_ASSIGNED'];
}
if ($assignment->user_id !== $item['CID']) {
return ['accepted' => false, 'code' => 'CID_MISMATCH'];
}
$existing = EarnTaskResult::query()->where('earn_task_assignment_id', $assignment->id)->first();
if ($existing instanceof EarnTaskResult) {
return ['accepted' => true, 'code' => 'ALREADY_RECORDED', 'duplicate' => true];
}
EarnTaskResult::query()->create([
'earn_task_assignment_id' => $assignment->id, 'user_id' => $assignment->user_id,
'earn_member_id' => $token->tokenable_id, 'personal_access_token_id' => $token->id,
'device_id_hash' => $deviceHash, 'completed' => $item['completed'],
'response_code' => $item['responseCode'], 'error_message' => $item['error'],
'submitted_at' => now(),
]);
return ['accepted' => true, 'code' => null];
}, 3);
$results[] = ['task_id' => $item['task_id'], 'accepted' => $outcome['accepted'], 'code' => $outcome['code']];
if ($outcome['accepted']) {
$accepted++;
}
$event = match ($outcome['code']) {
'ALREADY_RECORDED' => 'task_result_duplicate',
'CID_MISMATCH' => 'task_result_cid_mismatch',
'TASK_NOT_ASSIGNED' => 'task_result_not_assigned',
default => 'task_result_accepted',
};
$this->logs->record($request, $event, $outcome['accepted'] ? 200 : 422, $outcome['code']);
if (! $outcome['accepted']) {
$this->logs->record($request, 'task_result_rejected', 422, $outcome['code']);
}
}
return ['received' => count($items), 'accepted' => $accepted,
'rejected' => count($items) - $accepted, 'results' => $results];
}
}