Form Requests & API Resources
Validation lives in backend/app/Http/Requests/Api/V1/. Response shapes live in backend/app/Http/Resources/Api/V1/.
Form Requests
A request class per write endpoint. The authorize() method is rarely used (returns true) because authorization is enforced earlier by require.permission middleware + later by policies. Validation rules consistently:
- Restrict
Rule::exists()lookups to the active tenant:Rule::exists('clients','id')->where('tenant_id', $tenantId). - Cap free-text fields at sensible string lengths.
- Validate enums against the constants on the matching model (e.g.
Rule::in(Tenant::SUPPORTED_BUSINESS_TYPES)).
Catalog
| File | Used by | Notes |
|---|---|---|
Auth/RegisterOwnerRequest.php | AuthController@registerOwner | |
Auth/PasswordResetRequest.php (request + reset) | password reset flow | |
StoreClientRequest, StorePetRequest, UpdatePetRequest | clinic patient onboarding | |
StoreAppointmentRequest, UpdateAppointmentRequest, CancelAppointmentRequest | appointment CRUD + cancel | |
StoreInvoiceRequest, RecordPaymentRequest | billing | |
RefundPaymentRequest | invoice refund | |
StoreVisitRequest, UpdateVisitRequest | clinical | |
StorePrescriptionRequest, UpdatePrescriptionRequest | clinical | nested items[] validation |
StoreLabResultRequest, UpdateLabResultRequest | clinical | |
StoreMedicalAttachmentRequest | clinical | mime-type whitelist |
StorePetVaccinationRequest, UpdatePetVaccinationRequest | shared owner / clinic | |
StorePetHealthLogRequest | shared | |
StorePetDocumentRequest | owner | |
StorePetCaregiverRequest | owner | |
StoreOrderRequest, UpdateOrderRequest | owner + seller | |
StoreReminderRequest | clinic | |
StoreReminderOptOutRequest | clinic | |
StoreMedicineRequest, UpdateMedicineRequest | inventory | |
StoreStockMovementRequest | inventory | type-conditional rules |
StoreSupplierRequest, UpdateSupplierRequest | inventory | |
StorePurchaseOrderRequest, UpdatePurchaseOrderRequest, ReceivePurchaseOrderRequest | inventory | |
Admin/StoreRoleRequest, UpdateRoleRequest, AssignRoleRequest | admin | |
Tenant/UpdateTenantSettingsRequest | clinic settings | currency + timezone enum check |
Patterns to follow
- Always restrict
existsrules to the active tenant. - For
idfields the request shouldn't accept (e.g.tenant_id,owner_user_id), strip them out — don't validate them. The audit calls out several cases where the request validates a field instead of ignoring it, and the controller picks the validated value. - Cross-field rules (e.g.
subtotal + tax - discount === grand_total) belong on the request so the controller can stay thin.
API Resources
Resources shape the JSON response. Convention: one Resource per public model, narrower variants for public endpoints.
Catalog
| File | Returns |
|---|---|
UserResource | id, name, email, account_type, current tenant id, permissions list |
TenantResource | full editable tenant (used by clinic settings) |
ProviderListingResource | narrow: id, name, slug, business_type, phone, address, lat/lng, headline, bio, services, optional distance_km |
ClientResource, PetResource | full clinical view |
PetSummaryResource | minimal — used in lists / timeline |
AppointmentResource | (audit: drop tenant_id) |
InvoiceResource, PaymentResource | full money view |
VisitResource, PrescriptionResource, PrescriptionItemResource, LabResultResource, MedicalAttachmentResource | clinical |
PetVaccinationResource, PetHealthLogResource, PetDocumentResource, PetShareResource, PetCaregiverResource | owner-side |
OrderRequestResource | owner + seller views |
ReminderResource, ReminderTemplateResource, OptOutResource | notifications |
MedicineResource, StockMovementResource, SupplierResource, PurchaseOrderResource, PurchaseOrderItemResource | inventory |
RoleResource, PermissionResource, AuditLogResource | admin |
Conditional fields
distance_km on ProviderListingResource uses the $this->when(isset($this->distance_km), …) pattern so it only appears when the geo filter is active.
receipt_pdf_url on InvoiceResource is similarly conditional on receipt_emailed_at being set.
Privilege tiers (recommended — see audit)
The audit recommends formalising three tiers for resource classes to reduce accidental field leakage:
- Public — narrow, marketing-safe. Today's
ProviderListingResourceis the right shape. - Scoped — for tenant members. Today's main resources.
- Internal — for system_admin / Filament. Currently overlapping with Scoped.
The audit lists specific fields to remove (e.g. tenant_id from AppointmentResource, internal disk names from attachment resources).