أُنشئ من Tokal/Test
Fix API reliability and CORS config for hosted deployment
هذا الالتزام موجود في:
@@ -12,6 +12,8 @@ class AdminController extends Controller
|
||||
// LIST USERS (admin)
|
||||
public function users(Request $request)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$validated = $request->validate([
|
||||
'per_page' => 'nullable|integer|min:1|max:200',
|
||||
]);
|
||||
@@ -49,6 +51,8 @@ class AdminController extends Controller
|
||||
// LIST RESERVATIONS (admin)
|
||||
public function reservations(Request $request)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$validated = $request->validate([
|
||||
'per_page' => 'nullable|integer|min:1|max:200',
|
||||
]);
|
||||
|
||||
@@ -29,9 +29,49 @@ class FeedbackController extends Controller
|
||||
], 201);
|
||||
}
|
||||
|
||||
// CREATE FEEDBACK (public landing page)
|
||||
public function storePublic(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'message' => 'required|string|min:5|max:2000',
|
||||
'name' => 'nullable|string|max:120',
|
||||
'email' => 'nullable|email|max:255',
|
||||
]);
|
||||
|
||||
$details = [];
|
||||
if (! empty($validated['name'])) {
|
||||
$details[] = 'Name: ' . trim($validated['name']);
|
||||
}
|
||||
if (! empty($validated['email'])) {
|
||||
$details[] = 'Email: ' . trim($validated['email']);
|
||||
}
|
||||
|
||||
$message = trim($validated['message']);
|
||||
if (! empty($details)) {
|
||||
$message .= "\n\n[Landing Page]\n" . implode("\n", $details);
|
||||
} else {
|
||||
$message .= "\n\n[Landing Page]";
|
||||
}
|
||||
|
||||
$feedback = UserFeedback::create([
|
||||
'user_id' => null,
|
||||
'message' => $message,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Feedback sent successfully',
|
||||
'data' => [
|
||||
'id' => $feedback->id,
|
||||
'created_at' => $feedback->created_at,
|
||||
],
|
||||
], 201);
|
||||
}
|
||||
|
||||
// LIST FEEDBACK (admin)
|
||||
public function adminIndex(Request $request)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$validated = $request->validate([
|
||||
'per_page' => 'nullable|integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
@@ -281,6 +281,8 @@ class ReservationController extends Controller
|
||||
// CREATE RESERVATION (customer)
|
||||
public function store(Request $request)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$user = $request->user();
|
||||
if ($user->isBlocked()) {
|
||||
return response()->json(['message' => 'You are temporarily blocked from making reservations.'], 403);
|
||||
@@ -319,7 +321,8 @@ class ReservationController extends Controller
|
||||
'rejection_reason' => null,
|
||||
]);
|
||||
|
||||
$table = $this->findAvailableTable($reservation, true, true);
|
||||
// Avoid long lock waits in high-latency environments; use optimistic read.
|
||||
$table = $this->findAvailableTable($reservation, true, false);
|
||||
if (! $table) {
|
||||
throw new \RuntimeException('No available table for this reservation.');
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\Venue;
|
||||
use App\Models\VenueImage;
|
||||
use App\Models\VenueTable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -18,6 +19,9 @@ use Illuminate\Validation\ValidationException;
|
||||
|
||||
class VenueController extends Controller
|
||||
{
|
||||
private ?bool $hasInlineVenueArraysCache = null;
|
||||
private ?bool $hasStructuredVenueDataCache = null;
|
||||
|
||||
// LIST VENUES (customer)
|
||||
public function index(Request $request)
|
||||
{
|
||||
@@ -129,6 +133,8 @@ class VenueController extends Controller
|
||||
// LIST VENUES (admin)
|
||||
public function adminIndex(Request $request)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$ownerColumn = Venue::ownerColumn();
|
||||
$validated = $request->validate([
|
||||
'search' => 'nullable|string|max:255',
|
||||
@@ -145,7 +151,7 @@ class VenueController extends Controller
|
||||
$q->where('is_active', true);
|
||||
},
|
||||
]);
|
||||
$this->applyVenueRelations($query);
|
||||
// Keep admin list lightweight; loading full relations here can timeout.
|
||||
|
||||
if (! empty($validated['search'])) {
|
||||
$query->where('name', 'like', '%' . $validated['search'] . '%');
|
||||
@@ -171,6 +177,8 @@ class VenueController extends Controller
|
||||
// CREATE VENUE (admin)
|
||||
public function adminStore(Request $request)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$validated = $request->validate([
|
||||
'vendor_id' => 'nullable|integer|exists:users,id',
|
||||
'name' => 'required|string|max:255',
|
||||
@@ -268,6 +276,8 @@ class VenueController extends Controller
|
||||
// UPDATE VENUE (admin)
|
||||
public function adminUpdate(Request $request, int $id)
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
$validated = $request->validate([
|
||||
'vendor_id' => 'nullable|integer|exists:users,id',
|
||||
'name' => 'sometimes|required|string|max:255',
|
||||
@@ -542,6 +552,10 @@ class VenueController extends Controller
|
||||
}
|
||||
|
||||
if ($this->hasStructuredVenueData()) {
|
||||
if (! $venue->relationLoaded('amenitiesRelation')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ($venue->amenitiesRelation ?? collect())
|
||||
->pluck('name')
|
||||
->values()
|
||||
@@ -558,6 +572,10 @@ class VenueController extends Controller
|
||||
}
|
||||
|
||||
if ($this->hasStructuredVenueData()) {
|
||||
if (! $venue->relationLoaded('images')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ($venue->images ?? collect())
|
||||
->sortBy('sort_order')
|
||||
->pluck('url')
|
||||
@@ -575,6 +593,10 @@ class VenueController extends Controller
|
||||
}
|
||||
|
||||
if ($this->hasStructuredVenueData()) {
|
||||
if (! $venue->relationLoaded('offersRelation')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ($venue->offersRelation ?? collect())
|
||||
->map(fn (Offer $offer) => [
|
||||
'title' => $offer->title,
|
||||
@@ -591,17 +613,48 @@ class VenueController extends Controller
|
||||
|
||||
private function hasInlineVenueArrays(): bool
|
||||
{
|
||||
return Schema::hasColumn('venues', 'amenities')
|
||||
&& Schema::hasColumn('venues', 'image_urls')
|
||||
&& Schema::hasColumn('venues', 'offers');
|
||||
if ($this->hasInlineVenueArraysCache !== null) {
|
||||
return $this->hasInlineVenueArraysCache;
|
||||
}
|
||||
|
||||
$this->hasInlineVenueArraysCache = $this->rememberSchemaFlag(
|
||||
'schema:venues:inline-arrays:v1',
|
||||
fn () => Schema::hasColumn('venues', 'amenities')
|
||||
&& Schema::hasColumn('venues', 'image_urls')
|
||||
&& Schema::hasColumn('venues', 'offers')
|
||||
);
|
||||
|
||||
return $this->hasInlineVenueArraysCache;
|
||||
}
|
||||
|
||||
private function hasStructuredVenueData(): bool
|
||||
{
|
||||
return Schema::hasTable('venue_images')
|
||||
&& Schema::hasTable('offers')
|
||||
&& Schema::hasTable('venue_amenities')
|
||||
&& Schema::hasTable('amenities');
|
||||
if ($this->hasStructuredVenueDataCache !== null) {
|
||||
return $this->hasStructuredVenueDataCache;
|
||||
}
|
||||
|
||||
$this->hasStructuredVenueDataCache = $this->rememberSchemaFlag(
|
||||
'schema:venues:structured-data:v1',
|
||||
fn () => Schema::hasTable('venue_images')
|
||||
&& Schema::hasTable('offers')
|
||||
&& Schema::hasTable('venue_amenities')
|
||||
&& Schema::hasTable('amenities')
|
||||
);
|
||||
|
||||
return $this->hasStructuredVenueDataCache;
|
||||
}
|
||||
|
||||
private function rememberSchemaFlag(string $cacheKey, callable $resolver): bool
|
||||
{
|
||||
try {
|
||||
return (bool) Cache::remember(
|
||||
$cacheKey,
|
||||
now()->addMinutes(30),
|
||||
fn () => (bool) $resolver()
|
||||
);
|
||||
} catch (\Throwable) {
|
||||
return (bool) $resolver();
|
||||
}
|
||||
}
|
||||
|
||||
private function inlineVenuePayload(array $amenities, array $imageUrls, array $offers): array
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم