Add support API scaffold
This commit is contained in:
308
app/Http/Controllers/Api/Support/SupportResourceController.php
Normal file
308
app/Http/Controllers/Api/Support/SupportResourceController.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Support;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Support\SupportResourceRequest;
|
||||
use App\Support\ApiError;
|
||||
use App\Support\SupportApiAuthorizer;
|
||||
use App\Support\SupportApiRegistry;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class SupportResourceController extends Controller
|
||||
{
|
||||
public function index(Request $request, string $resource): JsonResponse
|
||||
{
|
||||
if ($response = SupportApiAuthorizer::authorizeResource($request, $resource, 'read')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$config = SupportApiRegistry::get($resource);
|
||||
if (! $config) {
|
||||
return $this->resourceNotFoundResponse($resource);
|
||||
}
|
||||
|
||||
$modelClass = $config['model'];
|
||||
/** @var Builder $query */
|
||||
$query = $modelClass::query();
|
||||
|
||||
$relations = SupportApiRegistry::withRelations($resource);
|
||||
if ($relations !== []) {
|
||||
$query->with($relations);
|
||||
}
|
||||
|
||||
$this->applySearch($request, $query, $resource);
|
||||
$this->applySorting($request, $query, $resource);
|
||||
|
||||
$perPage = $this->resolvePerPage($request);
|
||||
$paginator = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'data' => $paginator->items(),
|
||||
'meta' => [
|
||||
'page' => $paginator->currentPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, string $resource, string $record): JsonResponse
|
||||
{
|
||||
if ($response = SupportApiAuthorizer::authorizeResource($request, $resource, 'read')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$model = $this->resolveRecord($resource, $record);
|
||||
|
||||
if (! $model) {
|
||||
return $this->resourceNotFoundResponse($resource, $record);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(SupportResourceRequest $request, string $resource): JsonResponse
|
||||
{
|
||||
if ($response = SupportApiAuthorizer::authorizeResource($request, $resource, 'write')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (! SupportApiRegistry::allowsMutation($resource, 'create')) {
|
||||
return $this->mutationNotAllowedResponse($resource, 'create');
|
||||
}
|
||||
|
||||
$config = SupportApiRegistry::get($resource);
|
||||
if (! $config) {
|
||||
return $this->resourceNotFoundResponse($resource);
|
||||
}
|
||||
|
||||
$modelClass = $config['model'];
|
||||
/** @var Model $model */
|
||||
$model = new $modelClass;
|
||||
|
||||
$payload = $this->filteredPayload($request, $model);
|
||||
|
||||
if ($payload === []) {
|
||||
return $this->emptyPayloadResponse($resource);
|
||||
}
|
||||
|
||||
$record = $modelClass::query()->create($payload);
|
||||
|
||||
return response()->json([
|
||||
'data' => $record,
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update(SupportResourceRequest $request, string $resource, string $record): JsonResponse
|
||||
{
|
||||
if ($response = SupportApiAuthorizer::authorizeResource($request, $resource, 'write')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (! SupportApiRegistry::allowsMutation($resource, 'update')) {
|
||||
return $this->mutationNotAllowedResponse($resource, 'update');
|
||||
}
|
||||
|
||||
$model = $this->resolveRecord($resource, $record);
|
||||
|
||||
if (! $model) {
|
||||
return $this->resourceNotFoundResponse($resource, $record);
|
||||
}
|
||||
|
||||
$payload = $this->filteredPayload($request, $model);
|
||||
|
||||
if ($payload === []) {
|
||||
return $this->emptyPayloadResponse($resource);
|
||||
}
|
||||
|
||||
$model->fill($payload);
|
||||
$model->save();
|
||||
|
||||
return response()->json([
|
||||
'data' => $model->refresh(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, string $resource, string $record): JsonResponse
|
||||
{
|
||||
if ($response = SupportApiAuthorizer::authorizeResource($request, $resource, 'write')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (! SupportApiRegistry::allowsMutation($resource, 'delete')) {
|
||||
return $this->mutationNotAllowedResponse($resource, 'delete');
|
||||
}
|
||||
|
||||
$model = $this->resolveRecord($resource, $record);
|
||||
|
||||
if (! $model) {
|
||||
return $this->resourceNotFoundResponse($resource, $record);
|
||||
}
|
||||
|
||||
$model->delete();
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
private function resolveRecord(string $resource, string $record): ?Model
|
||||
{
|
||||
$config = SupportApiRegistry::get($resource);
|
||||
|
||||
if (! $config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$modelClass = $config['model'];
|
||||
|
||||
$query = $modelClass::query();
|
||||
|
||||
if (is_numeric($record)) {
|
||||
return $query->find($record);
|
||||
}
|
||||
|
||||
$keyName = (new $modelClass)->getKeyName();
|
||||
|
||||
return $query->where($keyName, $record)->first();
|
||||
}
|
||||
|
||||
private function filteredPayload(SupportResourceRequest $request, Model $model): array
|
||||
{
|
||||
$payload = $request->validated('data');
|
||||
|
||||
if (! is_array($payload)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fillable = $model->getFillable();
|
||||
|
||||
if ($fillable === [] && method_exists($model, 'getGuarded') && $model->getGuarded() !== ['*']) {
|
||||
$columns = Schema::getColumnListing($model->getTable());
|
||||
|
||||
return Arr::only($payload, $columns);
|
||||
}
|
||||
|
||||
if ($fillable === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Arr::only($payload, $fillable);
|
||||
}
|
||||
|
||||
private function applySearch(Request $request, Builder $query, string $resource): void
|
||||
{
|
||||
$term = $request->string('search')->trim()->value();
|
||||
|
||||
if ($term === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = SupportApiRegistry::searchFields($resource);
|
||||
|
||||
if ($fields === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$columns = Schema::getColumnListing($query->getModel()->getTable());
|
||||
$fields = array_values(array_intersect($fields, $columns));
|
||||
|
||||
if ($fields === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->where(function (Builder $builder) use ($fields, $term): void {
|
||||
foreach ($fields as $field) {
|
||||
if ($field === 'id' && is_numeric($term)) {
|
||||
$builder->orWhere($field, (int) $term);
|
||||
} else {
|
||||
$builder->orWhere($field, 'like', "%{$term}%");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function applySorting(Request $request, Builder $query, string $resource): void
|
||||
{
|
||||
$sort = $request->string('sort')->trim()->value();
|
||||
|
||||
if ($sort === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$direction = 'asc';
|
||||
$field = $sort;
|
||||
|
||||
if (str_starts_with($sort, '-')) {
|
||||
$direction = 'desc';
|
||||
$field = ltrim($sort, '-');
|
||||
}
|
||||
|
||||
$allowed = SupportApiRegistry::searchFields($resource);
|
||||
$allowed[] = 'id';
|
||||
|
||||
$columns = Schema::getColumnListing($query->getModel()->getTable());
|
||||
$allowed = array_values(array_intersect($allowed, $columns));
|
||||
|
||||
if (! in_array($field, $allowed, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query->orderBy($field, $direction);
|
||||
}
|
||||
|
||||
private function resolvePerPage(Request $request): int
|
||||
{
|
||||
$default = (int) config('support-api.pagination.default_per_page', 50);
|
||||
$max = (int) config('support-api.pagination.max_per_page', 200);
|
||||
|
||||
$perPage = (int) $request->input('per_page', $default);
|
||||
|
||||
if ($perPage < 1) {
|
||||
$perPage = $default;
|
||||
}
|
||||
|
||||
return min($perPage, $max);
|
||||
}
|
||||
|
||||
private function mutationNotAllowedResponse(string $resource, string $action): JsonResponse
|
||||
{
|
||||
return ApiError::response(
|
||||
'support_mutation_not_allowed',
|
||||
'Mutation Not Allowed',
|
||||
"{$resource} does not allow {$action} operations in support API.",
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
private function emptyPayloadResponse(string $resource): JsonResponse
|
||||
{
|
||||
return ApiError::response(
|
||||
'support_invalid_payload',
|
||||
'Invalid Payload',
|
||||
"No mutable fields provided for {$resource}.",
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
private function resourceNotFoundResponse(string $resource, ?string $record = null): JsonResponse
|
||||
{
|
||||
$message = $record
|
||||
? "{$resource} record not found."
|
||||
: "Support resource {$resource} is not registered.";
|
||||
|
||||
return ApiError::response(
|
||||
'support_resource_not_found',
|
||||
'Not Found',
|
||||
$message,
|
||||
404
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user