Implement file upload and viewing functionality; refactor letter migrations and models to use UUIDs
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\FileUpload;
|
||||||
|
|
||||||
|
class FileViewController extends Controller
|
||||||
|
{
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$file = FileUpload::findOrFail($id);
|
||||||
|
return response()->file(storage_path('app/public/' . $file->file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class FileUpload extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, HasUuids;
|
||||||
|
|
||||||
|
protected $table = 'file_uploads';
|
||||||
|
protected $keyType = 'string';
|
||||||
|
public $incrementing = false;
|
||||||
|
|
||||||
|
protected $fillable = ['letter_id', 'file_name', 'file_path', 'mime_type'];
|
||||||
|
|
||||||
|
protected static function boot()
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
static::creating(function ($model) {
|
||||||
|
$model->id = Str::uuid()->toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class Letter extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, HasUuids;
|
||||||
|
|
||||||
|
protected $table = 'letters';
|
||||||
|
protected $keyType = 'string';
|
||||||
|
public $incrementing = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'nama_mahasiswa',
|
||||||
|
'nomor_induk_mahasiswa',
|
||||||
|
'program_studi_mahasiswa',
|
||||||
|
'tahun_ajaran',
|
||||||
|
'start_date',
|
||||||
|
'completion_date',
|
||||||
|
'lampiran',
|
||||||
|
'is_acceptance',
|
||||||
|
'nomor_surat_acceptance',
|
||||||
|
'is_completion',
|
||||||
|
'nomor_surat_completion',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function boot()
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
static::creating(function ($model) {
|
||||||
|
$model->id = Str::uuid()->toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileUpload()
|
||||||
|
{
|
||||||
|
return $this->hasOne(FileUpload::class, 'letter_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
@@ -46,4 +47,15 @@ class User extends Authenticatable
|
|||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected $keyType = 'string';
|
||||||
|
public $incrementing = false;
|
||||||
|
|
||||||
|
public static function boot() {
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::creating(function ($model) {
|
||||||
|
$model->id = Str::uuid();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,20 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('acceptance_letters', function (Blueprint $table) {
|
Schema::create('letters', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->uuid('id')->primary();
|
||||||
$table->string('nomor_surat');
|
// $table->string('nomor_surat');
|
||||||
$table->string('nama_mahasiswa');
|
$table->string('nama_mahasiswa');
|
||||||
$table->string('nomor_induk_mahasiswa');
|
$table->string('nomor_induk_mahasiswa');
|
||||||
$table->string('program_studi_mahasiswa');
|
$table->string('program_studi_mahasiswa');
|
||||||
$table->string('tahun_ajaran');
|
$table->string('tahun_ajaran');
|
||||||
$table->date('start_date');
|
$table->date('start_date');
|
||||||
|
$table->date('completion_date');
|
||||||
$table->string('lampiran')->nullable();
|
$table->string('lampiran')->nullable();
|
||||||
|
$table->boolean('is_acceptance')->default(false);
|
||||||
|
$table->string('nomor_surat_acceptance')->nullable();
|
||||||
|
$table->boolean('is_completion')->default(false);
|
||||||
|
$table->string('nomor_surat_completion')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -29,6 +34,6 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('acceptance_letters');
|
Schema::dropIfExists('letters');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('completion_letters', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('nomor_surat');
|
|
||||||
$table->string('nama_mahasiswa');
|
|
||||||
$table->string('nomor_induk_mahasiswa');
|
|
||||||
$table->string('program_studi_mahasiswa');
|
|
||||||
$table->string('tahun_ajaran');
|
|
||||||
$table->date('completion_date');
|
|
||||||
$table->string('lampiran')->nullable();
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('completion_letters');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('completion_templates', function (Blueprint $table) {
|
Schema::create('completion_templates', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->uuid('id')->primary();
|
||||||
$table->string('greeting');
|
$table->string('greeting');
|
||||||
$table->text('content');
|
$table->text('content');
|
||||||
$table->string('closing');
|
$table->string('closing');
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('file_uploads', function (Blueprint $table) {
|
||||||
|
$table->uuid('id')->primary();
|
||||||
|
$table->uuid('letter_id')->nullable();
|
||||||
|
$table->string('file_name');
|
||||||
|
$table->string('file_path');
|
||||||
|
$table->string('mime_type');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('file_uploads');
|
||||||
|
}
|
||||||
|
};
|
||||||
+60
-6
@@ -4,16 +4,70 @@ use App\Http\Controllers\AcceptanceLetterController;
|
|||||||
use App\Http\Controllers\AcceptanceTemplateController;
|
use App\Http\Controllers\AcceptanceTemplateController;
|
||||||
use App\Http\Controllers\CompletionLetterController;
|
use App\Http\Controllers\CompletionLetterController;
|
||||||
use App\Http\Controllers\CompletionTemplateController;
|
use App\Http\Controllers\CompletionTemplateController;
|
||||||
|
use App\Http\Controllers\FileViewController;
|
||||||
|
use App\Http\Controllers\LetterController;
|
||||||
|
use App\Models\FileUpload;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| API Routes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you can register API routes for your application. These
|
||||||
|
| routes are loaded by the RouteServiceProvider within a group which
|
||||||
|
| is assigned the "api" middleware group.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Authentication Routes
|
||||||
Route::get('/user', function (Request $request) {
|
Route::get('/user', function (Request $request) {
|
||||||
return $request->user();
|
return $request->user();
|
||||||
})->middleware('auth:sanctum');
|
})->middleware('auth:sanctum');
|
||||||
|
|
||||||
// Route::middleware('auth:sanctum')->group(function () {
|
// Letter Management Routes
|
||||||
Route::apiResource('acceptance_letters', AcceptanceLetterController::class);
|
Route::apiResource('letters', LetterController::class);
|
||||||
Route::apiResource('completion_letters', CompletionLetterController::class);
|
|
||||||
Route::apiResource('completion_templates', CompletionTemplateController::class);
|
// File Attachment Routes
|
||||||
Route::apiResource('acceptance_templates', AcceptanceTemplateController::class);
|
Route::get('/lampiran/{id}', [LetterController::class, 'letterLampiran'])
|
||||||
// });
|
->name('letters.lampiran');
|
||||||
|
|
||||||
|
// Template Management Routes
|
||||||
|
Route::apiResource('completion_templates', CompletionTemplateController::class);
|
||||||
|
Route::apiResource('acceptance_templates', AcceptanceTemplateController::class);
|
||||||
|
|
||||||
|
// Letter Type Routes
|
||||||
|
Route::apiResource('acceptance_letters', AcceptanceLetterController::class);
|
||||||
|
Route::apiResource('completion_letters', CompletionLetterController::class);
|
||||||
|
|
||||||
|
// File Viewer Route (Internal use for signed URLs)
|
||||||
|
Route::get('/file/{id}', [FileViewController::class, 'show'])
|
||||||
|
->name('file.view')
|
||||||
|
->middleware('signed');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Endpoints Documentation:
|
||||||
|
*
|
||||||
|
* GET /api/user - Get authenticated user
|
||||||
|
*
|
||||||
|
* Letters:
|
||||||
|
* GET /api/letters - List all letters
|
||||||
|
* POST /api/letters - Create new letter
|
||||||
|
* GET /api/letters/{id} - Get letter details
|
||||||
|
* PUT /api/letters/{id} - Update letter
|
||||||
|
* DELETE /api/letters/{id} - Delete letter
|
||||||
|
* GET /api/letters/lampiran/{id} - Get file attachment URL
|
||||||
|
*
|
||||||
|
* Templates:
|
||||||
|
* GET /api/completion_templates - List completion templates
|
||||||
|
* GET /api/acceptance_templates - List acceptance templates
|
||||||
|
*
|
||||||
|
* Letter Types:
|
||||||
|
* GET /api/acceptance_letters - List acceptance letters
|
||||||
|
* GET /api/completion_letters - List completion letters
|
||||||
|
*
|
||||||
|
* File Handling:
|
||||||
|
* GET /api/file/{id} - View file (internal, signed URL)
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
Reference in New Issue
Block a user