# SkybirdFly Complete Setup Guide

**Status**: ✅ Fully bootstrapped and ready for Phase 2 (Admin Dashboard + Live API Integration)  
**Last Updated**: 2026-02-20  
**Framework**: Laravel 12 | **Database**: SQLite (local) + MySQL (prod) | **PHP**: 8.2.12

---

## 🚀 Quick Start (30 seconds)

```powershell
cd "c:\Users\educa\OneDrive\Documents\Projects\Skybird Fly\app"

# Already done in this session:
# 1. composer install
# 2. php artisan key:generate
# 3. Database migration + seeding
# 4. .env configuration

# Start server:
php artisan serve --host=0.0.0.0 --port=8000

# Open browser:
# http://localhost:8000
```

---

## ✨ What's Already Built

### Core Infrastructure
✅ **Scope Enforcement** – Hotels automatically blocked everywhere  
✅ **Multi-Provider Support** – Amadeus, RapidAPI, Mapbox, Google Maps ready  
✅ **Provider Registry** – Central orchestration with fallback logic  
✅ **Data Normalization** – FlightOffer + TaxiQuote DTOs for consistent API  
✅ **Audit Trail** – Every search logged to database for analytics  
✅ **Health Checks** – Provider status monitoring built-in  

### Database Schema
✅ **7 Tables** – Providers, Credentials, Module Maps, Search Requests, Users, Jobs, Cache  
✅ **Migrations** – All reversible; easy schema evolution  
✅ **Relationships** – Provider → Credentials (1:N), Provider → Modules (1:N)  
✅ **Encryption** – API keys encrypted using Laravel's APP_KEY  

### API Endpoints (v1)
✅ `GET /api/v1/health` – Server health + version info  
✅ `GET /` – Welcome message  
✅ `POST /api/v1/flights/search` – Flight offers (Amadeus/RapidAPI)  
✅ `POST /api/v1/taxi/search` – Taxi quotes (Mapbox/Google)  
✅ `GET /hotels/*` – 403 HOTELS_DISABLED (scope gate working)  

### Code Quality
✅ **Interface-Based** – All providers implement contracts  
✅ **DRY Principles** – ProviderRegistry centralizes all provider logic  
✅ **Error Handling** – Comprehensive validation + exception catching  
✅ **Logging** – SearchRequest model tracks every API call  

---

## 🔐 Security Checklist (Pre-Production)

- [ ] HTTPS enforced (`APP_URL=https://...`)
- [ ] CSRF tokens on all forms (Blade templates, not yet built)
- [ ] Rate limiting on API endpoints (use `throttle` middleware)
- [ ] API authentication (Bearer token or API key validation)
- [ ] SQL injection prevention (using Eloquent, not raw queries)
- [ ] XSS prevention (escaping in Blade templates)
- [ ] Sensitive data never in logs (API keys masked)
- [ ] CORS headers if frontend on different domain

**Current**: Development mode only (no auth required yet)

---

## 📦 Files Reference

### Configuration
- `.env` – Environment variables (API keys, DB credentials)
- `config/skybird.php` – Feature modules + blocked routes (source of truth)
- `config/app.php` – Laravel core config (name, locale, key, etc.)
- `config/database.php` – Database connections

### Application Code
- `app/Http/Middleware/EnforceFeatureScope.php` – Hotels blocker
- `app/Services/ProviderRegistry.php` – Provider orchestration
- `app/Services/Providers/Flights/*.php` – Flight adapters
- `app/Services/Providers/Taxi/*.php` – Taxi adapters
- `app/Models/*.php` – Database models
- `app/DTOs/*.php` – Data transfer objects
- `app/Contracts/*.php` – Interface definitions

### Routes & Views
- `routes/web.php` – All API v1 routes + welcome endpoint
- `resources/views/welcome.blade.php` – Default welcome (replaced by JSON response)

### Database
- `database/migrations/*.php` – Schema definitions
- `database/seeders/ProviderSeeder.php` – Initial provider data
- `database/database.sqlite` – SQLite file (checked into git for dev)

### Documentation
- `README_IMPLEMENTATION.md` – Complete technical guide (you are here)
- `PROGRESS.md` – Session progress + next steps
- Root `README.md` – Project policy (flights+taxi only, hotels disabled)

---

## 🧪 API Testing Examples

### 1. Health Check
```bash
curl http://localhost:8000/api/v1/health
```

Response (200 OK):
```json
{
  "status": "ok",
  "app": "SkybirdFly",
  "version": "1.0.0-beta",
  "timestamp": "2026-02-20T16:37:37+05:30"
}
```

### 2. Hotel Block (Scope Gate Test)
```bash
curl http://localhost:8000/hotels/search
```

Response (403 Forbidden):
```json
{
  "ok": false,
  "error": "HOTELS_DISABLED",
  "message": "Hotels feature is disabled in SkybirdFly (Flights + Taxi only)"
}
```

### 3. Flight Search (Requires Amadeus API Key in .env)
```bash
curl -X POST http://localhost:8000/api/v1/flights/search \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "JFK",
    "destination": "LAX",
    "departureDate": "2026-03-15",
    "adults": 1,
    "cabinClass": "ECONOMY"
  }'
```

Response (200 OK, or 503 if no credentials):
```json
{
  "ok": true,
  "provider": "amadeus",
  "count": 25,
  "offers": [
    {
      "id": "1",
      "provider": "amadeus",
      "origin": "JFK",
      "destination": "LAX",
      "departureDateTime": "2026-03-15T10:00:00",
      "arrivalDateTime": "2026-03-15T14:30:00",
      "durationSeconds": 16200,
      "durationFormatted": "4h 30m",
      "stops": 0,
      "isDirectFlight": true,
      "airline": "AA",
      "price": 350.00,
      "currency": "USD",
      "bookingLink": "https://...",
      "amenities": [],
      "expiresAt": "2026-02-20T22:37:37+05:30"
    }
  ]
}
```

### 4. Taxi Search (Requires Mapbox Token in .env)
```bash
curl -X POST http://localhost:8000/api/v1/taxi/search \
  -H "Content-Type: application/json" \
  -d '{
    "pickupLocation": "40.7128,-74.0060",
    "dropoffLocation": "34.0522,-118.2437",
    "pickupDateTime": "2026-03-15 14:30:00"
  }'
```

Response (200 OK):
```json
{
  "ok": true,
  "provider": "mapbox",
  "count": 1,
  "quotes": [
    {
      "id": "abc123...",
      "provider": "mapbox",
      "pickupLocation": "40.7128,-74.0060",
      "dropoffLocation": "34.0522,-118.2437",
      "distance": 3944.5,
      "distanceFormatted": "3944.50 km",
      "durationSeconds": 142200,
      "durationFormatted": "39h 30m",
      "baseFare": 5.0,
      "estimatedFare": 8294.75,
      "currency": "USD",
      "vehicleType": "economy",
      "capacity": 4,
      "partnerLink": "https://uber.com/ul/book?pickup=40.7128,-74.0060&dropoff=34.0522,-118.2437",
      "expiresAt": "2026-02-20T16:52:37+05:30"
    }
  ]
}
```

---

## 🔧 Configuration for Production

### 1. Update .env for Hostinger

```env
APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

# MySQL on Hostinger
DB_CONNECTION=mysql
DB_HOST=your-hostinger-mysql-host
DB_DATABASE=your_database_name
DB_USERNAME=your_db_user
DB_PASSWORD=your_secure_password

# Add real API credentials
AMADEUS_API_KEY=production_key_here
AMADEUS_SANDBOX_MODE=false
MAPBOX_ACCESS_TOKEN=production_token_here
GOOGLE_MAPS_API_KEY=production_key_here
```

### 2. Run Migrations on Production

```bash
php artisan migrate --force
php artisan db:seed--class=ProviderSeeder --force
```

### 3. Setup Hostinger Cron Job

Add to Hostinger cPanel > Cron Jobs:

```
/usr/bin/php /home/username/public_html/artisan schedule:run >> /dev/null 2>&1
```

Run every minute so Laravel's task scheduler can trigger:
- Provider health checks (every 30 min)
- Price alerts (every hour)
- Cleanup old searches (daily)

### 4. Optimize for Production

```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan optimize
```

---

##  🎓 How to Add a New Provider

### Example: Add Skyscanner as Flight Provider

**Step 1: Create Adapter**

```php
// app/Services/Providers/Flights/SkyscannerFlightAdapter.php

namespace App\Services\Providers\Flights;

use App\Contracts\FlightProviderInterface;
use App\DTOs\FlightOffer;

class SkyscannerFlightAdapter implements FlightProviderInterface
{
    private string $apiKey;
    private string $endpoint = 'https://api.skyscanner.com';

    public function __construct(string $apiKey, ?string $apiSecret = null, string $endpoint = '', bool $sandboxMode = false)
    {
        $this->apiKey = $apiKey;
        if (!empty($endpoint)) $this->endpoint = $endpoint;
    }

    public function search(array $criteria): array
    {
        // 1. Parse criteria
        // 2. Call Skyscanner API
        // 3. Map response to FlightOffer DTOs
        // 4. Return array of offers
    }

    public function getDetails(string $offerId): ?FlightOffer { /* ... */ }
    public function getStatus(): array { /* ... */ }
    public function getName(): string { return 'skyscanner'; }
}
```

**Step 2: Register in ProviderRegistry**

```php
// app/Services/ProviderRegistry.php

private function getAdapterClass(string $type, string $slug): string
{
    $adapterMap = [
        'flight' => [
            'amadeus' => 'App\Services\Providers\Flights\AmadeusFlightAdapter',
            'rapidapi_flights' => 'App\Services\Providers\Flights\RapidApiFlightAdapter',
            'skyscanner' => 'App\Services\Providers\Flights\SkyscannerFlightAdapter', // ← Add here
        ],
        // ... taxi providers
    ];
    return $adapterMap[$type][$slug] ?? throw new \Exception(...);
}
```

**Step 3: Add Credentials in .env**

```env
SKYSCANNER_API_KEY=your_key_here
```

**Step 4: Create via Admin Dashboard (Phase 2)**

Or manually in tinker:

```php
php artisan tinker

> \App\Models\Provider::create([
    'name' => 'Skyscanner',
    'type' => 'flight',
    'slug' => 'skyscanner',
    'endpoint' => 'https://api.skyscanner.com',
    'status' => 'inactive',
]);

> \App\Models\ProviderCredential::create([
    'provider_id' => 3,
    'name' => 'production',
    'api_key' => encrypt(env('SKYSCANNER_API_KEY')),
    'is_active' => true,
]);

> \App\Models\ProviderModuleMap::create([
    'module' => 'flights',
    'provider_id' => 3,
    'priority' => 2,  // Secondary after Amadeus
    'is_enabled' => true,
]);
```

That's it! Now `/api/v1/flights/search` can fallback to Skyscanner if Amadeus fails.

---

## 🐛 Debugging

### Enable Query Logging
```php
// In tinker or middleware:
DB::enableQueryLog();
// ... run code ...
dd(DB::getQueryLog());
```

### Check Provider Status
```bash
php artisan tinker

> \App\Models\Provider::all()->each(fn($p) => echo "{$p->name}: {$p->status}\n");
```

### View Search History
```bash
php artisan tinker

> \App\Models\SearchRequest::where('module', 'flights')->latest()->limit(5)->get();
```

### Clear Cache
```bash
php artisan cache:clear
php artisan config:clear
php artisan route:clear
```

---

## 📈 Monitoring & Analytics

### API Usage Dashboard (Future)

```sql
SELECT 
  DATE(created_at) as date,
  module,
  provider_id,
  COUNT(*) as searches,
  AVG(response_time_ms) as avg_response_ms,
  SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors
FROM search_requests
GROUP BY DATE(created_at), module, provider_id
ORDER BY date DESC;
```

### Error Rate by Provider
```sql
SELECT 
  p.name,
  p.type,
  COUNT(sr.id) as total,
  SUM(CASE WHEN sr.status = 'error' THEN 1 ELSE 0 END) as errors,
  ROUND(100.0 * SUM(CASE WHEN sr.status = 'error' THEN 1 ELSE 0 END) / COUNT(sr.id), 2) as error_pct
FROM search_requests sr
JOIN providers p ON sr.provider_id = p.id
GROUP BY p.id
HAVING COUNT(sr.id) > 10
ORDER BY error_pct DESC;
```

---

## 🎯 What Happens Next?

### Session 2 (Phase 1B – Admin Dashboard)
1. Build admin panel pages (Provider CRUD)
2. Implement API key management
3. Test live with real Amadeus sandbox
4. Complete RapidAPI + Google Maps adapters

### Session 3 (Phase 2 – Advanced Features)
1. User authentication (register/login)
2. Trip saving + favorites
3. Price alert engine
4. Email notifications

### Session 4 (Phase 3 – Optimization)
1. Caching (Redis)
2. Rate limiting
3. Load testing
4. Security audit

---

## 💡 Key Concepts

### ProviderRegistry
Central service that:
1. Selects active provider for a module
2. Instantiates adapter with credentials
3. Handles fallback if primary fails
4. Injects provider into request context

### Normalization DTOs
Transform raw API responses into canonical format so UI code doesn't need to know about provider-specific quirks. Example: Amadeus returns `PT10H30M` duration, but `FlightOffer.durationSeconds` is always an integer.

### Scope Gating
Hotels are blocked at:
1. **Middleware** – EnforceFeatureScope returns 403 before route matching
2. **Config** – skybird.php has `hotels.enabled = false`
3. **Routes** – No hotel routes registered
4. **Database** – No hotel tables

Defense in depth means if one layer fails, others still protect.

### Audit Trail
Every search is logged to `search_requests` table:
- Which provider handled it
- How long it took
- How many results
- Success or error
- IP address (for fraud detection)

Used for analytics, debugging, and billing.

---

##  📞 Support / Questions

Check these in order:
1. **Logs**: `storage/logs/laravel.log` (PHP errors)
2. **Database**: `php artisan tinker` (inspect records)
3. **Routes**: `php artisan route:list` (all registered routes)
4. **Config**: `php artisan config:list` (all configs merged)
5. **Health**: `GET /api/v1/health` (server status)

---

**End of Setup Guide**  
Ready to build Phase 2! 🚀
