Manage redirects, missing URLs, repositories and settings through the API
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
68
app/Http/Controllers/Api/V1/MaintenanceController.php
Normal file
68
app/Http/Controllers/Api/V1/MaintenanceController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Support\Maintenance;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* The public site's maintenance mode — the panel's maintenance screen, as two
|
||||
* calls. Only the public site goes down; the panel and this API keep working.
|
||||
*/
|
||||
class MaintenanceController extends Controller
|
||||
{
|
||||
public function show(): JsonResponse
|
||||
{
|
||||
$this->authorize('settings.view');
|
||||
|
||||
return $this->state();
|
||||
}
|
||||
|
||||
/**
|
||||
* `enabled` switches it; `message` (language → text) and `until` are kept
|
||||
* either way, so the wording can be prepared before the site goes down.
|
||||
*/
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
$this->authorize('settings.maintenance');
|
||||
|
||||
$data = $request->validate([
|
||||
'enabled' => ['required', 'boolean'],
|
||||
'message' => ['sometimes', 'nullable', 'array'],
|
||||
'message.*' => ['nullable', 'string', 'max:500'],
|
||||
'until' => ['sometimes', 'nullable', 'date', 'after:now'],
|
||||
]);
|
||||
|
||||
$message = array_key_exists('message', $data)
|
||||
? array_filter((array) $data['message'], fn ($text) => filled($text))
|
||||
: Maintenance::messages();
|
||||
|
||||
if ($data['enabled']) {
|
||||
Maintenance::enable(
|
||||
message: $message,
|
||||
until: filled($data['until'] ?? null) ? CarbonImmutable::parse($data['until']) : null,
|
||||
);
|
||||
} else {
|
||||
// Switching it off must not take the prepared wording with it.
|
||||
Setting::set(Maintenance::KEY, [...Maintenance::state(), 'enabled' => false, 'until' => null, 'message' => $message]);
|
||||
}
|
||||
|
||||
return $this->state();
|
||||
}
|
||||
|
||||
protected function state(): JsonResponse
|
||||
{
|
||||
return response()->json(['data' => [
|
||||
'enabled' => Maintenance::isEnabled(),
|
||||
'message' => (object) Maintenance::messages(),
|
||||
'until' => Maintenance::until()?->toIso8601String(),
|
||||
'started_at' => Maintenance::startedAt()?->toIso8601String(),
|
||||
]]);
|
||||
}
|
||||
}
|
||||
95
app/Http/Controllers/Api/V1/MissingUrlController.php
Normal file
95
app/Http/Controllers/Api/V1/MissingUrlController.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Enums\MissingUrlStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\V1\MissingUrlRedirectRequest;
|
||||
use App\Http\Resources\MissingUrlResource;
|
||||
use App\Http\Resources\RedirectResource;
|
||||
use App\Models\MissingUrl;
|
||||
use App\Models\Redirect;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Addresses the public site was asked for and could not find, reported by the
|
||||
* frontend. What can be done with one is what the panel does: ignore it, or
|
||||
* give it a destination.
|
||||
*/
|
||||
class MissingUrlController extends Controller
|
||||
{
|
||||
/**
|
||||
* Filters: status (pending by default), locale, search (path).
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$this->authorize('viewAny', MissingUrl::class);
|
||||
|
||||
$missing = MissingUrl::query()
|
||||
->where('status', $request->string('status', MissingUrlStatus::Pending->value))
|
||||
->when($request->filled('locale'), fn ($q) => $q->where('locale', $request->string('locale')))
|
||||
->when($request->filled('search'), fn ($q) => $q->where('path', 'like', '%'.$request->string('search').'%'))
|
||||
->orderByDesc('hits')
|
||||
->orderByDesc('last_seen_at')
|
||||
->paginate(perPage: min($request->integer('per_page', 25), 100))
|
||||
->withQueryString();
|
||||
|
||||
return MissingUrlResource::collection($missing);
|
||||
}
|
||||
|
||||
public function show(MissingUrl $missingUrl): MissingUrlResource
|
||||
{
|
||||
$this->authorize('view', $missingUrl);
|
||||
|
||||
return MissingUrlResource::make($missingUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only between pending and ignored: `resolved` is what writing a redirect
|
||||
* makes it, and claiming it without one would be a lie the next sighting
|
||||
* undoes anyway.
|
||||
*/
|
||||
public function update(Request $request, MissingUrl $missingUrl): MissingUrlResource
|
||||
{
|
||||
$this->authorize('update', $missingUrl);
|
||||
|
||||
$data = $request->validate([
|
||||
'status' => ['required', Rule::in([MissingUrlStatus::Pending->value, MissingUrlStatus::Ignored->value])],
|
||||
]);
|
||||
|
||||
$missingUrl->update($data);
|
||||
|
||||
return MissingUrlResource::make($missingUrl);
|
||||
}
|
||||
|
||||
public function destroy(MissingUrl $missingUrl): JsonResponse
|
||||
{
|
||||
$this->authorize('delete', $missingUrl);
|
||||
|
||||
$missingUrl->delete();
|
||||
|
||||
return response()->json(status: 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the address a destination. The report is marked resolved by the
|
||||
* redirect being created, as from anywhere else.
|
||||
*/
|
||||
public function redirect(MissingUrlRedirectRequest $request, MissingUrl $missingUrl): JsonResponse
|
||||
{
|
||||
$this->authorize('redirect', $missingUrl);
|
||||
|
||||
$redirect = Redirect::create([
|
||||
'from' => $missingUrl->path,
|
||||
'is_enabled' => true,
|
||||
...$request->validated(),
|
||||
])->refresh();
|
||||
|
||||
return RedirectResource::make($redirect)->response()->setStatusCode(201);
|
||||
}
|
||||
}
|
||||
77
app/Http/Controllers/Api/V1/RedirectController.php
Normal file
77
app/Http/Controllers/Api/V1/RedirectController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\V1\RedirectRequest;
|
||||
use App\Http\Resources\RedirectResource;
|
||||
use App\Models\Redirect;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
/**
|
||||
* Old addresses and where they go now.
|
||||
*/
|
||||
class RedirectController extends Controller
|
||||
{
|
||||
/**
|
||||
* Filters: is_enabled, locale, automatic (left behind by a slug rename),
|
||||
* search (either end).
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$this->authorize('viewAny', Redirect::class);
|
||||
|
||||
$redirects = Redirect::query()
|
||||
->when($request->has('is_enabled'), fn ($q) => $q->where('is_enabled', $request->boolean('is_enabled')))
|
||||
->when($request->filled('locale'), fn ($q) => $q->where('locale', $request->string('locale')))
|
||||
->when($request->has('automatic'), fn ($q) => $request->boolean('automatic')
|
||||
? $q->whereNotNull('source_type')
|
||||
: $q->whereNull('source_type'))
|
||||
->when($request->filled('search'), function ($q) use ($request): void {
|
||||
$term = '%'.$request->string('search').'%';
|
||||
|
||||
$q->where(fn ($q) => $q->where('from', 'like', $term)->orWhere('to', 'like', $term));
|
||||
})
|
||||
->latest()
|
||||
->paginate(perPage: min($request->integer('per_page', 25), 100))
|
||||
->withQueryString();
|
||||
|
||||
return RedirectResource::collection($redirects);
|
||||
}
|
||||
|
||||
public function store(RedirectRequest $request): JsonResponse
|
||||
{
|
||||
$this->authorize('create', Redirect::class);
|
||||
|
||||
return RedirectResource::make(Redirect::create($request->validated())->refresh())->response()->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(Redirect $redirect): RedirectResource
|
||||
{
|
||||
$this->authorize('view', $redirect);
|
||||
|
||||
return RedirectResource::make($redirect);
|
||||
}
|
||||
|
||||
public function update(RedirectRequest $request, Redirect $redirect): RedirectResource
|
||||
{
|
||||
$this->authorize('update', $redirect);
|
||||
|
||||
$redirect->update($request->validated());
|
||||
|
||||
return RedirectResource::make($redirect);
|
||||
}
|
||||
|
||||
public function destroy(Redirect $redirect): JsonResponse
|
||||
{
|
||||
$this->authorize('delete', $redirect);
|
||||
|
||||
$redirect->delete();
|
||||
|
||||
return response()->json(status: 204);
|
||||
}
|
||||
}
|
||||
87
app/Http/Controllers/Api/V1/RepositoryController.php
Normal file
87
app/Http/Controllers/Api/V1/RepositoryController.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\V1\RepositoryRequest;
|
||||
use App\Http\Resources\RepositoryResource;
|
||||
use App\Jobs\SyncRepository;
|
||||
use App\Models\Repository;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
/**
|
||||
* The collective's git repositories. Only which repository, whose project and
|
||||
* whether to show it are written here; everything else is what the provider
|
||||
* last reported.
|
||||
*/
|
||||
class RepositoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Filters: provider, project_id, show_publicly, failing, search (full name).
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$this->authorize('viewAny', Repository::class);
|
||||
|
||||
$repositories = Repository::query()
|
||||
->when($request->filled('provider'), fn ($q) => $q->where('provider', $request->string('provider')))
|
||||
->when($request->filled('project_id'), fn ($q) => $q->where('project_id', $request->integer('project_id')))
|
||||
->when($request->has('show_publicly'), fn ($q) => $q->where('show_publicly', $request->boolean('show_publicly')))
|
||||
->when($request->boolean('failing'), fn ($q) => $q->failing())
|
||||
->when($request->filled('search'), fn ($q) => $q->where('full_name', 'like', '%'.$request->string('search').'%'))
|
||||
->orderBy('full_name')
|
||||
->paginate(perPage: min($request->integer('per_page', 25), 100))
|
||||
->withQueryString();
|
||||
|
||||
return RepositoryResource::collection($repositories);
|
||||
}
|
||||
|
||||
public function store(RepositoryRequest $request): JsonResponse
|
||||
{
|
||||
$this->authorize('create', Repository::class);
|
||||
|
||||
return RepositoryResource::make(Repository::create($request->validated())->refresh())->response()->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(Repository $repository): RepositoryResource
|
||||
{
|
||||
$this->authorize('view', $repository);
|
||||
|
||||
return RepositoryResource::make($repository);
|
||||
}
|
||||
|
||||
public function update(RepositoryRequest $request, Repository $repository): RepositoryResource
|
||||
{
|
||||
$this->authorize('update', $repository);
|
||||
|
||||
$repository->update($request->validated());
|
||||
|
||||
return RepositoryResource::make($repository);
|
||||
}
|
||||
|
||||
public function destroy(Repository $repository): JsonResponse
|
||||
{
|
||||
$this->authorize('delete', $repository);
|
||||
|
||||
$repository->delete();
|
||||
|
||||
return response()->json(status: 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the provider again, on the queue: a sync spends a rate limit the
|
||||
* whole application shares, and a caller should not wait on it.
|
||||
*/
|
||||
public function sync(Repository $repository): JsonResponse
|
||||
{
|
||||
$this->authorize('sync', $repository);
|
||||
|
||||
SyncRepository::dispatch($repository);
|
||||
|
||||
return RepositoryResource::make($repository)->response()->setStatusCode(202);
|
||||
}
|
||||
}
|
||||
56
app/Http/Controllers/Api/V1/SettingController.php
Normal file
56
app/Http/Controllers/Api/V1/SettingController.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\SettingResource;
|
||||
use App\Models\Setting;
|
||||
use App\Support\ContentVersion;
|
||||
use App\Support\Maintenance;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
/**
|
||||
* Runtime settings, as the panel's settings screen shows them: the ones that
|
||||
* exist, each value as the JSON it is. None is created or deleted from here.
|
||||
*/
|
||||
class SettingController extends Controller
|
||||
{
|
||||
/**
|
||||
* Written by the application, never by hand: a hand-set content version
|
||||
* would leave the frontend's cache serving stale pages or refetching them
|
||||
* for nothing.
|
||||
*/
|
||||
protected const READ_ONLY = [ContentVersion::KEY];
|
||||
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$this->authorize('settings.view');
|
||||
|
||||
return SettingResource::collection(Setting::query()->orderBy('key')->get());
|
||||
}
|
||||
|
||||
public function show(Setting $setting): SettingResource
|
||||
{
|
||||
$this->authorize('settings.view');
|
||||
|
||||
return SettingResource::make($setting);
|
||||
}
|
||||
|
||||
public function update(Request $request, Setting $setting): SettingResource
|
||||
{
|
||||
// Taking the site down is its own permission, in the panel as here.
|
||||
$this->authorize($setting->key === Maintenance::KEY ? 'settings.maintenance' : 'settings.update');
|
||||
|
||||
abort_if(in_array($setting->key, self::READ_ONLY, true), 403, __('rules.settings.read_only'));
|
||||
|
||||
// `present` rather than `required`: null is a value a setting may hold.
|
||||
$request->validate(['value' => ['present']]);
|
||||
|
||||
$setting->update(['value' => $request->input('value')]);
|
||||
|
||||
return SettingResource::make($setting);
|
||||
}
|
||||
}
|
||||
68
app/Http/Requests/Api/V1/MissingUrlRedirectRequest.php
Normal file
68
app/Http/Requests/Api/V1/MissingUrlRedirectRequest.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1;
|
||||
|
||||
use App\Models\MissingUrl;
|
||||
use App\Models\Redirect;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Where a missing address should go. The origin is the address itself.
|
||||
*/
|
||||
class MissingUrlRedirectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if (is_string($this->input('to')) && filled($this->input('to'))) {
|
||||
$this->merge(['to' => Redirect::normalise($this->input('to'))]);
|
||||
}
|
||||
|
||||
// The language it was seen in, unless told otherwise; null for all.
|
||||
if (! $this->exists('locale')) {
|
||||
$this->merge(['locale' => $this->missingUrl()->locale]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$path = $this->missingUrl()->path;
|
||||
$locale = $this->input('locale');
|
||||
|
||||
return [
|
||||
'to' => ['required', 'string', 'max:500', Rule::notIn([$path])],
|
||||
'status' => ['sometimes', 'integer', Rule::in([301, 302])],
|
||||
'locale' => [
|
||||
'nullable',
|
||||
'string',
|
||||
Rule::in(array_keys((array) config('locales.supported'))),
|
||||
// Already answered by a rule: edit that one instead.
|
||||
function (string $attribute, mixed $value, \Closure $fail) use ($path, $locale): void {
|
||||
$exists = Redirect::query()
|
||||
->where('from', $path)
|
||||
->when($locale === null, fn ($q) => $q->whereNull('locale'), fn ($q) => $q->where('locale', $locale))
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
$fail(__('validation.unique', ['attribute' => 'from']));
|
||||
}
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function missingUrl(): MissingUrl
|
||||
{
|
||||
return $this->route('missing_url');
|
||||
}
|
||||
}
|
||||
67
app/Http/Requests/Api/V1/RedirectRequest.php
Normal file
67
app/Http/Requests/Api/V1/RedirectRequest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1;
|
||||
|
||||
use App\Models\Redirect;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* The panel's redirect form, as rules. Both ends are normalised before they
|
||||
* are checked, so `/a/` and `/a` are the same rule however it was typed.
|
||||
*/
|
||||
class RedirectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
foreach (['from', 'to'] as $end) {
|
||||
if (is_string($this->input($end)) && filled($this->input($end))) {
|
||||
$this->merge([$end => Redirect::normalise($this->input($end))]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$redirect = $this->route('redirect');
|
||||
$creating = ! $redirect instanceof Redirect;
|
||||
$sometimes = $creating ? 'required' : 'sometimes';
|
||||
|
||||
$from = $this->input('from', $redirect?->from);
|
||||
$locale = $this->exists('locale') ? $this->input('locale') : $redirect?->locale;
|
||||
|
||||
return [
|
||||
'from' => [
|
||||
$sometimes,
|
||||
'string',
|
||||
'max:500',
|
||||
// One rule per address and language, as the table's index says.
|
||||
Rule::unique('redirects', 'from')
|
||||
->ignore($redirect?->id)
|
||||
->where(fn ($q) => $locale === null ? $q->whereNull('locale') : $q->where('locale', $locale)),
|
||||
],
|
||||
'to' => [$sometimes, 'string', 'max:500', Rule::notIn([$from])],
|
||||
'status' => ['sometimes', 'integer', Rule::in([301, 302])],
|
||||
'locale' => ['sometimes', 'nullable', 'string', Rule::in(array_keys((array) config('locales.supported')))],
|
||||
'is_enabled' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return ['to.not_in' => __('validation.different', ['attribute' => 'to', 'other' => 'from'])];
|
||||
}
|
||||
}
|
||||
50
app/Http/Requests/Api/V1/RepositoryRequest.php
Normal file
50
app/Http/Requests/Api/V1/RepositoryRequest.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1;
|
||||
|
||||
use App\Enums\GitProvider;
|
||||
use App\Models\Repository;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* The panel's repository form, as rules. The synced columns are not here on
|
||||
* purpose: nothing a caller sends may fake a repository's numbers.
|
||||
*/
|
||||
class RepositoryRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$repository = $this->route('repository');
|
||||
$creating = ! $repository instanceof Repository;
|
||||
$sometimes = $creating ? 'required' : 'sometimes';
|
||||
|
||||
$provider = $this->input('provider', $repository?->provider?->value);
|
||||
|
||||
return [
|
||||
'provider' => [$sometimes, Rule::enum(GitProvider::class)],
|
||||
'full_name' => [
|
||||
$sometimes,
|
||||
'string',
|
||||
'max:255',
|
||||
'regex:/^[\w.-]+\/[\w.-]+$|^[\w.-]+$/',
|
||||
Rule::unique('repositories', 'full_name')
|
||||
->ignore($repository?->id)
|
||||
->where('provider', $provider),
|
||||
],
|
||||
'project_id' => ['sometimes', 'nullable', 'integer', Rule::exists('projects', 'id')->whereNull('deleted_at')],
|
||||
'is_primary' => ['sometimes', 'boolean'],
|
||||
'show_publicly' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
35
app/Http/Resources/MissingUrlResource.php
Normal file
35
app/Http/Resources/MissingUrlResource.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\MissingUrl;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin MissingUrl
|
||||
*/
|
||||
class MissingUrlResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'path' => $this->path,
|
||||
'locale' => $this->locale,
|
||||
'referrer' => $this->referrer,
|
||||
// Our own page links here: more urgent than someone else's stale link.
|
||||
'internal_referrer' => $this->resource->isInternalReferrer(),
|
||||
'status' => $this->status->value,
|
||||
'hits' => (int) $this->hits,
|
||||
'first_seen_at' => $this->first_seen_at?->toIso8601String(),
|
||||
'last_seen_at' => $this->last_seen_at?->toIso8601String(),
|
||||
'resolved_at' => $this->resolved_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
35
app/Http/Resources/RedirectResource.php
Normal file
35
app/Http/Resources/RedirectResource.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Redirect;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin Redirect
|
||||
*/
|
||||
class RedirectResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'from' => $this->from,
|
||||
'to' => $this->to,
|
||||
'status' => $this->status,
|
||||
'locale' => $this->locale,
|
||||
'is_enabled' => (bool) $this->is_enabled,
|
||||
'automatic' => $this->resource->wasAutomatic(),
|
||||
'hits' => (int) $this->hits,
|
||||
'last_used_at' => $this->last_used_at?->toIso8601String(),
|
||||
'created_at' => $this->created_at?->toIso8601String(),
|
||||
'updated_at' => $this->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
51
app/Http/Resources/RepositoryResource.php
Normal file
51
app/Http/Resources/RepositoryResource.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Repository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin Repository
|
||||
*/
|
||||
class RepositoryResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'provider' => $this->provider->value,
|
||||
'full_name' => $this->full_name,
|
||||
'project_id' => $this->project_id,
|
||||
'is_primary' => (bool) $this->is_primary,
|
||||
'show_publicly' => (bool) $this->show_publicly,
|
||||
|
||||
// What the provider last reported.
|
||||
'html_url' => $this->html_url,
|
||||
'description' => $this->description,
|
||||
'homepage' => $this->homepage,
|
||||
'default_branch' => $this->default_branch,
|
||||
'primary_language' => $this->primary_language,
|
||||
'license' => $this->license,
|
||||
'topics' => $this->topics ?? [],
|
||||
'stars' => (int) $this->stars,
|
||||
'forks' => (int) $this->forks,
|
||||
'open_issues_count' => (int) $this->open_issues_count,
|
||||
'is_archived' => (bool) $this->is_archived,
|
||||
'pushed_at' => $this->pushed_at?->toIso8601String(),
|
||||
|
||||
'last_synced_at' => $this->last_synced_at?->toIso8601String(),
|
||||
'sync_error' => $this->sync_error,
|
||||
'sync_failures' => (int) $this->sync_failures,
|
||||
|
||||
'created_at' => $this->created_at?->toIso8601String(),
|
||||
'updated_at' => $this->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Http/Resources/SettingResource.php
Normal file
27
app/Http/Resources/SettingResource.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin Setting
|
||||
*/
|
||||
class SettingResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
'updated_at' => $this->updated_at?->toIso8601String(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user