Manage clients, testimonials and talks through the API

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 19:38:58 +02:00
parent 96747abdc3
commit b02464a7ed
15 changed files with 1255 additions and 7 deletions

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\ClientRequest;
use App\Http\Resources\ClientResource;
use App\Models\Client;
use App\Support\TranslatablePayload;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
/**
* The organisations the work was for, as the panel edits them.
*/
class ClientController extends Controller
{
protected const WITH = ['logoMedia'];
/**
* Filters: is_public, search (name), trashed.
*/
public function index(Request $request): AnonymousResourceCollection
{
$this->authorize('viewAny', Client::class);
$clients = Client::query()
->with(self::WITH)
->withCount('projects')
->when($request->has('is_public'), fn ($q) => $q->where('is_public', $request->boolean('is_public')))
->when($request->filled('search'), fn ($q) => $q->where('name', 'like', '%'.$request->string('search').'%'))
->when($request->boolean('trashed'), fn ($q) => $q->onlyTrashed())
->orderBy('position')
->orderBy('name')
->paginate(perPage: min($request->integer('per_page', 25), 100))
->withQueryString();
return ClientResource::collection($clients);
}
public function store(ClientRequest $request): JsonResponse
{
$this->authorize('create', Client::class);
return ClientResource::make($this->save(new Client, $request))->response()->setStatusCode(201);
}
public function show(Client $client): ClientResource
{
$this->authorize('view', $client);
return ClientResource::make($client->load(self::WITH)->loadCount('projects'));
}
public function update(ClientRequest $request, Client $client): ClientResource
{
$this->authorize('update', $client);
return ClientResource::make($this->save($client, $request));
}
public function destroy(Client $client): JsonResponse
{
$this->authorize('delete', $client);
$client->delete();
return response()->json(status: 204);
}
public function restore(Client $client): ClientResource
{
$this->authorize('restore', $client);
$client->restore();
return ClientResource::make($client->load(self::WITH)->loadCount('projects'));
}
protected function save(Client $client, ClientRequest $request): Client
{
$client->fill(TranslatablePayload::merge($client, $request->validated(), $client->translatable));
$client->save();
return $client->load(self::WITH)->loadCount('projects');
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\TalkRequest;
use App\Http\Resources\TalkResource;
use App\Models\Talk;
use App\Support\TranslatablePayload;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
/**
* Talks given elsewhere. Deleted for good: there is no bin for them.
*/
class TalkController extends Controller
{
/**
* Filters: is_public, member_id, project_id, search (title or event).
*/
public function index(Request $request): AnonymousResourceCollection
{
$this->authorize('viewAny', Talk::class);
$talks = Talk::query()
->when($request->has('is_public'), fn ($q) => $q->where('is_public', $request->boolean('is_public')))
->when($request->filled('member_id'), fn ($q) => $q->where('member_id', $request->integer('member_id')))
->when($request->filled('project_id'), fn ($q) => $q->where('project_id', $request->integer('project_id')))
->when($request->filled('search'), function ($q) use ($request): void {
$term = '%'.$request->string('search').'%';
$q->where(fn ($q) => $q->where('title', 'like', $term)->orWhere('event', 'like', $term));
})
->recentFirst()
->paginate(perPage: min($request->integer('per_page', 25), 100))
->withQueryString();
return TalkResource::collection($talks);
}
public function store(TalkRequest $request): JsonResponse
{
$this->authorize('create', Talk::class);
return TalkResource::make($this->save(new Talk, $request))->response()->setStatusCode(201);
}
public function show(Talk $talk): TalkResource
{
$this->authorize('view', $talk);
return TalkResource::make($talk);
}
public function update(TalkRequest $request, Talk $talk): TalkResource
{
$this->authorize('update', $talk);
return TalkResource::make($this->save($talk, $request));
}
public function destroy(Talk $talk): JsonResponse
{
$this->authorize('delete', $talk);
$talk->delete();
return response()->json(status: 204);
}
protected function save(Talk $talk, TalkRequest $request): Talk
{
$talk->fill(TranslatablePayload::merge($talk, $request->validated(), $talk->translatable));
$talk->save();
return $talk;
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\TestimonialRequest;
use App\Http\Resources\TestimonialResource;
use App\Models\Testimonial;
use App\Support\TranslatablePayload;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
/**
* What people the collective worked with said about it.
*/
class TestimonialController extends Controller
{
protected const WITH = ['avatarMedia'];
/**
* Filters: is_public, client_id, project_id, service_id, search (author), trashed.
*/
public function index(Request $request): AnonymousResourceCollection
{
$this->authorize('viewAny', Testimonial::class);
$testimonials = Testimonial::query()
->with(self::WITH)
->when($request->has('is_public'), fn ($q) => $q->where('is_public', $request->boolean('is_public')))
->when($request->filled('client_id'), fn ($q) => $q->where('client_id', $request->integer('client_id')))
->when($request->filled('project_id'), fn ($q) => $q->where('project_id', $request->integer('project_id')))
->when($request->filled('service_id'), fn ($q) => $q->where('service_id', $request->integer('service_id')))
->when($request->filled('search'), fn ($q) => $q->where('author_name', 'like', '%'.$request->string('search').'%'))
->when($request->boolean('trashed'), fn ($q) => $q->onlyTrashed())
->orderBy('position')
->latest('id')
->paginate(perPage: min($request->integer('per_page', 25), 100))
->withQueryString();
return TestimonialResource::collection($testimonials);
}
public function store(TestimonialRequest $request): JsonResponse
{
$this->authorize('create', Testimonial::class);
return TestimonialResource::make($this->save(new Testimonial, $request))->response()->setStatusCode(201);
}
public function show(Testimonial $testimonial): TestimonialResource
{
$this->authorize('view', $testimonial);
return TestimonialResource::make($testimonial->load(self::WITH));
}
public function update(TestimonialRequest $request, Testimonial $testimonial): TestimonialResource
{
$this->authorize('update', $testimonial);
return TestimonialResource::make($this->save($testimonial, $request));
}
public function destroy(Testimonial $testimonial): JsonResponse
{
$this->authorize('delete', $testimonial);
$testimonial->delete();
return response()->json(status: 204);
}
public function restore(Testimonial $testimonial): TestimonialResource
{
$this->authorize('restore', $testimonial);
$testimonial->restore();
return TestimonialResource::make($testimonial->load(self::WITH));
}
protected function save(Testimonial $testimonial, TestimonialRequest $request): Testimonial
{
$testimonial->fill(TranslatablePayload::merge($testimonial, $request->validated(), $testimonial->translatable));
$testimonial->save();
return $testimonial->load(self::WITH);
}
}