All API controllers live in app/Http/Controllers/Api/. They return JSON and use Laravel Sanctum token authentication on protected routes.AuthController
File: app/Http/Controllers/Api/AuthController.phpHandles mobile/SPA authentication via Sanctum tokens.| Method | Route | Auth | Description |
|---|
POST | /api/v1/auth/register | — | Register a new traveller account |
POST | /api/v1/auth/login | — | Authenticate and receive a Sanctum token |
GET | /api/v1/auth/me | Required | Return the authenticated user’s profile |
POST | /api/v1/auth/logout | Required | Revoke the current device token |
POST | /api/v1/auth/logout-all | Required | Revoke tokens on all devices |
Register validationpublic function register(Request $request): JsonResponse
{
$validated = $request->validate([
'firstname' => ['required', 'string', 'max:100'],
'lastname' => ['required', 'string', 'max:100'],
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', 'confirmed', Password::min(8)->mixedCase()->numbers()],
'phone' => ['nullable', 'string', 'max:20'],
]);
// Creates user, issues 'mobile-app' token
$token = $user->createToken('mobile-app')->plainTextToken;
return response()->json(['token' => $token, ...], 201);
}
Login behaviourpublic function login(Request $request): JsonResponse
{
// Revokes previous mobile tokens before issuing a new one
$user->tokens()->where('name', 'mobile-app')->delete();
$token = $user->createToken('mobile-app')->plainTextToken;
// Returns 403 if user status == 0 (suspended)
}
PackageController
File: app/Http/Controllers/Api/PackageController.phpProvides read-only tour listings. Write operations remain in the web dashboard.| Method | Route | Auth | Description |
|---|
GET | /api/v1/packages | — | List active packages with filtering and pagination |
GET | /api/v1/packages/{slug} | — | Full detail for a single package |
GET | /api/v1/packages/categories | — | List active package categories |
GET | /api/v1/packages/featured | — | Featured packages for the home screen carousel |
Supported query parameters for index| Parameter | Type | Description |
|---|
destination_id | integer | Filter by destination |
category_id | integer | Filter by category |
min_price | float | Minimum adult price |
max_price | float | Maximum adult price |
search | string | Keyword match on title or description |
sort | string | price_asc, price_desc, rating, or newest (default) |
per_page | integer | Items per page (max 50, default 12) |
Listing filter examplepublic function index(Request $request): JsonResponse
{
$query = Package::with(['category:id,name', 'destination:id,title,slug', 'owner:id,firstname,lastname,image,image_driver', 'media'])
->where('status', 1)
->whereHas('owner', fn($q) => $q->whereHas('activePlan'));
if ($request->filled('destination_id')) {
$query->where('destination_id', (int) $request->destination_id);
}
// ... additional filters
match ($request->input('sort', 'newest')) {
'price_asc' => $query->orderBy('adult_price', 'asc'),
'price_desc' => $query->orderBy('adult_price', 'desc'),
'rating' => $query->orderByDesc('is_featured'),
default => $query->orderByDesc('created_at'),
};
}
Only packages whose owner has an active plan (activePlan relationship) appear in API listings.
BookingController
File: app/Http/Controllers/Api/BookingController.phpAllows travellers to view and manage their bookings. Payment processing happens through the web checkout flow.| Method | Route | Auth | Description |
|---|
GET | /api/v1/bookings | Required | Paginated list of the authenticated user’s bookings |
GET | /api/v1/bookings/{uid} | Required | Single booking detail (scoped to owner) |
POST | /api/v1/bookings/{uid}/cancel | Required | Cancel a pending (0) or confirmed (1) booking |
GET | /api/v1/bookings/availability | — | Check remaining spaces for a package on a date |
Cancel logicpublic function cancel(Request $request, string $uid): JsonResponse
{
$booking = Booking::where('uid', $uid)
->where('user_id', $request->user()->id) // scoped to owner
->firstOrFail();
if (!in_array($booking->status, [0, 1])) {
return response()->json(['message' => 'This booking cannot be cancelled in its current status.'], 422);
}
$booking->update(['status' => 3]); // 3 = cancelled
}
Availability check query parameters| Parameter | Validation |
|---|
package_slug | required, string |
date | required, date, after_or_equal:today |
ReviewController
File: app/Http/Controllers/Api/ReviewController.phpOnly users with a confirmed or completed booking can submit a review.| Method | Route | Auth | Description |
|---|
GET | /api/v1/packages/{slug}/reviews | — | Paginated approved reviews for a package |
POST | /api/v1/reviews | Required | Submit a review (requires qualifying booking) |
Review submission validationpublic function store(Request $request): JsonResponse
{
$validated = $request->validate([
'package_id' => ['required', 'integer', 'exists:packages,id'],
'rating' => ['required', 'integer', 'min:1', 'max:5'],
'comment' => ['required', 'string', 'min:10', 'max:1000'],
]);
// Verify qualifying booking (status 1 or 2)
$hasBooking = Booking::where('user_id', $user->id)
->where('package_id', $validated['package_id'])
->whereIn('status', [1, 2])
->exists();
// Submitted reviews start with status=0 (pending admin approval)
Review::create([..., 'status' => 0]);
}
UserController
File: app/Http/Controllers/Api/UserController.phpLets the mobile app read and update the authenticated user’s profile.| Method | Route | Auth | Description |
|---|
GET | /api/v1/user/profile | Required | Full profile including vendor info and active plan |
PUT | /api/v1/user/profile | Required | Update profile fields |
PUT | /api/v1/user/password | Required | Change password and revoke all tokens |
GET | /api/v1/user/stats | Required | Booking statistics for the dashboard summary |
Password change — revokes all tokenspublic function changePassword(Request $request): JsonResponse
{
// Validates current password, then:
$user->update(['password' => Hash::make($validated['password'])]);
$user->tokens()->delete(); // force re-login on all devices
}
DestinationController
File: app/Http/Controllers/Api/DestinationController.php| Method | Route | Auth | Description |
|---|
GET | /api/v1/destinations | — | All active destinations ordered by visitor count |
GET | /api/v1/destinations/{slug} | — | Destination detail with up to 20 featured packages |
public function index(Request $request): JsonResponse
{
$destinations = Destination::where('status', 1)
->withCount('visitor')
->orderByDesc('visitor_count')
->get();
}