authorize('create', TranscriptionProject::class); return view('user.projects.create'); } public function store(CreateProjectRequest $request, StorageService $storage, AuditLogService $audit): RedirectResponse { $this->authorize('create', TranscriptionProject::class); $file = $request->file('audio'); $uuid = (string) \Illuminate\Support\Str::uuid(); $path = $storage->storeAudio($file, $uuid); $project = TranscriptionProject::create([ 'uuid' => $uuid, 'title' => $request->title, 'description' => $request->description, 'owner_user_id' => Auth::id(), 'original_filename' => $file->getClientOriginalName(), 'stored_audio_path' => $path, 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'language' => $request->language ?? 'ms', 'transcription_status' => 'pending', 'transcription_engine' => config('speech2text.transcription.engine'), ]); $audit->log('project_created', [ 'project_id' => $project->id, 'new_values' => ['title' => $project->title, 'uuid' => $project->uuid], ]); $audit->log('audio_uploaded', [ 'project_id' => $project->id, 'new_values' => [ 'original_filename' => $project->original_filename, 'file_size' => $project->file_size, 'mime_type' => $project->mime_type, ], ]); TranscribeAudioJob::dispatch($project); return redirect()->route('user.projects.show', $project) ->with('success', 'Projek berjaya dicipta. Audio sedang dalam barisan untuk ditranskripkan.'); } public function show(TranscriptionProject $project): View { $this->authorize('view', $project); $project->load(['owner', 'collaborators.department', 'transcriptVersions.editor', 'comments.user']); return view('user.projects.show', compact('project')); } // Endpoint polling status — JSON, tidak mendedahkan transcript public function status(TranscriptionProject $project): JsonResponse { $this->authorize('view', $project); return response()->json([ 'status' => $project->transcription_status, 'duration_seconds' => $project->duration_seconds, 'processed_at' => $project->processed_at?->format('d/m/Y H:i'), 'error_message' => $project->isFailed() ? $project->error_message : null, ]); } public function retry(TranscriptionProject $project, AuditLogService $audit): RedirectResponse { $this->authorize('retryTranscription', $project); $project->update([ 'transcription_status' => 'pending', 'error_message' => null, ]); $audit->log('transcription_started', [ 'project_id' => $project->id, 'new_values' => ['retry' => true], ]); TranscribeAudioJob::dispatch($project); return back()->with('success', 'Transkripsi sedang dicuba semula.'); } public function destroy(TranscriptionProject $project, AuditLogService $audit): RedirectResponse { $this->authorize('delete', $project); $audit->log('project_deleted', [ 'project_id' => $project->id, 'old_values' => ['title' => $project->title, 'uuid' => $project->uuid], ]); $project->delete(); return redirect()->route('user.dashboard') ->with('success', 'Projek berjaya dipadam.'); } }