Files
cetaksuratkp-api/app/Http/Controllers/LetterController.php
T

347 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\FileUpload;
use App\Models\Letter;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Exception;
use Illuminate\Validation\ValidationException;
/**
* Class LetterController
*
* Handles all letter-related operations including CRUD and file attachments.
* This controller manages student letters with their attachments.
*/
class LetterController extends Controller
{
/**
* Display a listing of all letters with their attachments
*
* Uses eager loading with 'fileUpload' relation to prevent N+1 query problem
* Formats each letter response using formatLetterResponse helper
*
* @return JsonResponse Array of formatted letters
*/
public function index(): JsonResponse
{
try {
$letters = Letter::with('fileUpload')
->get()
->map(fn ($letter) => $this->formatLetterResponse($letter));
return response()->json([
'success' => true,
'data' => $letters
]);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error fetching letters',
'error' => $e->getMessage()
], 500);
}
}
/**
* Store a newly created letter
*
* Validates input data and handles file upload if present
* Creates new letter with UUID as primary key
*
* @param Request $request Contains letter data and optional file attachment
* @return JsonResponse Created letter with its file upload info
*/
public function store(Request $request): JsonResponse
{
try {
// Validate incoming request data
$validated = $request->validate([
'nama_mahasiswa' => 'required|string',
'nomor_induk_mahasiswa' => 'required|string',
'program_studi_mahasiswa' => 'required|string',
'tahun_ajaran' => 'required|string',
'start_date' => 'required|date',
'completion_date' => 'required|date',
'lampiran' => 'nullable|file|mimes:pdf,jpg,png|max:2048', // Max 2MB
]);
// Create letter with UUID and optional fields
$letter = Letter::create([
'id' => Str::uuid()->toString(),
...$validated,
'is_acceptance' => $request->is_acceptance ?? false,
'nomor_surat_acceptance' => $request->nomor_surat_acceptance,
'is_completion' => $request->is_completion ?? false,
'nomor_surat_completion' => $request->nomor_surat_completion,
]);
// Handle file upload if present
if ($request->hasFile('lampiran')) {
$this->handleFileUpload($request->file('lampiran'), $letter);
}
return response()->json([
'success' => true,
'message' => 'Surat berhasil disimpan',
'data' => $letter->load('fileUpload')
], 201);
} catch (ValidationException $e) {
return response()->json([
'success' => false,
'message' => 'Validation failed',
'errors' => $e->errors()
], 422);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error creating letter',
'error' => $e->getMessage()
], 500);
}
}
/**
* Display the specified letter
*
* @param string $id Letter UUID
* @return JsonResponse Letter details with file attachment
* @throws ModelNotFoundException If letter not found
*/
public function show(string $id): JsonResponse
{
try {
$letter = Letter::with('fileUpload')->findOrFail($id);
return response()->json([
'success' => true,
'data' => $this->formatLetterResponse($letter)
]);
} catch (ModelNotFoundException $e) {
return response()->json([
'success' => false,
'message' => 'Letter not found'
], 404);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error retrieving letter',
'error' => $e->getMessage()
], 500);
}
}
/**
* Update the specified letter
*
* Handles partial updates with 'sometimes' validation rules
* Updates file attachment if new file is uploaded
*
* @param Request $request Update data and optional new file
* @param string $id Letter UUID
* @return JsonResponse Updated letter data
* @throws ModelNotFoundException If letter not found
*/
public function update(Request $request, string $id): JsonResponse
{
try {
$letter = Letter::findOrFail($id);
$validated = $request->validate([
'nama_mahasiswa' => 'sometimes|string',
'nomor_induk_mahasiswa' => 'sometimes|string',
'program_studi_mahasiswa' => 'sometimes|string',
'tahun_ajaran' => 'sometimes|string',
'start_date' => 'sometimes|date',
'completion_date' => 'sometimes|date',
'is_acceptance' => 'sometimes|boolean',
'nomor_surat_acceptance' => 'nullable|string',
'is_completion' => 'sometimes|boolean',
'nomor_surat_completion' => 'nullable|string',
'lampiran' => 'nullable|file|mimes:pdf,jpg,png|max:2048',
]);
$letter->update($validated);
if ($request->hasFile('lampiran')) {
$this->handleFileUpload($request->file('lampiran'), $letter, true);
}
return response()->json([
'success' => true,
'message' => 'Surat berhasil diperbarui',
'data' => $letter->load('fileUpload')
]);
} catch (ModelNotFoundException $e) {
return response()->json([
'success' => false,
'message' => 'Letter not found'
], 404);
} catch (ValidationException $e) {
return response()->json([
'success' => false,
'message' => 'Validation failed',
'errors' => $e->errors()
], 422);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error updating letter',
'error' => $e->getMessage()
], 500);
}
}
/**
* Delete letter and its attachment
* DELETE /api/letters/{id}
*/
public function destroy(string $id): JsonResponse
{
try {
$letter = Letter::findOrFail($id);
$this->deleteFileUpload($letter->id);
$letter->delete();
return response()->json([
'success' => true,
'message' => 'Surat berhasil dihapus'
]);
} catch (ModelNotFoundException $e) {
return response()->json([
'success' => false,
'message' => 'Letter not found'
], 404);
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error deleting letter',
'error' => $e->getMessage()
], 500);
}
}
/**
* Generate signed URL for file attachment
*
* Creates temporary signed URL valid for 5 minutes
* Used for secure file access without exposing actual file path
*
* @param string $id FileUpload UUID
* @return JsonResponse Signed URL and file metadata
*/
public function letterLampiran(string $id): JsonResponse
{
try {
$file = FileUpload::findOrFail($id);
$filePath = storage_path('app/public/' . $file->file_path);
if (!file_exists($filePath)) {
return response()->json([
'success' => false,
'message' => 'File not found'
], 404);
}
// Generate signed URL valid for 5 minutes
return response()->json([
'success' => true,
'data' => [
'file_name' => $file->file_name,
'mime_type' => $file->mime_type,
'url' => URL::signedRoute('file.view', ['id' => $id], now()->addMinutes(5))
]
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Error retrieving file',
'error' => $e->getMessage()
], 500);
}
}
/**
* Format letter data for consistent API response
*
* Includes file URL if attachment exists
* Uses Laravel's asset() helper for public storage URLs
*
* @param Letter $letter Letter model instance
* @return array Formatted letter data
*/
private function formatLetterResponse(Letter $letter): array
{
return [
'id' => $letter->id,
'nama_mahasiswa' => $letter->nama_mahasiswa,
'nomor_induk_mahasiswa' => $letter->nomor_induk_mahasiswa,
'program_studi_mahasiswa' => $letter->program_studi_mahasiswa,
'tahun_ajaran' => $letter->tahun_ajaran,
'start_date' => $letter->start_date,
'completion_date' => $letter->completion_date,
'is_acceptance' => $letter->is_acceptance,
'nomor_surat_acceptance' => $letter->nomor_surat_acceptance,
'is_completion' => $letter->is_completion,
'nomor_surat_completion' => $letter->nomor_surat_completion,
'lampiran' => $letter->fileUpload ?
asset('storage/lampiran/' . basename($letter->fileUpload->file_path)) :
null,
'created_at' => $letter->created_at,
];
}
/**
* Handle file upload process
*
* Stores file in public storage
* Creates FileUpload record
* Updates letter with file reference
*
* @param UploadedFile $file The uploaded file
* @param Letter $letter Associated letter
* @param bool $isUpdate Whether this is an update operation
*/
private function handleFileUpload($file, Letter $letter, bool $isUpdate = false): void
{
// Delete existing file if updating
if ($isUpdate) {
$this->deleteFileUpload($letter->id);
}
// Store file and create record
$path = $file->store('lampiran', 'public');
$fileId = Str::uuid()->toString();
FileUpload::create([
'id' => $fileId,
'letter_id' => $letter->id,
'file_name' => $file->getClientOriginalName(),
'file_path' => $path,
'mime_type' => $file->getMimeType(),
]);
$letter->update(['lampiran' => $fileId]);
}
/**
* Helper method to delete file uploads
*/
private function deleteFileUpload(string $letterId): void
{
$file = FileUpload::where('letter_id', $letterId)->first();
if ($file) {
Storage::delete($file->file_path);
$file->delete();
}
}
}