530 lines
16 KiB
Markdown
530 lines
16 KiB
Markdown
### Project Idea
|
|||
|
|
|
||
|
|
This is a travel app based on wikipedia informations.
|
||
|
|
The user can choose a city, we show the city on the map with some info about it (from wikipedia, like city summary) and show things to do there.
|
||
|
|
We need to store in mysql data from wikipedia, opentripmap and overpass for cache.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Technical Architecture
|
||
|
|
|
||
|
|
### Tech Stack
|
||
|
|
|
||
|
|
**Frontend:**
|
||
|
|
- Alpine.js for interactive UI components
|
||
|
|
- Tailwind CSS for styling (or Bootstrap 5)
|
||
|
|
- Leaflet.js for interactive maps
|
||
|
|
- Laravel Livewire (optional) for dynamic components
|
||
|
|
- Blade templating engine
|
||
|
|
|
||
|
|
**Backend:**
|
||
|
|
- Laravel 10/11 (PHP)
|
||
|
|
- MySQL database for caching API responses
|
||
|
|
- Redis for caching and queue management
|
||
|
|
- Laravel HTTP Client (Guzzle) for API requests
|
||
|
|
- Laravel Cache for API response caching
|
||
|
|
- Laravel Jobs/Queues for background API fetching
|
||
|
|
|
||
|
|
**External APIs:**
|
||
|
|
- Wikipedia API - City summaries and information
|
||
|
|
- OpenTripMap API - Points of interest and attractions
|
||
|
|
- Overpass API (OpenStreetMap) - Geographic data and additional POIs
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Database Schema
|
||
|
|
|
||
|
|
### Migrations
|
||
|
|
|
||
|
|
**cities** (create_cities_table.php)
|
||
|
|
```php
|
||
|
|
$table->id();
|
||
|
|
$table->string('name');
|
||
|
|
$table->string('country');
|
||
|
|
$table->decimal('latitude', 10, 7);
|
||
|
|
$table->decimal('longitude', 10, 7);
|
||
|
|
$table->text('wikipedia_summary')->nullable();
|
||
|
|
$table->string('wikipedia_url')->nullable();
|
||
|
|
$table->string('wikipedia_page_id')->nullable();
|
||
|
|
$table->timestamps();
|
||
|
|
$table->timestamp('wikipedia_cached_at')->nullable();
|
||
|
|
|
||
|
|
// Indexes
|
||
|
|
$table->index(['name', 'country']);
|
||
|
|
$table->index('wikipedia_cached_at');
|
||
|
|
```
|
||
|
|
|
||
|
|
**attractions** (create_attractions_table.php)
|
||
|
|
```php
|
||
|
|
$table->id();
|
||
|
|
$table->foreignId('city_id')->constrained()->onDelete('cascade');
|
||
|
|
$table->string('name');
|
||
|
|
$table->text('description')->nullable();
|
||
|
|
$table->decimal('latitude', 10, 7);
|
||
|
|
$table->decimal('longitude', 10, 7);
|
||
|
|
$table->string('category')->nullable();
|
||
|
|
$table->enum('source', ['opentripmap', 'overpass', 'wikipedia']);
|
||
|
|
$table->string('source_id')->nullable();
|
||
|
|
$table->decimal('rating', 3, 2)->nullable();
|
||
|
|
$table->string('image_url')->nullable();
|
||
|
|
$table->timestamps();
|
||
|
|
$table->timestamp('cached_at')->nullable();
|
||
|
|
|
||
|
|
// Indexes
|
||
|
|
$table->index('city_id');
|
||
|
|
$table->index('category');
|
||
|
|
$table->index('cached_at');
|
||
|
|
```
|
||
|
|
|
||
|
|
**users** (Laravel's default users table with modifications)
|
||
|
|
```php
|
||
|
|
$table->id();
|
||
|
|
$table->string('name');
|
||
|
|
$table->string('email')->unique();
|
||
|
|
$table->timestamp('email_verified_at')->nullable();
|
||
|
|
$table->string('password');
|
||
|
|
$table->rememberToken();
|
||
|
|
$table->timestamps();
|
||
|
|
```
|
||
|
|
|
||
|
|
**favorites** (create_favorites_table.php)
|
||
|
|
```php
|
||
|
|
$table->id();
|
||
|
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||
|
|
$table->foreignId('city_id')->constrained()->onDelete('cascade');
|
||
|
|
$table->timestamps();
|
||
|
|
|
||
|
|
// Indexes
|
||
|
|
$table->unique(['user_id', 'city_id']); // Prevent duplicate favorites
|
||
|
|
$table->index('user_id');
|
||
|
|
```
|
||
|
|
|
||
|
|
**trips** (create_trips_table.php)
|
||
|
|
```php
|
||
|
|
$table->id();
|
||
|
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||
|
|
$table->string('name');
|
||
|
|
$table->text('description')->nullable();
|
||
|
|
$table->timestamps();
|
||
|
|
|
||
|
|
// Indexes
|
||
|
|
$table->index('user_id');
|
||
|
|
```
|
||
|
|
|
||
|
|
**city_trip** (create_city_trip_table.php) - Pivot table
|
||
|
|
```php
|
||
|
|
$table->id();
|
||
|
|
$table->foreignId('trip_id')->constrained()->onDelete('cascade');
|
||
|
|
$table->foreignId('city_id')->constrained()->onDelete('cascade');
|
||
|
|
$table->integer('order')->default(0); // Order of cities in trip
|
||
|
|
$table->timestamps();
|
||
|
|
|
||
|
|
// Indexes
|
||
|
|
$table->index(['trip_id', 'city_id']);
|
||
|
|
```
|
||
|
|
|
||
|
|
**personal_access_tokens** (Laravel Sanctum - auto-generated)
|
||
|
|
- Handles API tokens for mobile app authentication
|
||
|
|
|
||
|
|
**Note:** Laravel's built-in caching system (Redis/Database) will handle cache metadata, so a separate cache_metadata table is optional.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Routes Structure
|
||
|
|
|
||
|
|
### Web Routes (resources/routes/web.php)
|
||
|
|
|
||
|
|
**GET /**
|
||
|
|
- Home page with city search
|
||
|
|
|
||
|
|
**GET /cities/{city}**
|
||
|
|
- City details page with map and attractions
|
||
|
|
- Uses Blade view with Alpine.js for interactivity
|
||
|
|
|
||
|
|
### API Routes (resources/routes/api.php)
|
||
|
|
|
||
|
|
**Authentication Routes (Laravel Sanctum)**
|
||
|
|
|
||
|
|
**POST /api/register**
|
||
|
|
- Register new user account
|
||
|
|
- Returns user data and API token
|
||
|
|
|
||
|
|
**POST /api/login**
|
||
|
|
- Login with email and password
|
||
|
|
- Returns user data and API token
|
||
|
|
|
||
|
|
**POST /api/logout** (Protected)
|
||
|
|
- Logout and revoke current token
|
||
|
|
- Requires: Bearer token
|
||
|
|
|
||
|
|
**GET /api/user** (Protected)
|
||
|
|
- Get authenticated user profile
|
||
|
|
- Requires: Bearer token
|
||
|
|
|
||
|
|
**Public API Routes**
|
||
|
|
|
||
|
|
**GET /api/cities/search?q={query}**
|
||
|
|
- Search for cities by name
|
||
|
|
- Returns JSON list of matching cities
|
||
|
|
- Public access (no auth required)
|
||
|
|
|
||
|
|
**GET /api/cities/{cityId}**
|
||
|
|
- Get detailed information about a specific city
|
||
|
|
- Returns city data from cache or fetches from Wikipedia API
|
||
|
|
- Public access
|
||
|
|
|
||
|
|
**GET /api/cities/{cityId}/attractions**
|
||
|
|
- Get all attractions for a specific city
|
||
|
|
- Returns cached data or fetches from OpenTripMap and Overpass APIs
|
||
|
|
- Optional query params: ?category={category}&limit={number}
|
||
|
|
- Public access
|
||
|
|
|
||
|
|
**GET /api/cities/{cityId}/map-data**
|
||
|
|
- Get geographic data for map rendering
|
||
|
|
- Returns coordinates and boundaries for Leaflet
|
||
|
|
- Public access
|
||
|
|
|
||
|
|
**Protected API Routes (Require Authentication)**
|
||
|
|
|
||
|
|
**POST /api/cities** (Protected)
|
||
|
|
- Add a new city to the database
|
||
|
|
- Dispatches job to fetch data from all APIs
|
||
|
|
- Requires: Bearer token
|
||
|
|
|
||
|
|
**GET /api/favorites** (Protected)
|
||
|
|
- Get user's favorite cities
|
||
|
|
- Returns list of saved cities
|
||
|
|
|
||
|
|
**POST /api/favorites/{cityId}** (Protected)
|
||
|
|
- Add city to user favorites
|
||
|
|
- Returns updated favorites list
|
||
|
|
|
||
|
|
**DELETE /api/favorites/{cityId}** (Protected)
|
||
|
|
- Remove city from user favorites
|
||
|
|
- Returns success status
|
||
|
|
|
||
|
|
**GET /api/trips** (Protected)
|
||
|
|
- Get user's saved trips/itineraries
|
||
|
|
- Returns list of trips with cities
|
||
|
|
|
||
|
|
**POST /api/trips** (Protected)
|
||
|
|
- Create new trip/itinerary
|
||
|
|
- Body: {name, description, city_ids[]}
|
||
|
|
|
||
|
|
**PUT /api/trips/{tripId}** (Protected)
|
||
|
|
- Update existing trip
|
||
|
|
- Body: {name, description, city_ids[]}
|
||
|
|
|
||
|
|
**DELETE /api/trips/{tripId}** (Protected)
|
||
|
|
- Delete trip
|
||
|
|
- Returns success status
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Core Features
|
||
|
|
|
||
|
|
### 1. City Search
|
||
|
|
- Autocomplete search functionality
|
||
|
|
- Search by city name
|
||
|
|
- Display search results with country and preview info
|
||
|
|
|
||
|
|
### 2. City Details Page
|
||
|
|
- Display city name and country
|
||
|
|
- Show Wikipedia summary (first 2-3 paragraphs)
|
||
|
|
- Link to full Wikipedia article
|
||
|
|
- Display city location on interactive map
|
||
|
|
|
||
|
|
### 3. Interactive Map
|
||
|
|
- Centered on selected city
|
||
|
|
- Markers for attractions/points of interest
|
||
|
|
- Color-coded markers by category
|
||
|
|
- Click markers to see attraction details
|
||
|
|
- Zoom and pan controls
|
||
|
|
|
||
|
|
### 4. Attractions List
|
||
|
|
- Categorized list of things to do
|
||
|
|
- Filter by category (museums, restaurants, parks, etc.)
|
||
|
|
- Sort by distance, rating, or name
|
||
|
|
- Click to highlight on map
|
||
|
|
|
||
|
|
### 5. Caching System
|
||
|
|
- Cache Wikipedia data for 30 days
|
||
|
|
- Cache OpenTripMap data for 7 days
|
||
|
|
- Cache Overpass data for 7 days
|
||
|
|
- Automatic cache invalidation and refresh
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Steps
|
||
|
|
|
||
|
|
### Phase 1: Laravel Project Setup
|
||
|
|
1. Initialize Laravel project: `composer create-project laravel/laravel wiki_travel_site`
|
||
|
|
2. Install Laravel Sanctum for API authentication: `composer require laravel/sanctum`
|
||
|
|
3. Publish Sanctum config: `php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"`
|
||
|
|
4. Install Tailwind CSS: `npm install -D tailwindcss postcss autoprefixer && npx tailwindcss init`
|
||
|
|
5. Configure MySQL database connection in `.env`
|
||
|
|
6. Set up Redis for caching
|
||
|
|
7. Create database migrations for cities, attractions, users, favorites, trips
|
||
|
|
8. Run migrations: `php artisan migrate`
|
||
|
|
9. Configure CORS for mobile app in `config/cors.php`
|
||
|
|
|
||
|
|
### Phase 2: Models & Services
|
||
|
|
1. Create Eloquent models: `City`, `Attraction`, `Trip`, `Favorite`
|
||
|
|
2. Define model relationships:
|
||
|
|
- City hasMany Attractions
|
||
|
|
- User hasMany Favorites
|
||
|
|
- User hasMany Trips
|
||
|
|
- Trip belongsToMany Cities (pivot: city_trip)
|
||
|
|
- City belongsToMany Trips
|
||
|
|
3. Add HasApiTokens trait to User model for Sanctum
|
||
|
|
4. Create service classes:
|
||
|
|
- `WikipediaService` - Fetch city data from Wikipedia API
|
||
|
|
- `OpenTripMapService` - Fetch attractions from
|
||
|
|
- OpenTripMap API
|
||
|
|
- `OverpassService` - Fetch geographic data from Overpass API
|
||
|
|
5. Implement Laravel Cache facade for API response caching
|
||
|
|
6. Create Jobs for background API fetching:
|
||
|
|
- `FetchCityDataJob`
|
||
|
|
- `FetchAttractionsJob`
|
||
|
|
|
||
|
|
### Phase 3: Authentication & Controllers
|
||
|
|
1. Create `AuthController` for registration/login/logout
|
||
|
|
2. Create `CityController` for web routes
|
||
|
|
3. Create API controllers:
|
||
|
|
- `API\CityController` - City and attraction endpoints
|
||
|
|
- `API\FavoriteController` - Favorite management
|
||
|
|
- `API\TripController` - Trip/itinerary management
|
||
|
|
- `API\UserController` - User profile management
|
||
|
|
4. Define web routes in `routes/web.php`
|
||
|
|
5. Define API routes in `routes/api.php` with Sanctum middleware
|
||
|
|
6. Implement API resource classes for JSON responses:
|
||
|
|
- `CityResource`
|
||
|
|
- `AttractionResource`
|
||
|
|
- `TripResource`
|
||
|
|
- `UserResource`
|
||
|
|
7. Create Form Request validators for registration and trip creation
|
||
|
|
|
||
|
|
### Phase 4: Frontend Views
|
||
|
|
1. Create Blade layouts:
|
||
|
|
- `layouts/app.blade.php` - Main layout with Tailwind/Bootstrap
|
||
|
|
- `welcome.blade.php` - Homepage with search
|
||
|
|
- `cities/show.blade.php` - City details page
|
||
|
|
2. Install and configure Alpine.js
|
||
|
|
3. Integrate Leaflet.js for maps
|
||
|
|
4. Create Alpine.js components:
|
||
|
|
- City search with autocomplete
|
||
|
|
- Interactive map with markers
|
||
|
|
- Attractions filter and list
|
||
|
|
5. Style with Tailwind CSS utility classes
|
||
|
|
|
||
|
|
### Phase 5: Integration & Caching
|
||
|
|
1. Implement cache logic in services (30 days for Wikipedia, 7 days for attractions)
|
||
|
|
2. Configure Redis cache driver in `config/cache.php`
|
||
|
|
3. Add error handling and logging
|
||
|
|
4. Create artisan commands:
|
||
|
|
- `php artisan cache:clear-cities` - Clear city cache
|
||
|
|
- `php artisan attractions:refresh {cityId}` - Refresh attraction data
|
||
|
|
5. Test all API integrations
|
||
|
|
|
||
|
|
### Phase 6: Polish & Optimization
|
||
|
|
1. Add loading states with Alpine.js
|
||
|
|
2. Implement responsive design (mobile-first with Tailwind)
|
||
|
|
3. Optimize database queries (eager loading, indexing)
|
||
|
|
4. Add pagination for attractions list
|
||
|
|
5. Implement search history using Laravel sessions
|
||
|
|
6. Write feature tests with PHPUnit
|
||
|
|
7. Deploy to production (Laravel Forge, Vapor, or custom server)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Flow
|
||
|
|
|
||
|
|
1. User searches for a city
|
||
|
|
2. Frontend sends request to backend
|
||
|
|
3. Backend checks MySQL cache:
|
||
|
|
- If cached and not expired: return cached data
|
||
|
|
- If not cached or expired: fetch from Wikipedia API, store in cache, return data
|
||
|
|
4. User selects a city
|
||
|
|
5. Backend fetches attractions:
|
||
|
|
- Check cache for OpenTripMap and Overpass data
|
||
|
|
- If expired, fetch fresh data and update cache
|
||
|
|
- Merge data from multiple sources
|
||
|
|
6. Frontend displays city info on map with markers
|
||
|
|
7. User interacts with map and attraction list
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Environment Variables (.env)
|
||
|
|
|
||
|
|
```
|
||
|
|
APP_NAME="Wiki Travel Site"
|
||
|
|
APP_ENV=local
|
||
|
|
APP_KEY=
|
||
|
|
APP_DEBUG=true
|
||
|
|
APP_URL=http://localhost
|
||
|
|
|
||
|
|
# Database
|
||
|
|
DB_CONNECTION=mysql
|
||
|
|
DB_HOST=127.0.0.1
|
||
|
|
DB_PORT=3306
|
||
|
|
DB_DATABASE=wiki_travel_site
|
||
|
|
DB_USERNAME=root
|
||
|
|
DB_PASSWORD=
|
||
|
|
|
||
|
|
# Redis Cache
|
||
|
|
CACHE_DRIVER=redis
|
||
|
|
QUEUE_CONNECTION=redis
|
||
|
|
REDIS_HOST=127.0.0.1
|
||
|
|
REDIS_PASSWORD=null
|
||
|
|
REDIS_PORT=6379
|
||
|
|
|
||
|
|
# API Keys
|
||
|
|
OPENTRIPMAP_API_KEY=your_key_here
|
||
|
|
WIKIPEDIA_API_URL=https://en.wikipedia.org/api/rest_v1
|
||
|
|
OVERPASS_API_URL=https://overpass-api.de/api/interpreter
|
||
|
|
|
||
|
|
# Cache Settings (in days)
|
||
|
|
CACHE_WIKIPEDIA_TTL=30
|
||
|
|
CACHE_ATTRACTIONS_TTL=7
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Laravel-Specific Features to Leverage
|
||
|
|
|
||
|
|
- **Eloquent ORM** - For database interactions and relationships
|
||
|
|
- **Laravel Cache** - Redis-backed caching for API responses
|
||
|
|
- **Laravel Jobs/Queues** - Background processing for API requests
|
||
|
|
- **Artisan Commands** - Custom commands for cache management
|
||
|
|
- **API Resources** - Transform Eloquent models to JSON
|
||
|
|
- **Form Requests** - Validate incoming city search requests
|
||
|
|
- **Laravel Mix/Vite** - Asset compilation for Tailwind CSS and Alpine.js
|
||
|
|
- **Blade Components** - Reusable UI components (search bar, attraction card, etc.)
|
||
|
|
- **Laravel Sanctum** - API authentication (for future mobile app)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Project Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
wiki_travel_site/
|
||
|
|
├── app/
|
||
|
|
│ ├── Http/
|
||
|
|
│ │ ├── Controllers/
|
||
|
|
│ │ │ ├── CityController.php
|
||
|
|
│ │ │ ├── AuthController.php
|
||
|
|
│ │ │ └── API/
|
||
|
|
│ │ │ ├── CityController.php
|
||
|
|
│ │ │ ├── FavoriteController.php
|
||
|
|
│ │ │ ├── TripController.php
|
||
|
|
│ │ │ └── UserController.php
|
||
|
|
│ │ ├── Resources/
|
||
|
|
│ │ │ ├── CityResource.php
|
||
|
|
│ │ │ ├── AttractionResource.php
|
||
|
|
│ │ │ ├── TripResource.php
|
||
|
|
│ │ │ └── UserResource.php
|
||
|
|
│ │ ├── Requests/
|
||
|
|
│ │ │ ├── RegisterRequest.php
|
||
|
|
│ │ │ ├── LoginRequest.php
|
||
|
|
│ │ │ └── TripRequest.php
|
||
|
|
│ │ └── Middleware/
|
||
|
|
│ │ └── EnsureTokenIsValid.php
|
||
|
|
│ ├── Models/
|
||
|
|
│ │ ├── User.php (with HasApiTokens trait)
|
||
|
|
│ │ ├── City.php
|
||
|
|
│ │ ├── Attraction.php
|
||
|
|
│ │ ├── Trip.php
|
||
|
|
│ │ └── Favorite.php
|
||
|
|
│ ├── Services/
|
||
|
|
│ │ ├── WikipediaService.php
|
||
|
|
│ │ ├── OpenTripMapService.php
|
||
|
|
│ │ └── OverpassService.php
|
||
|
|
│ └── Jobs/
|
||
|
|
│ ├── FetchCityDataJob.php
|
||
|
|
│ └── FetchAttractionsJob.php
|
||
|
|
├── database/
|
||
|
|
│ └── migrations/
|
||
|
|
│ ├── create_users_table.php
|
||
|
|
│ ├── create_cities_table.php
|
||
|
|
│ ├── create_attractions_table.php
|
||
|
|
│ ├── create_favorites_table.php
|
||
|
|
│ ├── create_trips_table.php
|
||
|
|
│ ├── create_city_trip_table.php
|
||
|
|
│ └── create_personal_access_tokens_table.php (Sanctum)
|
||
|
|
├── resources/
|
||
|
|
│ ├── views/
|
||
|
|
│ │ ├── layouts/
|
||
|
|
│ │ │ └── app.blade.php
|
||
|
|
│ │ ├── auth/
|
||
|
|
│ │ │ ├── login.blade.php
|
||
|
|
│ │ │ └── register.blade.php
|
||
|
|
│ │ ├── welcome.blade.php
|
||
|
|
│ │ └── cities/
|
||
|
|
│ │ └── show.blade.php
|
||
|
|
│ ├── css/
|
||
|
|
│ │ └── app.css (Tailwind)
|
||
|
|
│ └── js/
|
||
|
|
│ └── app.js (Alpine.js)
|
||
|
|
├── routes/
|
||
|
|
│ ├── web.php
|
||
|
|
│ └── api.php (with Sanctum auth middleware)
|
||
|
|
└── config/
|
||
|
|
├── cache.php
|
||
|
|
├── sanctum.php
|
||
|
|
├── cors.php (configured for mobile app)
|
||
|
|
└── services.php (API keys)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Mobile App Integration
|
||
|
|
|
||
|
|
The backend API is designed to support a mobile application using Laravel Sanctum for token-based authentication.
|
||
|
|
|
||
|
|
### Mobile App Authentication Flow
|
||
|
|
|
||
|
|
1. **Registration:** POST /api/register → Returns user + token
|
||
|
|
2. **Login:** POST /api/login → Returns user + token
|
||
|
|
3. **Authenticated Requests:** Include token in header: `Authorization: Bearer {token}`
|
||
|
|
4. **Logout:** POST /api/logout → Revokes token
|
||
|
|
|
||
|
|
### Mobile App Features
|
||
|
|
|
||
|
|
- Browse and search cities (no auth required)
|
||
|
|
- View city details and attractions on map
|
||
|
|
- User registration and login
|
||
|
|
- Save favorite cities
|
||
|
|
- Create and manage trip itineraries
|
||
|
|
- Sync favorites and trips across devices
|
||
|
|
- Offline caching (handle on mobile side)
|
||
|
|
|
||
|
|
### CORS Configuration
|
||
|
|
|
||
|
|
Configure `config/cors.php` to allow requests from mobile app:
|
||
|
|
```php
|
||
|
|
'paths' => ['api/*'],
|
||
|
|
'allowed_origins' => ['*'], // Or specify mobile app origins
|
||
|
|
'allowed_methods' => ['*'],
|
||
|
|
'allowed_headers' => ['*'],
|
||
|
|
'supports_credentials' => false,
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Future Enhancements
|
||
|
|
|
||
|
|
- Weather information integration
|
||
|
|
- User reviews and ratings with Laravel policies
|
||
|
|
- Photo galleries from Wikimedia Commons
|
||
|
|
- Nearby restaurants and hotels
|
||
|
|
- Public transportation information
|
||
|
|
- Push notifications for trip reminders (mobile app)
|
||
|
|
- Social sharing of trips
|
||
|
|
- Multi-language support with Laravel localization
|
||
|
|
- Laravel Scout for full-text search
|
||
|
|
- Telescope for debugging during development
|
||
|
|
- Rate limiting for API endpoints
|
||
|
|
- Email verification for user accounts
|
||
|
|
- Password reset functionality
|
||
|
|
- User profile pictures and customization
|