171 lines
9.0 KiB
PHP
171 lines
9.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\V1;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\StoreSimulationRequest;
|
|
use App\Http\Requests\UpdateSimulationScenarioRequest;
|
|
use App\Models\QuestionnaireVersion;
|
|
use App\Models\Simulation;
|
|
use App\Models\SimulationJob;
|
|
use App\Policies\SimulationPolicy;
|
|
use App\Services\SimulationExecution;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
class SimulationController extends Controller
|
|
{
|
|
public function questionnaire(string $code): JsonResponse
|
|
{
|
|
abort_unless(preg_match('/^[a-z0-9][a-z0-9-]{0,79}$/', $code) === 1, 404);
|
|
$version = QuestionnaireVersion::query()->where('code', $code)->where('locale', 'pt-MZ')->where('status', 'published')
|
|
->where(fn ($query) => $query->whereNull('valid_from')->orWhereDate('valid_from', '<=', today()))
|
|
->where(fn ($query) => $query->whereNull('valid_to')->orWhereDate('valid_to', '>', today()))
|
|
->orderByDesc('version_no')->firstOrFail();
|
|
$etag = '"questionnaire-'.$version->id.'-v'.$version->version_no.'"';
|
|
|
|
return response()->json(['data' => $version])->header('ETag', $etag)->header('Cache-Control', 'private, max-age=60');
|
|
}
|
|
|
|
public function store(StoreSimulationRequest $request, SimulationExecution $execution): JsonResponse
|
|
{
|
|
$key = $request->header('Idempotency-Key');
|
|
abort_unless(is_string($key) && strlen($key) >= 16 && strlen($key) <= 128, 428, 'Idempotency-Key é obrigatório.');
|
|
$actorId = $request->user()?->id;
|
|
$token = $actorId === null ? $request->header('X-Simulation-Token') : null;
|
|
abort_if($actorId === null && (! is_string($token) || strlen($token) < 32 || strlen($token) > 256), 422, 'X-Simulation-Token opaco gerado pelo cliente é obrigatório para execução anónima.');
|
|
$validated = $request->validated();
|
|
$data = ['base' => (string) $validated['base'], 'rate' => (string) $validated['rate'],
|
|
'reduction' => (string) ($validated['reduction'] ?? '0'), 'organization_id' => $validated['organization_id'] ?? null];
|
|
$organizationId = $data['organization_id'] ?? null;
|
|
$execution->authorizeOrganization($actorId, $organizationId);
|
|
$result = $execution->create($data, $actorId, $organizationId, $token, $key);
|
|
$simulation = $result['simulation'];
|
|
$body = ['data' => $simulation, 'replayed' => $result['replayed']];
|
|
if ($actorId === null) {
|
|
$body['access_token'] = $token;
|
|
}
|
|
|
|
return response()->json($body, $result['replayed'] ? 200 : 202)
|
|
->header('ETag', $execution->etag($simulation))
|
|
->header('Location', url('/api/v1/simulations/'.$simulation->id));
|
|
}
|
|
|
|
public function show(Request $request, string $id, SimulationExecution $execution): JsonResponse
|
|
{
|
|
abort_unless(Str::isUuid($id), 404);
|
|
$simulation = $execution->find($id, $request);
|
|
if ($request->user()) {
|
|
abort_unless(app(SimulationPolicy::class)->view($request->user(), $simulation), 404);
|
|
} else {
|
|
abort_unless($simulation->anonymous_token_hash !== null && hash_equals($simulation->anonymous_token_hash, (string) $execution->tokenHash($request)), 404);
|
|
}
|
|
|
|
return response()->json(['data' => $simulation])->header('ETag', $execution->etag($simulation));
|
|
}
|
|
|
|
public function history(Request $request, SimulationExecution $execution): JsonResponse
|
|
{
|
|
$user = $request->user();
|
|
abort_unless($user !== null, 401);
|
|
abort_unless($user->hasVerifiedEmail(), 403, 'Verifique o correio electrónico para consultar o histórico.');
|
|
$perPage = min(50, max(1, $request->integer('per_page', 20)));
|
|
$page = max(1, $request->integer('page', 1));
|
|
|
|
$history = DB::transaction(function () use ($execution, $user, $perPage, $page) {
|
|
$execution->setContext($user->id, null);
|
|
|
|
return Simulation::query()
|
|
->select([
|
|
'simulations.id', 'simulations.organization_id', 'visible_organization.name as organization_name',
|
|
'simulations.status', 'simulations.version_no', 'simulations.created_at', 'simulations.updated_at',
|
|
])
|
|
->leftJoin('organizations as visible_organization', 'visible_organization.id', '=', 'simulations.organization_id')
|
|
->where(function ($scope) use ($user) {
|
|
$scope->where(fn ($personal) => $personal->whereNull('simulations.organization_id')->where('simulations.owner_user_id', $user->id))
|
|
->orWhere(fn ($organization) => $organization->whereNotNull('simulations.organization_id')->whereExists(
|
|
fn ($membership) => $membership->selectRaw('1')->from('memberships')
|
|
->whereColumn('memberships.organization_id', 'simulations.organization_id')
|
|
->where('memberships.user_id', $user->id)->whereNull('memberships.deleted_at'),
|
|
));
|
|
})
|
|
->withExists(['reports as has_ready_report' => fn ($query) => $query->where('status', 'ready')])
|
|
->orderByDesc('simulations.created_at')->orderByDesc('simulations.id')
|
|
->paginate($perPage, ['*'], 'page', $page);
|
|
});
|
|
|
|
return response()->json([
|
|
'data' => $history->getCollection()->map(fn (Simulation $simulation): array => [
|
|
'id' => $simulation->id,
|
|
'scope' => $simulation->organization_id === null ? 'personal' : 'organization',
|
|
'organization_name' => $simulation->getAttribute('organization_name'),
|
|
'status' => $simulation->status,
|
|
'version_no' => $simulation->version_no,
|
|
'created_at' => $simulation->created_at?->toISOString(),
|
|
'updated_at' => $simulation->updated_at?->toISOString(),
|
|
'has_ready_report' => (bool) $simulation->getAttribute('has_ready_report'),
|
|
]),
|
|
'meta' => [
|
|
'current_page' => $history->currentPage(),
|
|
'last_page' => $history->lastPage(),
|
|
'per_page' => $history->perPage(),
|
|
'total' => $history->total(),
|
|
],
|
|
])->header('Cache-Control', 'no-store, private');
|
|
}
|
|
|
|
public function updateScenario(UpdateSimulationScenarioRequest $request, string $id, string $scenarioId, SimulationExecution $execution): JsonResponse
|
|
{
|
|
abort_unless(Str::isUuid($id) && Str::isUuid($scenarioId), 404);
|
|
$simulation = $execution->find($id, $request);
|
|
if ($request->user()) {
|
|
abort_unless(app(SimulationPolicy::class)->update($request->user(), $simulation), 403);
|
|
} else {
|
|
abort_unless($simulation->anonymous_token_hash !== null && hash_equals($simulation->anonymous_token_hash, (string) $execution->tokenHash($request)), 404);
|
|
}
|
|
$validated = $request->validated();
|
|
$data = ['label' => (string) $validated['label'], 'base' => (string) $validated['base'], 'rate' => (string) $validated['rate'],
|
|
'reduction' => (string) ($validated['reduction'] ?? '0')];
|
|
$updated = $execution->updateScenario($id, $scenarioId, $data, $request);
|
|
|
|
return response()->json(['data' => $updated], 202)->header('ETag', $execution->etag($updated));
|
|
}
|
|
|
|
public function report(Request $request, string $id, string $reportId, SimulationExecution $execution): JsonResponse
|
|
{
|
|
abort_unless(Str::isUuid($id) && Str::isUuid($reportId), 404);
|
|
$simulation = $execution->find($id, $request);
|
|
if ($request->user()) {
|
|
abort_unless(app(SimulationPolicy::class)->view($request->user(), $simulation), 404);
|
|
} else {
|
|
abort_unless($simulation->anonymous_token_hash !== null && hash_equals($simulation->anonymous_token_hash, (string) $execution->tokenHash($request)), 404);
|
|
}
|
|
$report = $simulation->reports->firstWhere('id', $reportId);
|
|
abort_if($report === null, 404);
|
|
|
|
return response()->json(['data' => $report]);
|
|
}
|
|
|
|
public function jobs(Request $request, string $id, SimulationExecution $execution): JsonResponse
|
|
{
|
|
abort_unless(Str::isUuid($id), 404);
|
|
$simulation = $execution->find($id, $request);
|
|
if ($request->user()) {
|
|
abort_unless(app(SimulationPolicy::class)->view($request->user(), $simulation), 404);
|
|
} else {
|
|
abort_unless($simulation->anonymous_token_hash !== null && hash_equals($simulation->anonymous_token_hash, (string) $execution->tokenHash($request)), 404);
|
|
}
|
|
|
|
$jobs = DB::transaction(function () use ($request, $id, $execution) {
|
|
$execution->setContext($request->user()?->id, $execution->tokenHash($request));
|
|
|
|
return SimulationJob::query()->where('simulation_id', $id)
|
|
->orderBy('created_at')->orderBy('id')->get();
|
|
});
|
|
|
|
return response()->json(['data' => $jobs]);
|
|
}
|
|
}
|