feat: update CourseService and CourseController to accept User parameter for improved scoping

This commit is contained in:
Yoga Pangestu 2026-09-03 13:45:27 +07:00
parent 2b458e26fa
commit 1c901e9103
3 changed files with 50 additions and 36 deletions

View File

@ -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.

View File

@ -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'),

View File

@ -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();
}
}