From e7be54b84e77024eb69b6feb2c94a0dfe79b80ab Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Fri, 19 Jun 2026 01:15:27 +0330 Subject: [PATCH] feat(clinic): enforce maximum gallery image limit of 5 and update validation logic --- assets/admin/pages/ClinicDetailPage.tsx | 22 +++++++++++++---- docs/api/clinic.md | 2 +- src/Clinic/Controller/ClinicController.php | 28 ++++++++++++++++++++-- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/assets/admin/pages/ClinicDetailPage.tsx b/assets/admin/pages/ClinicDetailPage.tsx index 44563675..e82fe014 100644 --- a/assets/admin/pages/ClinicDetailPage.tsx +++ b/assets/admin/pages/ClinicDetailPage.tsx @@ -34,6 +34,7 @@ L.Icon.Default.mergeOptions({ const HUES_LIST = [256, 205, 162, 295, 272]; const IRAN_CENTER: [number, number] = [32.4279, 53.6880]; +const MAX_GALLERY_IMAGES = 5; // ── Types ────────────────────────────────────────────────────────────────── @@ -623,10 +624,21 @@ export default function ClinicDetailPage() { }; const handleGalleryUpload = async (files: FileList) => { + const existing = (clinic?.images_clinic ?? []).filter(img => img?.url); + const remaining = MAX_GALLERY_IMAGES - existing.length; + if (remaining <= 0) { + toast.error(`گالری حداکثر ${MAX_GALLERY_IMAGES} عکس می‌تواند داشته باشد`); + return; + } + const selected = Array.from(files); + if (selected.length > remaining) { + toast.error(`فقط ${remaining} عکس دیگر می‌توانید اضافه کنید (حداکثر ${MAX_GALLERY_IMAGES})`); + } + const toUpload = selected.slice(0, remaining); setGalleryUploading(true); try { const uploaded: { url: string }[] = []; - for (const file of Array.from(files)) { + for (const file of toUpload) { const res = await fetch('/file/upload/clinic_pro/clinic/field_image_clinic', { method: 'POST', headers: { @@ -640,7 +652,6 @@ export default function ClinicDetailPage() { if (json?.data?.url) uploaded.push({ url: json.data.url }); } if (uploaded.length > 0 && clinic) { - const existing = (clinic.images_clinic ?? []).filter(img => img?.url); await api.patch(`/api/v1/clinic/${uuid}`, { image_clinic: [...existing, ...uploaded] }); toast.success(`${uploaded.length} تصویر اضافه شد`); qc.invalidateQueries({ queryKey: ['clinic-detail', uuid] }); @@ -930,8 +941,11 @@ export default function ClinicDetailPage() { {/* Gallery */}
- گالری تصاویر - diff --git a/docs/api/clinic.md b/docs/api/clinic.md index c76620d1..c66344b1 100644 --- a/docs/api/clinic.md +++ b/docs/api/clinic.md @@ -46,7 +46,7 @@ Create a new clinic. | `longitude` | float | ❌ | Longitude for map | | `state` | string | ❌ | Province name | | `city` | string | ❌ | City name | -| `image_clinic` | object[] | ❌ | Gallery images `[{url: "..."}]` | +| `image_clinic` | object[] | ❌ | Gallery images `[{url: "..."}]` — **max 5**; more returns `ERR_VALIDATION_001` (422) | | `clinic_logo` | string | ❌ | Logo URL | | `doctors` | string[] | ❌ | Doctor UUIDs to associate | | `specialties` | integer[] | ❌ | Specialty IDs | diff --git a/src/Clinic/Controller/ClinicController.php b/src/Clinic/Controller/ClinicController.php index ac3676a0..95c10f58 100644 --- a/src/Clinic/Controller/ClinicController.php +++ b/src/Clinic/Controller/ClinicController.php @@ -30,6 +30,8 @@ use Symfony\Component\Uid\Uuid; #[OA\Tag(name: 'Clinics')] class ClinicController extends BaseController { + private const MAX_GALLERY_IMAGES = 5; + public function __construct( private readonly ClinicRepository $clinicRepo, private readonly DoctorRepository $doctorRepo, @@ -91,6 +93,9 @@ class ClinicController extends BaseController public function create(Request $request, #[CurrentUser] User $user): JsonResponse { $data = json_decode($request->getContent(), true) ?? []; + if (($err = $this->validateGallerySize($data)) !== null) { + return $err; + } $clinic = new Clinic($user); $this->hydrateClinic($clinic, $data); $this->clinicRepo->save($clinic); @@ -209,6 +214,9 @@ class ClinicController extends BaseController } $data = json_decode($request->getContent(), true) ?? []; + if (($err = $this->validateGallerySize($data)) !== null) { + return $err; + } $this->hydrateClinic($clinic, $data); $this->clinicRepo->save($clinic); @@ -457,6 +465,22 @@ class ClinicController extends BaseController // ── Helpers ─────────────────────────────────────────────────────────────── + private function validateGallerySize(array $data): ?JsonResponse + { + if (array_key_exists('image_clinic', $data) + && is_array($data['image_clinic']) + && count($data['image_clinic']) > self::MAX_GALLERY_IMAGES + ) { + return $this->error( + ErrorCodes::ERR_VALIDATION_001, + 'گالری تصاویر حداکثر ' . self::MAX_GALLERY_IMAGES . ' عکس می‌تواند داشته باشد', + 422, + 'image_clinic', + ); + } + return null; + } + private function hydrateClinic(Clinic $clinic, array $data): void { if (array_key_exists('name', $data)) $clinic->setName($data['name']); @@ -476,9 +500,9 @@ class ClinicController extends BaseController $clinic->setCityId((int) $data['city'][0]); } - // Images stored as JSON (from upload response) + // Images stored as JSON (from upload response); gallery is capped at 5. if (array_key_exists('image_clinic', $data) && is_array($data['image_clinic'])) { - $clinic->setImagesClinic($data['image_clinic']); + $clinic->setImagesClinic(array_slice(array_values($data['image_clinic']), 0, self::MAX_GALLERY_IMAGES)); } if (array_key_exists('clinic_logo', $data)) { $logo = $data['clinic_logo'];