379 lines
12 KiB
PHP
379 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Services\ActivityLogger;
|
|
use App\Traits\NotifiesAdmins;
|
|
use Exception;
|
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
abstract class BaseCrudController extends Controller
|
|
{
|
|
use AuthorizesRequests, NotifiesAdmins;
|
|
|
|
/**
|
|
* The repository interface for this controller
|
|
*/
|
|
protected $repository;
|
|
|
|
/**
|
|
* The model class for authorization
|
|
*/
|
|
protected $modelClass;
|
|
|
|
/**
|
|
* The resource class for API responses
|
|
*/
|
|
protected $resourceClass;
|
|
|
|
/**
|
|
* The request class for validation
|
|
*/
|
|
protected $requestClass;
|
|
|
|
/**
|
|
* The name of the resource for logging (e.g., 'category', 'model')
|
|
*/
|
|
protected $resourceName;
|
|
|
|
/**
|
|
* The plural name of the resource for messages
|
|
*/
|
|
protected $resourceNamePlural;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct($repository)
|
|
{
|
|
$this->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);
|
|
}
|
|
}
|