Initial commit
This commit is contained in:
344
app/Http/Controllers/LogController.php
Normal file
344
app/Http/Controllers/LogController.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Log;
|
||||
use App\Models\LogQso;
|
||||
use App\Models\EvaluationRun;
|
||||
use App\Models\QsoOverride;
|
||||
use App\Models\QsoResult;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class LogController extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// zápisové operace jen pro přihlášené
|
||||
$this->middleware('auth:sanctum')->only(['store', 'update', 'destroy']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seznam logů – s možností filtrování podle round_id, pcall, processed/accepted.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = (int) $request->get('per_page', 100);
|
||||
|
||||
$query = Log::query()
|
||||
->with(['round', 'file'])
|
||||
->withExists(['logResults as parsed'])
|
||||
->withExists(['logResults as parsed_claimed' => function ($q) {
|
||||
$q->whereHas('evaluationRun', function ($runQuery) {
|
||||
$runQuery->where('rules_version', 'CLAIMED');
|
||||
});
|
||||
}]);
|
||||
|
||||
if ($request->filled('round_id')) {
|
||||
$query->where('round_id', (int) $request->get('round_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('pcall')) {
|
||||
$query->where('pcall', $request->get('pcall'));
|
||||
}
|
||||
|
||||
if ($request->filled('processed')) {
|
||||
$query->where('processed', filter_var($request->get('processed'), FILTER_VALIDATE_BOOL));
|
||||
}
|
||||
|
||||
if ($request->filled('accepted')) {
|
||||
$query->where('accepted', filter_var($request->get('accepted'), FILTER_VALIDATE_BOOL));
|
||||
}
|
||||
|
||||
$logs = $query
|
||||
->orderByRaw('parsed_claimed asc, pcall asc')
|
||||
->paginate($perPage);
|
||||
|
||||
return response()->json($logs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vytvoření logu.
|
||||
* Typicky voláno po úspěšném uploadu / parsování EDI ve službě.
|
||||
* Autorizace přes LogPolicy@create.
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$this->authorize('create', Log::class);
|
||||
|
||||
$data = $this->validateData($request);
|
||||
|
||||
$log = Log::create($data);
|
||||
|
||||
$log->load(['round', 'file']);
|
||||
|
||||
return response()->json($log, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail jednoho logu včetně vazeb a počtu QSO.
|
||||
*/
|
||||
public function show(Request $request, Log $log): JsonResponse
|
||||
{
|
||||
$includeQsos = $request->boolean('include_qsos', false);
|
||||
$relations = ['round', 'file'];
|
||||
if ($includeQsos) {
|
||||
$relations[] = 'qsos';
|
||||
}
|
||||
$log->load($relations);
|
||||
|
||||
return response()->json($log);
|
||||
}
|
||||
|
||||
/**
|
||||
* QSO tabulka pro log: raw QSO + výsledky vyhodnocení + případné overrides.
|
||||
*/
|
||||
public function qsoTable(Request $request, Log $log): JsonResponse
|
||||
{
|
||||
$evalRunId = $request->filled('evaluation_run_id')
|
||||
? (int) $request->get('evaluation_run_id')
|
||||
: null;
|
||||
|
||||
if ($evalRunId) {
|
||||
$run = EvaluationRun::find($evalRunId);
|
||||
if (! $run) {
|
||||
$evalRunId = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $evalRunId) {
|
||||
$run = EvaluationRun::query()
|
||||
->where('round_id', $log->round_id)
|
||||
->where('status', 'SUCCEEDED')
|
||||
->where(function ($q) {
|
||||
$q->whereNull('rules_version')
|
||||
->orWhere('rules_version', '!=', 'CLAIMED');
|
||||
})
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
$evalRunId = $run?->id;
|
||||
}
|
||||
|
||||
$qsos = LogQso::query()
|
||||
->where('log_id', $log->id)
|
||||
->orderBy('qso_index')
|
||||
->orderBy('id')
|
||||
->get([
|
||||
'id',
|
||||
'qso_index',
|
||||
'time_on',
|
||||
'dx_call',
|
||||
'my_rst',
|
||||
'my_serial',
|
||||
'dx_rst',
|
||||
'dx_serial',
|
||||
'rx_wwl',
|
||||
'rx_exchange',
|
||||
'mode_code',
|
||||
'new_exchange',
|
||||
'new_wwl',
|
||||
'new_dxcc',
|
||||
'duplicate_qso',
|
||||
'points',
|
||||
]);
|
||||
|
||||
$qsoIds = $qsos->pluck('id')->all();
|
||||
$resultMap = collect();
|
||||
$overrideMap = collect();
|
||||
|
||||
if ($evalRunId && $qsoIds) {
|
||||
$resultMap = QsoResult::query()
|
||||
->where('evaluation_run_id', $evalRunId)
|
||||
->whereIn('log_qso_id', $qsoIds)
|
||||
->get([
|
||||
'log_qso_id',
|
||||
'points',
|
||||
'penalty_points',
|
||||
'error_code',
|
||||
'error_side',
|
||||
'match_confidence',
|
||||
'match_type',
|
||||
'error_flags',
|
||||
'is_valid',
|
||||
'is_duplicate',
|
||||
'is_nil',
|
||||
'is_busted_call',
|
||||
'is_busted_rst',
|
||||
'is_busted_exchange',
|
||||
'is_time_out_of_window',
|
||||
])
|
||||
->keyBy('log_qso_id');
|
||||
|
||||
$overrideMap = QsoOverride::query()
|
||||
->where('evaluation_run_id', $evalRunId)
|
||||
->whereIn('log_qso_id', $qsoIds)
|
||||
->get([
|
||||
'id',
|
||||
'log_qso_id',
|
||||
'forced_status',
|
||||
'forced_matched_log_qso_id',
|
||||
'forced_points',
|
||||
'forced_penalty',
|
||||
'reason',
|
||||
])
|
||||
->keyBy('log_qso_id');
|
||||
}
|
||||
|
||||
$data = $qsos->map(function (LogQso $qso) use ($resultMap, $overrideMap) {
|
||||
$result = $resultMap->get($qso->id);
|
||||
$override = $overrideMap->get($qso->id);
|
||||
|
||||
return [
|
||||
'id' => $qso->id,
|
||||
'qso_index' => $qso->qso_index,
|
||||
'time_on' => $qso->time_on,
|
||||
'dx_call' => $qso->dx_call,
|
||||
'my_rst' => $qso->my_rst,
|
||||
'my_serial' => $qso->my_serial,
|
||||
'dx_rst' => $qso->dx_rst,
|
||||
'dx_serial' => $qso->dx_serial,
|
||||
'rx_wwl' => $qso->rx_wwl,
|
||||
'rx_exchange' => $qso->rx_exchange,
|
||||
'mode_code' => $qso->mode_code,
|
||||
'new_exchange' => $qso->new_exchange,
|
||||
'new_wwl' => $qso->new_wwl,
|
||||
'new_dxcc' => $qso->new_dxcc,
|
||||
'duplicate_qso' => $qso->duplicate_qso,
|
||||
'points' => $qso->points,
|
||||
'remarks' => null,
|
||||
'result' => $result ? [
|
||||
'log_qso_id' => $result->log_qso_id,
|
||||
'points' => $result->points,
|
||||
'penalty_points' => $result->penalty_points,
|
||||
'error_code' => $result->error_code,
|
||||
'error_side' => $result->error_side,
|
||||
'match_confidence' => $result->match_confidence,
|
||||
'match_type' => $result->match_type,
|
||||
'error_flags' => $result->error_flags,
|
||||
'is_valid' => $result->is_valid,
|
||||
'is_duplicate' => $result->is_duplicate,
|
||||
'is_nil' => $result->is_nil,
|
||||
'is_busted_call' => $result->is_busted_call,
|
||||
'is_busted_rst' => $result->is_busted_rst,
|
||||
'is_busted_exchange' => $result->is_busted_exchange,
|
||||
'is_time_out_of_window' => $result->is_time_out_of_window,
|
||||
] : null,
|
||||
'override' => $override ? [
|
||||
'id' => $override->id,
|
||||
'log_qso_id' => $override->log_qso_id,
|
||||
'forced_status' => $override->forced_status,
|
||||
'forced_matched_log_qso_id' => $override->forced_matched_log_qso_id,
|
||||
'forced_points' => $override->forced_points,
|
||||
'forced_penalty' => $override->forced_penalty,
|
||||
'reason' => $override->reason,
|
||||
] : null,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'evaluation_run_id' => $evalRunId,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizace logu (partial update).
|
||||
* Typicky pro ruční úpravu flagů accepted/processed, případně oprav hlavičky.
|
||||
* Autorizace přes LogPolicy@update.
|
||||
*/
|
||||
public function update(Request $request, Log $log): JsonResponse
|
||||
{
|
||||
$this->authorize('update', $log);
|
||||
|
||||
$data = $this->validateData($request, partial: true);
|
||||
|
||||
$log->fill($data);
|
||||
$log->save();
|
||||
|
||||
$log->load(['round', 'file']);
|
||||
|
||||
return response()->json($log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smazání logu (včetně QSO přes FK ON DELETE CASCADE).
|
||||
* Autorizace přes LogPolicy@delete.
|
||||
*/
|
||||
public function destroy(Log $log): JsonResponse
|
||||
{
|
||||
$this->authorize('delete', $log);
|
||||
|
||||
// pokud je navázaný soubor, smaž i jeho fyzický obsah a záznam
|
||||
if ($log->file) {
|
||||
if ($log->file->path && Storage::exists($log->file->path)) {
|
||||
Storage::delete($log->file->path);
|
||||
}
|
||||
$log->file->delete();
|
||||
}
|
||||
|
||||
$log->delete();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jednoduchý parser nahraného souboru – aktuálně podporuje EDI.
|
||||
* Pokud jde o EDI, naplní základní pole Logu a uloží raw_header (bez sekce QSORecords).
|
||||
*/
|
||||
public static function parseUploadedFile(Log $log, string $path): void
|
||||
{
|
||||
app(\App\Services\Evaluation\EdiParserService::class)->parseLogFile($log, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validace vstupu pro store / update.
|
||||
* EDI parser bude typicky volat store/update s již připravenými daty.
|
||||
*/
|
||||
protected function validateData(Request $request, bool $partial = false): array
|
||||
{
|
||||
$required = $partial ? 'sometimes' : 'required';
|
||||
|
||||
return $request->validate([
|
||||
'round_id' => [$required, 'integer', 'exists:rounds,id'],
|
||||
'file_id' => ['sometimes', 'nullable', 'integer', 'exists:files,id'],
|
||||
|
||||
'accepted' => ['sometimes', 'boolean'],
|
||||
'processed' => ['sometimes', 'boolean'],
|
||||
'ip_address' => ['sometimes', 'nullable', 'string', 'max:45'],
|
||||
|
||||
'tname' => ['sometimes', 'nullable', 'string', 'max:100'],
|
||||
'tdate' => ['sometimes', 'nullable', 'string', 'max:50'],
|
||||
'pcall' => ['sometimes', 'nullable', 'string', 'max:20'],
|
||||
'pwwlo' => ['sometimes', 'nullable', 'string', 'max:6'],
|
||||
'pexch' => ['sometimes', 'nullable', 'string', 'max:10'],
|
||||
'psect' => ['sometimes', 'nullable', 'string', 'max:10'],
|
||||
'pband' => ['sometimes', 'nullable', 'string', 'max:10'],
|
||||
'pclub' => ['sometimes', 'nullable', 'string', 'max:50'],
|
||||
|
||||
'country_name' => ['sometimes', 'nullable', 'string', 'max:150'],
|
||||
'operator_name' => ['sometimes', 'nullable', 'string', 'max:100'],
|
||||
'locator' => ['sometimes', 'nullable', 'string', 'max:6'],
|
||||
|
||||
'power_watt' => ['sometimes', 'nullable', 'numeric', 'min:0'],
|
||||
'power_category' => ['sometimes', 'nullable', 'string', 'max:3'],
|
||||
'power_category_id' => ['sometimes', 'nullable', 'integer', 'exists:power_categories,id'],
|
||||
'sixhr_category' => ['sometimes', 'nullable', 'boolean'],
|
||||
|
||||
'claimed_qso_count' => ['sometimes', 'nullable', 'integer', 'min:0'],
|
||||
'claimed_score' => ['sometimes', 'nullable', 'integer', 'min:0'],
|
||||
'claimed_wwl' => ['sometimes', 'nullable', 'string', 'max:50'],
|
||||
'claimed_dxcc' => ['sometimes', 'nullable', 'string', 'max:50'],
|
||||
|
||||
'remarks' => ['sometimes', 'nullable', 'string', 'max:500'],
|
||||
'remarks_eval' => ['sometimes', 'nullable', 'string', 'max:500'],
|
||||
|
||||
'raw_header' => ['sometimes', 'nullable', 'string'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user