service = $service; } public function health(): JsonResponse { $response = $this->service->healthCheck(); return response()->json($response->json(), $response->status()); } public function process(ProcessRequest $request): JsonResponse { $protein = $request->file('protein'); $ligand = $request->file('ligand'); $params = $request->safe()->except(['protein', 'ligand']); $response = $this->service->submitJob( $protein->getRealPath(), $protein->getClientOriginalName(), $ligand->getRealPath(), $ligand->getClientOriginalName(), $params ); if ($response->failed()) { return $this->errorResponse( 'MD Simulation service error: '.$response->body(), $response->status() ); } $remoteJobId = $response->json('job_id'); if (! $remoteJobId) { return $this->errorResponse('Service did not return a job ID', 500); } $job = MdSimulationJob::create([ 'user_id' => Auth::id(), 'remote_job_id' => $remoteJobId, 'status' => 'processing', 'input_params' => $params, 'protein_original_name' => $protein->getClientOriginalName(), 'ligand_original_name' => $ligand->getClientOriginalName(), ]); return $this->successResponse('MD Simulation job submitted successfully', [ 'remote_job_id' => $job->remote_job_id, 'status' => $job->status, 'created_at' => $job->created_at->toDateTimeString(), ], 202); } public function status(string $remoteJobId): JsonResponse { $job = MdSimulationJob::where('user_id', Auth::id())->where('remote_job_id', $remoteJobId)->firstOrFail(); $response = $this->service->getJobStatus($job->remote_job_id); if ($response->successful()) { $remoteStatus = $response->json('status'); if ($remoteStatus) { $mapped = $this->mapRemoteStatus($remoteStatus); if ($mapped !== $job->status) { $update = ['status' => $mapped]; if ($mapped === 'completed') { $update['result_meta'] = [ 'download_url' => $response->json('download_url'), 'download_analysis_url' => $response->json('download_analysis_url'), ]; } elseif ($mapped === 'failed') { $update['error_message'] = $response->json('error') ?? $remoteStatus; } $job->update($update); } } } return $this->successResponse('Status retrieved', [ 'remote_job_id' => $job->remote_job_id, 'status' => $job->status, 'remote_status' => $response->json('status'), 'protein' => $job->protein_original_name, 'ligand' => $job->ligand_original_name, 'result_meta' => $job->result_meta, 'analysis_meta' => $job->analysis_meta, 'error_message' => $job->error_message, 'created_at' => $job->created_at->toDateTimeString(), ]); } public function analyze(Request $request, string $remoteJobId): JsonResponse { $job = MdSimulationJob::where('user_id', Auth::id())->where('remote_job_id', $remoteJobId)->firstOrFail(); if (! $job->isCompleted()) { return $this->errorResponse('Job is not completed yet', 400); } $response = $this->service->runAnalysis($job->remote_job_id, $request->only([ 'rmsd_mask', 'cc_mask', 'skip', 'dpi', 'threshold', ])); if ($response->failed()) { return $this->errorResponse( 'Analysis failed: '.$response->body(), $response->status() ); } $data = $response->json(); $job->update([ 'analysis_meta' => [ 'download_url' => $data['download_url'] ?? null, 'outputs' => $data['outputs'] ?? [], ], ]); return $this->successResponse('Analysis triggered successfully', [ 'download_url' => $data['download_url'] ?? null, 'outputs' => $data['outputs'] ?? [], ]); } public function download(string $remoteJobId): JsonResponse|\Illuminate\Http\Response { $job = MdSimulationJob::where('user_id', Auth::id())->where('remote_job_id', $remoteJobId)->firstOrFail(); if (! $job->isCompleted()) { return $this->errorResponse('Simulation not completed yet', 400); } $response = $this->service->downloadResults($job->remote_job_id); if ($response->failed()) { return $this->errorResponse('Results not available', 404); } return response($response->body(), 200) ->header('Content-Type', $response->header('Content-Type') ?? 'application/zip') ->header('Content-Disposition', "attachment; filename=\"{$job->remote_job_id}_Results.zip\""); } public function downloadAnalysis(string $remoteJobId): JsonResponse|\Illuminate\Http\Response { $job = MdSimulationJob::where('user_id', Auth::id())->where('remote_job_id', $remoteJobId)->firstOrFail(); if (! $job->isCompleted()) { return $this->errorResponse('Simulation not completed yet', 400); } $response = $this->service->downloadAnalysis($job->remote_job_id); if ($response->failed()) { return $this->errorResponse('Analysis results not available', 404); } return response($response->body(), 200) ->header('Content-Type', $response->header('Content-Type') ?? 'application/zip') ->header('Content-Disposition', "attachment; filename=\"{$job->remote_job_id}_Analysis.zip\""); } public function history(Request $request): JsonResponse { $perPage = $request->input('per_page', 15); $jobs = MdSimulationJob::where('user_id', Auth::id()) ->orderBy('created_at', 'desc') ->paginate($perPage); return $this->paginatedResponse('MD Simulation history retrieved', $jobs); } private function mapRemoteStatus(string $remoteStatus): string { if (str_starts_with($remoteStatus, 'Success:')) { return 'completed'; } if (str_starts_with($remoteStatus, 'Failed:')) { return 'failed'; } return 'processing'; } }