feature test su institution

This commit is contained in:
Giuseppe Naponiello
2026-06-22 22:14:15 +02:00
parent cc4e174880
commit fe73662903
39 changed files with 2432 additions and 1 deletions

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreInstitutionCategoryRequest;
use App\Http\Requests\UpdateInstitutionCategoryRequest;
use App\Http\Traits\ApiResponse;
use App\Models\Lists\InstitutionCategory;
use Illuminate\Http\JsonResponse;
/**
* CRUD del lookup categorie di istituzione (scrittura riservata agli Admin).
*
* NB gli id 2/3/4/6 sono allineati al legacy e usati dall'ETL: cancellarne uno
* in uso è bloccato (409). Nessun soft delete su questo lookup.
*/
class InstitutionCategoryController extends Controller
{
use ApiResponse;
/**
* Elenco delle categorie.
*/
public function index(): JsonResponse
{
return $this->collectionResponse(InstitutionCategory::orderBy('value')->get());
}
/**
* Crea una categoria.
*/
public function store(StoreInstitutionCategoryRequest $request): JsonResponse
{
$category = InstitutionCategory::create($request->validated());
return $this->createdResponse($category);
}
/**
* Dettaglio di una categoria.
*/
public function show(InstitutionCategory $institutionCategory): JsonResponse
{
return $this->okResponse($institutionCategory);
}
/**
* Aggiorna una categoria.
*/
public function update(UpdateInstitutionCategoryRequest $request, InstitutionCategory $institutionCategory): JsonResponse
{
$institutionCategory->update($request->validated());
return $this->updatedResponse($institutionCategory);
}
/**
* Elimina una categoria (se non assegnata ad alcuna istituzione).
*/
public function destroy(InstitutionCategory $institutionCategory): JsonResponse
{
if ($institutionCategory->isInUse()) {
return $this->conflictResponse('Cannot delete: category assigned to at least one institution.');
}
$institutionCategory->delete();
return $this->deletedResponse();
}
/**
* Indica se la categoria è in uso (bloccata per l'eliminazione).
*/
public function usage(InstitutionCategory $institutionCategory): JsonResponse
{
return response()->json([
'in_use' => $institutionCategory->isInUse(),
]);
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\IndexInstitutionRequest;
use App\Http\Requests\StoreInstitutionRequest;
use App\Http\Requests\UpdateInstitutionRequest;
use App\Http\Traits\ApiResponse;
use App\Models\Institution;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
/**
* CRUD delle istituzioni (scrittura riservata agli Admin via tier di rotta).
*
* Identificate verso l'esterno dallo `uuid` (route key), non dall'id interno.
* Soft delete: destroy = cestina, restore = ripristina, forceDestroy = elimina
* definitivamente (e rimuove il file logo + i link via cascade FK).
*/
class InstitutionController extends Controller
{
use ApiResponse;
/** Sottocartella del disco `public` dove vivono i loghi. */
private const LOGO_DIR = 'institution_logo';
/**
* Elenco paginato, con filtri opzionali (ricerca, categoria, cestino).
*/
public function index(IndexInstitutionRequest $request): JsonResponse
{
$query = Institution::query()
->with('category')
->orderBy('name');
$this->applyTrashed($query, $request->input('trashed'));
$this->applySearch($query, $request->input('search'));
if ($request->filled('category_id')) {
$query->where('category_id', $request->integer('category_id'));
}
$institutions = $query->paginate($request->integer('per_page') ?: 20);
return $this->paginatedCollectionResponse($institutions);
}
/**
* Crea un'istituzione (con upload del logo).
*/
public function store(StoreInstitutionRequest $request): JsonResponse
{
$data = $request->validated();
$data['logo'] = $this->storeLogo($request->file('logo'));
$institution = Institution::create($data);
return $this->createdResponse($institution->load('category'));
}
/**
* Dettaglio (con categoria e link).
*/
public function show(Institution $institution): JsonResponse
{
return $this->okResponse($institution->load(['category', 'links']));
}
/**
* Aggiorna un'istituzione. Logo opzionale: se presente sostituisce il vecchio.
*/
public function update(UpdateInstitutionRequest $request, Institution $institution): JsonResponse
{
$data = $request->validated();
if ($request->hasFile('logo')) {
$this->deleteLogo($institution->logo);
$data['logo'] = $this->storeLogo($request->file('logo'));
} else {
unset($data['logo']);
}
$institution->update($data);
return $this->updatedResponse($institution->load('category'));
}
/**
* Cestina (soft delete).
*/
public function destroy(Institution $institution): JsonResponse
{
$institution->delete();
return $this->deletedResponse();
}
/**
* Ripristina un'istituzione cestinata.
*/
public function restore(Institution $institution): JsonResponse
{
$institution->restore();
return $this->restoredResponse($institution->load('category'));
}
/**
* Elimina definitivamente (rimuove il file logo; i link cadono in cascata DB).
*/
public function forceDestroy(Institution $institution): JsonResponse
{
$this->deleteLogo($institution->logo);
$institution->forceDelete();
return $this->deletedResponse();
}
/**
* Salva il file logo sul disco `public` e ne restituisce il path relativo.
*/
private function storeLogo(UploadedFile $file): string
{
return $file->store(self::LOGO_DIR, 'public');
}
/**
* Rimuove un file logo dal disco `public`, se presente.
*/
private function deleteLogo(?string $path): void
{
if (filled($path)) {
Storage::disk('public')->delete($path);
}
}
/**
* Include i soft-deleted: `with` (tutti) o `only` (solo cestinati).
*
* @param Builder<Institution> $query
*/
private function applyTrashed(Builder $query, ?string $trashed): void
{
match ($trashed) {
'with' => $query->withTrashed(),
'only' => $query->onlyTrashed(),
default => null,
};
}
/**
* Ricerca parziale su nome, città e sigla.
*
* @param Builder<Institution> $query
*/
private function applySearch(Builder $query, ?string $search): void
{
if (blank($search)) {
return;
}
$term = '%'.$search.'%';
$query->where(function (Builder $q) use ($term): void {
$q->where('name', 'like', $term)
->orWhere('city', 'like', $term)
->orWhere('abbreviation', 'like', $term);
});
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreInstitutionLinkRequest;
use App\Http\Requests\UpdateInstitutionLinkRequest;
use App\Http\Traits\ApiResponse;
use App\Models\Institution;
use App\Models\InstitutionLink;
use Illuminate\Http\JsonResponse;
/**
* Risorse web di un'istituzione (sito ufficiale, ticketing, social, ...).
*
* Risorsa annidata e scoped sotto `institutions/{institution}`: il link deve
* appartenere all'istituzione del path. Nessun soft delete (solo Institution lo ha).
*/
class InstitutionLinkController extends Controller
{
use ApiResponse;
/**
* Elenco dei link di un'istituzione, ordinati.
*/
public function index(Institution $institution): JsonResponse
{
$links = $institution->links()->orderBy('sort_order')->get();
return $this->collectionResponse($links);
}
/**
* Aggiunge un link all'istituzione.
*/
public function store(StoreInstitutionLinkRequest $request, Institution $institution): JsonResponse
{
$link = $institution->links()->create($request->validated());
return $this->createdResponse($link);
}
/**
* Dettaglio di un link.
*/
public function show(Institution $institution, InstitutionLink $link): JsonResponse
{
return $this->okResponse($link);
}
/**
* Aggiorna un link.
*/
public function update(UpdateInstitutionLinkRequest $request, Institution $institution, InstitutionLink $link): JsonResponse
{
$link->update($request->validated());
return $this->updatedResponse($link);
}
/**
* Elimina un link.
*/
public function destroy(Institution $institution, InstitutionLink $link): JsonResponse
{
$link->delete();
return $this->deletedResponse();
}
}