diff --git a/BEST_PRACTICE.md b/BEST_PRACTICE.md index cbe387a..4fe93e6 100644 --- a/BEST_PRACTICE.md +++ b/BEST_PRACTICE.md @@ -161,6 +161,21 @@ ## 4. Controller, Request, dan Service - **Controller** hanya mengatur alur request → response (validasi input dipanggil, service dipanggil, redirect/Inertia render dikembalikan). **Tidak boleh** ada query Eloquent kompleks atau business logic langsung di controller. - **Selalu gunakan FormRequest**, sekecil apapun validasinya — jangan validasi inline di controller dengan `$request->validate()`. - Business logic (kalkulasi, orkestrasi antar model, side effect seperti notifikasi/log) **disimpan di Service**, bukan di controller atau model. +- **Kalau method Service butuh user yang sedang login untuk scoping/filtering** (mis. dosen cuma lihat kelasnya sendiri, mahasiswa cuma lihat jurusannya sendiri), terima sebagai parameter eksplisit `User $user` (non-nullable, tanpa default) di **posisi pertama** — jangan panggil `auth()->user()` langsung di dalam Service. Controller yang menyuplainya lewat `$request->user()`. Ini soal testability (Service tidak bergantung diam-diam ke global state) dan konsistensi lintas Service. + ```php + // Bad + public function paginated(int $perPage = 25): LengthAwarePaginator + { + $user = auth()->user(); + // ... + } + + // Good + public function paginated(User $user, int $perPage = 25): LengthAwarePaginator + { + // ... + } + ``` - Otorisasi berbasis permission (Spatie) dicek di dua tempat: - Route-level: middleware `permission:create-xxx` dipasang per-route/per-group. - Object-level (mis. user hanya boleh mengubah record miliknya sendiri): di `authorize()` milik FormRequest, kombinasikan `$this->user()->can('permission-name')` dengan pengecekan kepemilikan record. diff --git a/app/Http/Controllers/Admin/Master/CourseController.php b/app/Http/Controllers/Admin/Master/CourseController.php index 7d577fd..8a84e10 100644 --- a/app/Http/Controllers/Admin/Master/CourseController.php +++ b/app/Http/Controllers/Admin/Master/CourseController.php @@ -23,6 +23,7 @@ public function index(PaginatedRequest $request): Response { return Inertia::render('admin/master/courses/index', [ 'courses' => $this->service->paginated( + $request->user(), ...$request->validatedWithDefaults(), departmentId: $request->validated('department_id'), semesterNumber: $request->validated('semester_number'), diff --git a/app/Services/Admin/Master/CourseService.php b/app/Services/Admin/Master/CourseService.php index 6dd36bf..d736ef1 100644 --- a/app/Services/Admin/Master/CourseService.php +++ b/app/Services/Admin/Master/CourseService.php @@ -2,52 +2,28 @@ namespace App\Services\Admin\Master; +use App\Enums\UserRole; use App\Models\Course; +use App\Models\User; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; class CourseService { - public function getAllForSelect(): Collection + public function paginated(User $user, int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $semesterNumber = null): LengthAwarePaginator { return Course::query() - ->select(['courses.id', 'courses.code', 'courses.name', 'courses.department_id', 'courses.semester_number']) - ->join('departments', 'departments.id', '=', 'courses.department_id') - ->with('department:id,name') - ->orderBy('departments.name') - ->orderBy('courses.semester_number') - ->orderBy('courses.name') - ->get(); - } - - public function getSemesterNumbers(): array - { - return Course::query() - ->select('semester_number') - ->whereNotNull('semester_number') - ->distinct() - ->orderBy('semester_number') - ->pluck('semester_number') - ->all(); - } - - public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $semesterNumber = null): LengthAwarePaginator - { - $user = auth()->user(); - - return Course::query() - ->select(['courses.id', 'courses.code', 'courses.name', 'courses.credits', 'courses.department_id', 'courses.semester_number']) ->join('departments', 'departments.id', '=', 'courses.department_id') ->with('department:id,name') ->when($search, fn ($q) => $q->where('courses.name', 'like', "%{$search}%")->orWhere('courses.code', 'like', "%{$search}%")) ->when($departmentId, fn ($q) => $q->where('courses.department_id', $departmentId)) ->when($semesterNumber, fn ($q) => $q->where('courses.semester_number', $semesterNumber)) - ->when($user->hasRole('mahasiswa'), fn ($q) => $q->where('courses.department_id', $user->student?->department_id)) - ->when($user->hasRole('dosen'), fn ($q) => $q->whereIn('courses.department_id', $user->lecturer?->departments()->pluck('departments.id') ?? [])) + ->when($user->hasRole(UserRole::Mahasiswa->value), fn ($q) => $q->where('courses.department_id', $user->student?->department_id)) + ->when($user->hasRole(UserRole::Dosen->value), fn ($q) => $q->whereIn('courses.department_id', $user->lecturer?->departments()->pluck('departments.id') ?? [])) ->orderBy('departments.name') ->orderBy('courses.semester_number') ->orderBy('courses.name') - ->paginate($perPage); + ->paginate($perPage, ['courses.id', 'courses.code', 'courses.name', 'courses.credits', 'courses.department_id', 'courses.semester_number']); } public function create(array $data): Course @@ -57,12 +33,13 @@ public function create(array $data): Course public function update(Course $course, array $data): Course { - $course->code = $data['code']; - $course->name = $data['name']; - $course->credits = $data['credits']; - $course->department_id = $data['department_id']; - $course->semester_number = $data['semester_number'] ?? null; - $course->update(); + $course->update([ + 'code' => $data['code'], + 'name' => $data['name'], + 'credits' => $data['credits'], + 'department_id' => $data['department_id'], + 'semester_number' => $data['semester_number'] ?? null, + ]); return $course; } @@ -71,4 +48,25 @@ public function delete(Course $course): bool { return $course->delete(); } + + public function getAllForSelect(): Collection + { + return Course::query() + ->join('departments', 'departments.id', '=', 'courses.department_id') + ->with('department:id,name') + ->orderBy('departments.name') + ->orderBy('courses.semester_number') + ->orderBy('courses.name') + ->get(['courses.id', 'courses.code', 'courses.name', 'courses.department_id', 'courses.semester_number']); + } + + public function getSemesterNumbers(): array + { + return Course::query() + ->whereNotNull('semester_number') + ->distinct() + ->orderBy('semester_number') + ->pluck('semester_number') + ->all(); + } }