@@ -3,11 +3,16 @@
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AttachSimulationToDraftRequest;
|
||||
use App\Http\Requests\ClaimSimulationFormDraftRequest;
|
||||
use App\Http\Requests\LinkSimulationDraftBusinessRequest;
|
||||
use App\Http\Requests\StoreSimulationFormDraftRequest;
|
||||
use App\Models\SimulationFormDraft;
|
||||
use App\Policies\SimulationPolicy;
|
||||
use App\Services\SimulationExecution;
|
||||
use App\Services\SimulationFormDrafts;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SimulationFormDraftController extends Controller
|
||||
@@ -16,13 +21,65 @@ class SimulationFormDraftController extends Controller
|
||||
{
|
||||
$key = $request->header('Idempotency-Key');
|
||||
abort_unless(is_string($key) && strlen($key) >= 16 && strlen($key) <= 128, 428, 'Idempotency-Key é obrigatório.');
|
||||
$result = $drafts->create($request->validated(), $request, $key);
|
||||
$result = $drafts->create($request->snapshots(), $request, $key);
|
||||
$draft = $result['draft'];
|
||||
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no, 'replayed' => $result['replayed']]], $result['replayed'] ? 200 : 201)
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no, 'replayed' => $result['replayed']], 'expires_at' => $draft->expires_at?->toISOString()], $result['replayed'] ? 200 : 201)
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function index(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless($request->user() !== null, 401);
|
||||
|
||||
return response()->json(['data' => $drafts->ownedDrafts($request->user())])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function claimPreview(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless($request->user() !== null, 401);
|
||||
|
||||
return response()->json(['data' => $drafts->claimPreview($request)])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function claim(ClaimSimulationFormDraftRequest $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
$result = $drafts->claim($request, $request->validated('choice'), $request->validated('existing_draft_id'));
|
||||
$draft = $result['draft'];
|
||||
|
||||
return response()->json(['data' => $drafts->present($draft, true), 'choice' => $result['choice'], 'replayed' => $result['replayed'] ?? false, 'expires_at' => $draft->expires_at?->toISOString()])
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function linkableBusinesses(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless($request->user()?->hasVerifiedEmail(), 403);
|
||||
|
||||
return response()->json(['data' => $drafts->linkableBusinesses($request->user())])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function businessLink(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id) && $request->user()?->hasVerifiedEmail(), 404);
|
||||
|
||||
return response()->json(['data' => $drafts->activeBusinessLink($request->user(), $id)])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function linkBusiness(LinkSimulationDraftBusinessRequest $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$result = $drafts->linkBusiness($request->user(), $id, $request->validated('business_id'));
|
||||
|
||||
return response()->json(['data' => $result], isset($result['replayed']) ? 200 : 201)->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function unlinkBusiness(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id) && $request->user()?->hasVerifiedEmail(), 404);
|
||||
|
||||
return response()->json(['data' => $drafts->unlinkBusiness($request->user(), $id)])->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function current(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
$draft = $drafts->current($request);
|
||||
@@ -30,7 +87,14 @@ class SimulationFormDraftController extends Controller
|
||||
return response()->json(['data' => null], 200)->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
return response()->json(['data' => $drafts->present($draft)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
return response()->json(['data' => $drafts->present($draft, $request->user() !== null)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function forgetCurrent(Request $request, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
$drafts->forgetCurrent($request);
|
||||
|
||||
return response()->json([], 204)->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function show(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
@@ -38,18 +102,82 @@ class SimulationFormDraftController extends Controller
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$draft = $drafts->find($id, $request);
|
||||
|
||||
return response()->json(['data' => $drafts->present($draft)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
return response()->json(['data' => $drafts->present($draft, $request->user() !== null)])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function update(StoreSimulationFormDraftRequest $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$draft = $drafts->update($id, $request->validated(), $request);
|
||||
$draft = $drafts->update($id, $request->snapshots(), $request);
|
||||
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no]])
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no], 'expires_at' => $draft->expires_at?->toISOString()])
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function renew(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$draft = $drafts->renew($id, $request);
|
||||
|
||||
return response()->json(['data' => ['id' => $draft->id, 'version_no' => $draft->version_no], 'expires_at' => $draft->expires_at?->toISOString()])
|
||||
->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function attachSimulation(AttachSimulationToDraftRequest $request, string $id, SimulationFormDrafts $drafts, SimulationExecution $execution): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
$validated = $request->validated();
|
||||
$simulation = $execution->find($validated['simulation_id'], $request);
|
||||
$tokenOwned = $simulation->anonymous_token_hash !== null
|
||||
&& hash_equals($simulation->anonymous_token_hash, (string) $execution->tokenHash($request));
|
||||
if ($request->user()) {
|
||||
$policy = app(SimulationPolicy::class);
|
||||
$canTransfer = $simulation->organization_id === null
|
||||
? $policy->view($request->user(), $simulation)
|
||||
: $policy->update($request->user(), $simulation);
|
||||
abort_unless($canTransfer || $tokenOwned, 404);
|
||||
} else {
|
||||
abort_unless($tokenOwned, 404);
|
||||
}
|
||||
abort_unless($simulation->status === 'completed' && is_array($simulation->result_snapshot), 409, 'Só é possível reutilizar uma simulação concluída.');
|
||||
$report = $simulation->reports->where('status', 'ready')->sortByDesc('created_at')->first();
|
||||
abort_unless($report !== null && is_array($report->report_snapshot), 409, 'O relatório versionado da simulação ainda não está disponível.');
|
||||
abort_unless(preg_match('/^[a-f0-9]{64}$/D', $report->report_sha256) === 1, 409,
|
||||
'O relatório da simulação não tem um hash verificável.');
|
||||
$canonicalSnapshots = DB::selectOne('select CAST(? AS jsonb)::text as report_canonical, CAST(? AS jsonb)::text as result_canonical', [
|
||||
json_encode($report->report_snapshot, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
||||
json_encode($simulation->result_snapshot, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
abort_unless(hash_equals($report->report_sha256, hash('sha256', $canonicalSnapshots->report_canonical))
|
||||
&& hash_equals($canonicalSnapshots->report_canonical, $canonicalSnapshots->result_canonical), 409,
|
||||
'O relatório não corresponde ao resultado versionado da simulação.');
|
||||
$inputSnapshot = array_intersect_key($simulation->input_snapshot, array_flip(['base', 'rate', 'reduction']));
|
||||
|
||||
$sourceSnapshot = [
|
||||
'simulation_id' => $simulation->id,
|
||||
'source_version_no' => $simulation->version_no,
|
||||
'source_created_at' => $simulation->created_at?->toISOString(),
|
||||
'source_updated_at' => $simulation->updated_at?->toISOString(),
|
||||
'input_snapshot' => $inputSnapshot,
|
||||
'calculation_snapshot' => $simulation->calculation_snapshot,
|
||||
'result_snapshot' => $simulation->result_snapshot,
|
||||
'result_sha256' => $report->report_sha256,
|
||||
'report_id' => $report->id,
|
||||
'report_version_no' => $report->version_no,
|
||||
'report_sha256' => $report->report_sha256,
|
||||
'report_snapshot' => $report->report_snapshot,
|
||||
'consented_at' => now()->toISOString(),
|
||||
];
|
||||
$attached = $drafts->attachSimulation($id, $sourceSnapshot, $request);
|
||||
$draft = $attached['draft'];
|
||||
|
||||
return response()->json([
|
||||
'data' => $drafts->present($draft, $request->user() !== null),
|
||||
'replayed' => $attached['replayed'],
|
||||
'expires_at' => $draft->expires_at?->toISOString(),
|
||||
])->header('ETag', $drafts->etag($draft))->header('Cache-Control', 'no-store, private');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, string $id, SimulationFormDrafts $drafts): JsonResponse
|
||||
{
|
||||
abort_unless(Str::isUuid($id), 404);
|
||||
|
||||
Reference in New Issue
Block a user