feat(demo): add story 1 — Sorano: Rock and Time
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Api\Response;
|
||||
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class ApiResponse
|
||||
{
|
||||
/**
|
||||
* Create a standard JSON response with the data envelope.
|
||||
*/
|
||||
public static function create(mixed $data, int $status = 200, array $headers = [], ?array $meta = null): ResponseInterface
|
||||
{
|
||||
$body = [
|
||||
'data' => $data,
|
||||
];
|
||||
if ($meta !== null) {
|
||||
$body['meta'] = $meta;
|
||||
}
|
||||
|
||||
$headers = array_merge($headers, [
|
||||
'Content-Type' => 'application/json',
|
||||
'Cache-Control' => 'no-store, max-age=0',
|
||||
]);
|
||||
|
||||
return new Response($status, $headers, json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a paginated response with meta and links.
|
||||
*/
|
||||
public static function paginated(
|
||||
array $data,
|
||||
int $total,
|
||||
int $page,
|
||||
int $perPage,
|
||||
string $baseUrl,
|
||||
int $status = 200,
|
||||
array $headers = [],
|
||||
array $extraMeta = [],
|
||||
?int $locatedAtIndex = null,
|
||||
): ResponseInterface {
|
||||
$totalPages = $perPage > 0 ? (int) ceil($total / $perPage) : 1;
|
||||
|
||||
$pagination = [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total' => $total,
|
||||
'total_pages' => $totalPages,
|
||||
];
|
||||
if ($locatedAtIndex !== null) {
|
||||
$pagination['located_at_index'] = $locatedAtIndex;
|
||||
}
|
||||
|
||||
$meta = [
|
||||
'pagination' => $pagination,
|
||||
];
|
||||
|
||||
if ($extraMeta !== []) {
|
||||
$meta = array_merge($meta, $extraMeta);
|
||||
}
|
||||
|
||||
$body = [
|
||||
'data' => $data,
|
||||
'meta' => $meta,
|
||||
'links' => [
|
||||
'self' => $baseUrl . '?' . http_build_query(['page' => $page, 'per_page' => $perPage]),
|
||||
],
|
||||
];
|
||||
|
||||
if ($page > 1) {
|
||||
$body['links']['first'] = $baseUrl . '?' . http_build_query(['page' => 1, 'per_page' => $perPage]);
|
||||
$body['links']['prev'] = $baseUrl . '?' . http_build_query(['page' => $page - 1, 'per_page' => $perPage]);
|
||||
}
|
||||
|
||||
if ($page < $totalPages) {
|
||||
$body['links']['next'] = $baseUrl . '?' . http_build_query(['page' => $page + 1, 'per_page' => $perPage]);
|
||||
$body['links']['last'] = $baseUrl . '?' . http_build_query(['page' => $totalPages, 'per_page' => $perPage]);
|
||||
}
|
||||
|
||||
$headers = array_merge($headers, [
|
||||
'Content-Type' => 'application/json',
|
||||
'Cache-Control' => 'no-store, max-age=0',
|
||||
]);
|
||||
|
||||
return new Response($status, $headers, json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 200 OK with data envelope.
|
||||
*/
|
||||
public static function ok(mixed $data, array $headers = []): ResponseInterface
|
||||
{
|
||||
return self::create($data, 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 201 Created with Location header.
|
||||
*/
|
||||
public static function created(mixed $data, string $location, array $headers = []): ResponseInterface
|
||||
{
|
||||
return self::create($data, 201, array_merge($headers, ['Location' => $location]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 204 No Content.
|
||||
*/
|
||||
public static function noContent(array $headers = []): ResponseInterface
|
||||
{
|
||||
return new Response(204, $headers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Api\Response;
|
||||
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Plugin\Api\Exceptions\ApiException;
|
||||
use Grav\Plugin\Api\Exceptions\ValidationException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* RFC 7807 Problem Details response builder.
|
||||
*/
|
||||
class ErrorResponse
|
||||
{
|
||||
/**
|
||||
* @param array<string,mixed> $headers
|
||||
* @param array<string,mixed>|null $toast Optional toast hint honored by Admin
|
||||
* Next: { message?, type?, duration?, dismissible? }. `duration` is in ms;
|
||||
* use 0 (or dismissible:true) for a toast that stays until manually closed.
|
||||
*/
|
||||
public static function create(int $status, string $title, string $detail, array $headers = [], ?array $toast = null): ResponseInterface
|
||||
{
|
||||
$body = [
|
||||
'status' => $status,
|
||||
'title' => $title,
|
||||
'detail' => $detail,
|
||||
];
|
||||
if ($toast !== null) {
|
||||
$body['toast'] = $toast;
|
||||
}
|
||||
|
||||
$headers = array_merge($headers, [
|
||||
'Content-Type' => 'application/problem+json',
|
||||
'Cache-Control' => 'no-store, max-age=0',
|
||||
]);
|
||||
|
||||
return new Response($status, $headers, json_encode($body, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
public static function fromException(ApiException $e): ResponseInterface
|
||||
{
|
||||
$body = [
|
||||
'status' => $e->getStatusCode(),
|
||||
'title' => $e->getErrorTitle(),
|
||||
'detail' => $e->getMessage(),
|
||||
];
|
||||
|
||||
if ($e instanceof ValidationException && $e->getValidationErrors()) {
|
||||
$body['errors'] = $e->getValidationErrors();
|
||||
}
|
||||
|
||||
$headers = array_merge($e->getHeaders(), [
|
||||
'Content-Type' => 'application/problem+json',
|
||||
'Cache-Control' => 'no-store, max-age=0',
|
||||
]);
|
||||
|
||||
return new Response($e->getStatusCode(), $headers, json_encode($body, JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user