Files
fotospiel-app/app/Http/Controllers/Api/Support/SupportResourceController.php
Codex Agent 53a6500e6a
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Add support API scaffold
2026-01-28 13:52:47 +01:00

309 lines
8.7 KiB
PHP

<?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
);
}
}