repository = $repository; } /** * Display a listing of the resource. */ public function index(Request $request): JsonResponse { $this->authorize('viewAny', $this->modelClass); try { $perPage = $request->get('per_page', 10) ?? 10; $perPage = min($perPage, 1000); // Limit max per page to 1000 $search = (string) ($request->get('search', '') ?? ''); $sortBy = (string) ($request->get('sort_by', 'id') ?? 'id'); $sortOrder = (string) ($request->get('sort_order', 'asc') ?? 'asc'); $items = $this->getIndexData($request, $perPage, $search, $sortBy, $sortOrder); // Check if the result is paginated if ($items instanceof LengthAwarePaginator) { return response()->json([ 'success' => true, 'data' => $this->resourceClass::collection($items->items()), 'pagination' => [ 'current_page' => $items->currentPage(), 'per_page' => $items->perPage(), 'total' => $items->total(), 'last_page' => $items->lastPage(), 'from' => $items->firstItem(), 'to' => $items->lastItem(), 'has_more_pages' => $items->hasMorePages(), ], 'message' => $this->getSuccessMessage('index'), ]); } // Fallback for non-paginated results return response()->json([ 'success' => true, 'data' => $this->resourceClass::collection($items), 'message' => $this->getSuccessMessage('index'), ]); } catch (Exception $e) { Log::error("Error fetching {$this->resourceNamePlural}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('index'), 500); } } /** * Store a newly created resource in storage. */ public function store(Request $request): JsonResponse { $this->authorize('create', $this->modelClass); try { $validated = $this->validateRequest($request); $data = $this->prepareStoreData($validated); $item = $this->repository->create($data); ActivityLogger::log("Created {$this->resourceName}: {$this->getItemName($item)}", $item); return response()->json([ 'success' => true, 'data' => new $this->resourceClass($item), 'message' => $this->getSuccessMessage('store'), ], 201); } catch (Exception $e) { Log::error("Error creating {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('store').': '.$e->getMessage(), 500); } } /** * Display the specified resource. */ public function show(string $id): JsonResponse { $this->authorize('view', $this->modelClass); try { $item = $this->repository->findById($id); if (! $item) { return $this->errorResponse($this->getNotFoundMessage(), 404); } $item = $this->loadShowRelations($item); return response()->json([ 'success' => true, 'data' => new $this->resourceClass($item), 'message' => $this->getSuccessMessage('show'), ]); } catch (Exception $e) { Log::error("Error fetching {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('show'), 500); } } /** * Update the specified resource in storage. */ public function update(Request $request, string $id): JsonResponse { $this->authorize('update', $this->modelClass); try { $item = $this->repository->findById($id); if (! $item) { return $this->errorResponse($this->getNotFoundMessage(), 404); } $validated = $this->validateRequest($request); $data = $this->prepareUpdateData($validated, $item); $item->update($data); ActivityLogger::log("Updated {$this->resourceName}: {$this->getItemName($item)}", $item); return response()->json([ 'success' => true, 'data' => new $this->resourceClass($item), 'message' => $this->getSuccessMessage('update'), ]); } catch (Exception $e) { Log::error("Error updating {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('update').': '.$e->getMessage(), 500); } } /** * Partially update the specified resource in storage (PATCH). */ public function patch(Request $request, string $id): JsonResponse { // PATCH uses the same logic as update return $this->update($request, $id); } /** * Remove the specified resource from storage. */ public function destroy(string $id): JsonResponse { $this->authorize('deleteAny', $this->modelClass); try { $item = $this->repository->findById($id); if (! $item) { return $this->errorResponse($this->getNotFoundMessage(), 404); } // Check for dependencies before deletion $dependencyCheck = $this->checkDependencies($item); if ($dependencyCheck !== null) { return $dependencyCheck; } $this->repository->delete($id); ActivityLogger::log("Deleted {$this->resourceName}: {$this->getItemName($item)}", $item); return response()->json([ 'success' => true, 'message' => $this->getSuccessMessage('destroy'), ]); } catch (Exception $e) { Log::error("Error deleting {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('destroy').': '.$e->getMessage(), 500); } } /** * Get data for index method - can be overridden by child classes */ protected function getIndexData(Request $request, int $perPage = 10, string $search = '', string $sortBy = 'id', string $sortOrder = 'asc') { // Always try paginated methods first when perPage is specified if ($perPage > 0) { if (method_exists($this->repository, 'getAllWithRelationsPaginated')) { return $this->repository->getAllWithRelationsPaginated($perPage, $search, $sortBy, $sortOrder); } if (method_exists($this->repository, 'getAllPaginated')) { return $this->repository->getAllPaginated($perPage, $search, $sortBy, $sortOrder); } } // Fallback to non-paginated methods if (method_exists($this->repository, 'getAllWithRelations')) { return $this->repository->getAllWithRelations($search, $sortBy, $sortOrder); } return $this->repository->all($search, $sortBy, $sortOrder); } /** * Validate request data - can be overridden by child classes */ protected function validateRequest(Request $request): array { if ($this->requestClass) { // For multipart/form-data, use direct validation to avoid FormRequest parsing issues if (str_contains($request->header('Content-Type', ''), 'multipart/form-data')) { return $request->validate(app($this->requestClass)->rules()); } // Create FormRequest instance with the current request $formRequest = $this->requestClass::createFrom($request); $formRequest->setContainer(app()); $formRequest->setRedirector(app('redirect')); // Validate the request $formRequest->validateResolved(); return $formRequest->validated(); } return $request->all(); } /** * Prepare data for store method - can be overridden by child classes */ protected function prepareStoreData(array $validated): array { return $validated; } /** * Prepare data for update method - can be overridden by child classes */ protected function prepareUpdateData(array $validated, $item): array { return $validated; } /** * Load relations for show method - can be overridden by child classes */ protected function loadShowRelations($item) { return $item; } /** * Check dependencies before deletion - can be overridden by child classes * Return null if deletion is allowed, or a JsonResponse if not */ protected function checkDependencies($item): ?JsonResponse { return null; } /** * Get the name of the item for logging - can be overridden by child classes */ protected function getItemName($item): string { return $item->name ?? $item->id; } /** * Get success message for different operations */ protected function getSuccessMessage(string $operation): string { $messages = [ 'index' => ucfirst($this->resourceNamePlural).' berjaya dimuatkan', 'store' => ucfirst($this->resourceName).' berjaya ditambah', 'show' => ucfirst($this->resourceName).' berjaya dimuatkan', 'update' => ucfirst($this->resourceName).' berjaya dikemaskini', 'destroy' => ucfirst($this->resourceName).' berjaya dipadam', ]; return $messages[$operation] ?? 'Operasi berjaya'; } /** * Get error message for different operations */ protected function getErrorMessage(string $operation): string { $messages = [ 'index' => 'Gagal memuatkan '.$this->resourceNamePlural, 'store' => 'Gagal menambah '.$this->resourceName, 'show' => 'Gagal memuatkan '.$this->resourceName, 'update' => 'Gagal mengemaskini '.$this->resourceName, 'destroy' => 'Gagal memadam '.$this->resourceName, ]; return $messages[$operation] ?? 'Operasi gagal'; } /** * Get not found message */ protected function getNotFoundMessage(): string { return ucfirst($this->resourceName).' tidak dijumpai.'; } /** * Return a standardized error response */ protected function errorResponse(string $message, int $statusCode = 400): JsonResponse { return response()->json([ 'success' => false, 'message' => $message, ], $statusCode); } }