auto-commit for 01035626-fc86-4553-b85b-3396ef438dce

This commit is contained in:
emergent-agent-e1
2026-05-06 04:10:43 +00:00
parent 0f89cba316
commit cbc00fa39c
103 changed files with 9779 additions and 0 deletions
@@ -0,0 +1,63 @@
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import HTTPException, Security, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import os
from dotenv import load_dotenv
from pathlib import Path
# Load environment variables
ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')
# JWT Configuration
SECRET_KEY = os.environ['JWT_SECRET_KEY']
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24 hours
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Security
security = HTTPBearer()
def hash_password(password: str) -> str:
"""Hash a password"""
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""Verify a password against a hash"""
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""Create a JWT access token"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_access_token(token: str) -> dict:
"""Decode and verify a JWT token"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
raise HTTPException(status_code=401, detail="Could not validate credentials")
async def get_current_admin(credentials: HTTPAuthorizationCredentials = Security(security)) -> dict:
"""Dependency to get current admin from JWT token"""
token = credentials.credentials
payload = decode_access_token(token)
email: str = payload.get("sub")
if email is None:
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
return {"email": email}
@@ -0,0 +1 @@
# Models module
@@ -0,0 +1,85 @@
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
import uuid
class Destination(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str
location: str
description: str
image: str
category: str # City, Beach, Adventure
rating: float
price: float
currency: str = "USD"
created_at: datetime = Field(default_factory=datetime.utcnow)
class DestinationCreate(BaseModel):
name: str
location: str
description: str
image: str
category: str
rating: float
price: float
currency: str = "USD"
class DestinationUpdate(BaseModel):
name: Optional[str] = None
location: Optional[str] = None
description: Optional[str] = None
image: Optional[str] = None
category: Optional[str] = None
rating: Optional[float] = None
price: Optional[float] = None
currency: Optional[str] = None
class Special(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
destination_id: str
discount: float
end_date: str # ISO format date
highlights: List[str]
created_at: datetime = Field(default_factory=datetime.utcnow)
class SpecialCreate(BaseModel):
destination_id: str
discount: float
end_date: str
highlights: List[str]
class SpecialUpdate(BaseModel):
discount: Optional[float] = None
end_date: Optional[str] = None
highlights: Optional[List[str]] = None
class AdminUser(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
email: str
password_hash: str
created_at: datetime = Field(default_factory=datetime.utcnow)
class AdminLogin(BaseModel):
email: str
password: str
class Contact(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str
email: str
message: str
created_at: datetime = Field(default_factory=datetime.utcnow)
class ContactCreate(BaseModel):
name: str
email: str
message: str
class NewsletterSubscriber(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
email: str
subscribed_at: datetime = Field(default_factory=datetime.utcnow)
class NewsletterSubscribe(BaseModel):
email: str
@@ -0,0 +1,123 @@
aiohappyeyeballs==2.6.1
aiohttp==3.13.3
aiosignal==1.4.0
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
attrs==25.4.0
bcrypt==4.1.3
black==26.1.0
boto3==1.42.58
botocore==1.42.58
certifi==2026.2.25
cffi==2.0.0
charset-normalizer==3.4.4
click==8.3.1
cryptography==46.0.5
distro==1.9.0
dnspython==2.8.0
ecdsa==0.19.1
email-validator==2.3.0
emergentintegrations==0.1.0
fastapi==0.110.1
fastuuid==0.14.0
filelock==3.25.0
flake8==7.3.0
frozenlist==1.8.0
fsspec==2026.2.0
google-ai-generativelanguage==0.6.15
google-api-core==2.30.0
google-api-python-client==2.191.0
google-auth==2.49.0.dev0
google-auth-httplib2==0.3.0
google-genai==1.65.0
google-generativeai==0.8.6
googleapis-common-protos==1.72.0
grpcio==1.78.0
grpcio-status==1.71.2
h11==0.16.0
hf-xet==1.3.2
httpcore==1.0.9
httplib2==0.31.2
httpx==0.28.1
huggingface_hub==1.5.0
idna==3.11
importlib_metadata==8.7.1
iniconfig==2.3.0
isort==8.0.0
Jinja2==3.1.6
jiter==0.13.0
jmespath==1.1.0
jq==1.11.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
librt==0.8.1
litellm==1.80.0
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mccabe==0.7.0
mdurl==0.1.2
motor==3.3.1
multidict==6.7.1
mypy==1.19.1
mypy_extensions==1.1.0
numpy==2.4.2
oauthlib==3.3.1
openai==1.99.9
packaging==26.0
pandas==3.0.1
passlib==1.7.4
pathspec==1.0.4
pillow==12.1.1
platformdirs==4.9.2
pluggy==1.6.0
propcache==0.4.1
proto-plus==1.27.1
protobuf==5.29.6
pyasn1==0.6.2
pyasn1_modules==0.4.2
pycodestyle==2.14.0
pycparser==3.0
pydantic==2.12.5
pydantic_core==2.41.5
pyflakes==3.4.0
Pygments==2.19.2
PyJWT==2.11.0
pymongo==4.5.0
pyparsing==3.3.2
pytest==9.0.2
python-dateutil==2.9.0.post0
python-dotenv==1.2.1
python-jose==3.5.0
python-multipart==0.0.22
pytokens==0.4.1
PyYAML==6.0.3
referencing==0.37.0
regex==2026.2.28
requests==2.32.5
requests-oauthlib==2.0.0
rich==14.3.3
rpds-py==0.30.0
rsa==4.9.1
s3transfer==0.16.0
s5cmd==0.2.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
starlette==0.37.2
stripe==14.4.0
tenacity==9.1.4
tiktoken==0.12.0
tokenizers==0.22.2
tqdm==4.67.3
typer==0.24.1
typing-inspection==0.4.2
typing_extensions==4.15.0
tzdata==2025.3
uritemplate==4.2.0
urllib3==2.6.3
uvicorn==0.25.0
watchfiles==1.1.1
websockets==16.0
yarl==1.23.0
zipp==3.23.0
@@ -0,0 +1 @@
# Routes module
@@ -0,0 +1,61 @@
from fastapi import APIRouter, HTTPException, Depends
from models.schemas import AdminLogin
from auth import hash_password, verify_password, create_access_token
from motor.motor_asyncio import AsyncIOMotorClient
import os
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
# MongoDB connection will be injected
db = None
def set_db(database):
global db
db = database
@router.post("/login")
async def login(credentials: AdminLogin):
"""Admin login endpoint"""
# Find admin user
admin = await db.admin_users.find_one({"email": credentials.email})
if not admin:
raise HTTPException(status_code=401, detail="Invalid email or password")
# Verify password
if not verify_password(credentials.password, admin["password_hash"]):
raise HTTPException(status_code=401, detail="Invalid email or password")
# Create access token
access_token = create_access_token(data={"sub": admin["email"]})
return {
"access_token": access_token,
"token_type": "bearer",
"email": admin["email"]
}
@router.post("/verify")
async def verify_token(admin: dict = Depends(lambda: __import__('auth').get_current_admin)):
"""Verify JWT token"""
return {"valid": True, "email": admin["email"]}
@router.post("/initialize-admin")
async def initialize_admin():
"""Initialize default admin user (for development/setup only)"""
# Check if admin already exists
existing_admin = await db.admin_users.find_one({"email": "admin@epictravel.com"})
if existing_admin:
return {"message": "Admin user already exists"}
# Create default admin
admin_data = {
"email": "admin@epictravel.com",
"password_hash": hash_password("admin123"),
"created_at": __import__('datetime').datetime.utcnow()
}
await db.admin_users.insert_one(admin_data)
return {"message": "Admin user created successfully", "email": "admin@epictravel.com"}
@@ -0,0 +1,113 @@
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Optional
from models.schemas import Destination, DestinationCreate, DestinationUpdate
from auth import get_current_admin
import uuid
from datetime import datetime
router = APIRouter(prefix="/api/destinations", tags=["Destinations"])
# MongoDB connection will be injected
db = None
def set_db(database):
global db
db = database
@router.get("", response_model=List[Destination])
async def get_destinations(category: Optional[str] = None, search: Optional[str] = None):
"""Get all destinations with optional filtering"""
query = {}
if category and category != "All":
query["category"] = category
if search:
query["$or"] = [
{"name": {"$regex": search, "$options": "i"}},
{"location": {"$regex": search, "$options": "i"}}
]
destinations = await db.destinations.find(query, {'_id': 0}).limit(100).to_list(100)
# Convert MongoDB _id to id for response
for dest in destinations:
if "_id" in dest:
del dest["_id"]
return destinations
@router.get("/{destination_id}", response_model=Destination)
async def get_destination(destination_id: str):
"""Get a single destination by ID"""
destination = await db.destinations.find_one({"id": destination_id})
if not destination:
raise HTTPException(status_code=404, detail="Destination not found")
if "_id" in destination:
del destination["_id"]
return destination
@router.post("", response_model=Destination)
async def create_destination(
destination: DestinationCreate,
admin: dict = Depends(get_current_admin)
):
"""Create a new destination (admin only)"""
destination_data = destination.dict()
destination_data["id"] = str(uuid.uuid4())
destination_data["created_at"] = datetime.utcnow()
await db.destinations.insert_one(destination_data)
if "_id" in destination_data:
del destination_data["_id"]
return destination_data
@router.put("/{destination_id}", response_model=Destination)
async def update_destination(
destination_id: str,
destination_update: DestinationUpdate,
admin: dict = Depends(get_current_admin)
):
"""Update a destination (admin only)"""
# Check if destination exists
existing = await db.destinations.find_one({"id": destination_id})
if not existing:
raise HTTPException(status_code=404, detail="Destination not found")
# Update only provided fields
update_data = {k: v for k, v in destination_update.dict().items() if v is not None}
if update_data:
await db.destinations.update_one(
{"id": destination_id},
{"$set": update_data}
)
# Fetch updated destination
updated = await db.destinations.find_one({"id": destination_id})
if "_id" in updated:
del updated["_id"]
return updated
@router.delete("/{destination_id}")
async def delete_destination(
destination_id: str,
admin: dict = Depends(get_current_admin)
):
"""Delete a destination (admin only)"""
result = await db.destinations.delete_one({"id": destination_id})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail="Destination not found")
# Also delete any specials for this destination
await db.specials.delete_many({"destination_id": destination_id})
return {"message": "Destination deleted successfully"}
@@ -0,0 +1,37 @@
<?php
/**
* Download Deployment Packages
*/
if ($method === 'GET') {
if ($id === 'php-package') {
// Serve PHP/cPanel package
$file = '/app/cpanel_php/epic-travel-php-cpanel.zip';
if (!file_exists($file)) {
jsonResponse(['error' => 'PHP package not found'], 404);
}
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="epic-travel-php-cpanel.zip"');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
if ($id === 'list') {
jsonResponse([
'packages' => [
[
'name' => 'PHP/cPanel Package',
'description' => 'Standard cPanel hosting with PHP & MySQL (No SSH/Python required)',
'size' => '790 KB',
'download_url' => '/api/download/php-package',
'requirements' => ['PHP 7.4+', 'MySQL 5.7+', 'cPanel', 'FTP/File Manager access']
]
]
]);
}
}
jsonResponse(['error' => 'Invalid download endpoint'], 404);
@@ -0,0 +1,75 @@
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from pathlib import Path
import os
router = APIRouter(prefix="/api/download", tags=["Downloads"])
# Package directory
PACKAGE_DIR = Path("/app/cpanel_deployment")
@router.get("/package/{format}")
async def download_package(format: str):
"""
Download the cPanel deployment package
Formats: tar.gz or zip
"""
# Find the package file
if format == "tar.gz":
pattern = "epic-travel-cpanel-*.tar.gz"
elif format == "zip":
pattern = "epic-travel-cpanel-*.zip"
else:
raise HTTPException(status_code=400, detail="Invalid format. Use 'tar.gz' or 'zip'")
# Find the latest package
import glob
files = glob.glob(str(PACKAGE_DIR / pattern))
if not files:
raise HTTPException(status_code=404, detail="Package not found")
# Get the most recent file
latest_file = max(files, key=os.path.getctime)
file_path = Path(latest_file)
if not file_path.exists():
raise HTTPException(status_code=404, detail="Package file not found")
# Determine media type
media_type = "application/gzip" if format == "tar.gz" else "application/zip"
return FileResponse(
path=str(file_path),
media_type=media_type,
filename=file_path.name,
headers={
"Content-Disposition": f"attachment; filename={file_path.name}"
}
)
@router.get("/list")
async def list_packages():
"""
List available deployment packages
"""
import glob
packages = []
# Find all package files
for pattern in ["*.tar.gz", "*.zip"]:
files = glob.glob(str(PACKAGE_DIR / pattern))
for file_path in files:
file_stat = os.stat(file_path)
packages.append({
"filename": Path(file_path).name,
"size": f"{file_stat.st_size / 1024:.0f} KB",
"format": "tar.gz" if file_path.endswith(".tar.gz") else "zip",
"download_url": f"/api/download/package/{'tar.gz' if file_path.endswith('.tar.gz') else 'zip'}"
})
return {
"packages": packages,
"total": len(packages)
}
@@ -0,0 +1,82 @@
from fastapi import APIRouter, HTTPException, File, UploadFile
from fastapi.responses import FileResponse
from typing import List
from models.schemas import ContactCreate, NewsletterSubscribe
from datetime import datetime
from pathlib import Path
import uuid
import os
import shutil
router = APIRouter(prefix="/api", tags=["Other"])
# MongoDB connection will be injected
db = None
# Upload directory setup
UPLOAD_DIR = Path(__file__).parent.parent / 'uploads'
UPLOAD_DIR.mkdir(exist_ok=True)
def set_db(database):
global db
db = database
@router.post("/contact")
async def submit_contact(contact: ContactCreate):
"""Submit a contact form"""
contact_data = contact.dict()
contact_data["id"] = str(uuid.uuid4())
contact_data["created_at"] = datetime.utcnow()
await db.contacts.insert_one(contact_data)
return {"message": "Contact form submitted successfully"}
@router.post("/newsletter/subscribe")
async def subscribe_newsletter(subscriber: NewsletterSubscribe):
"""Subscribe to newsletter"""
# Check if already subscribed
existing = await db.newsletter_subscribers.find_one({"email": subscriber.email})
if existing:
return {"message": "Email already subscribed"}
subscriber_data = subscriber.dict()
subscriber_data["id"] = str(uuid.uuid4())
subscriber_data["subscribed_at"] = datetime.utcnow()
await db.newsletter_subscribers.insert_one(subscriber_data)
return {"message": "Successfully subscribed to newsletter"}
@router.post("/upload/image")
async def upload_image(file: UploadFile = File(...)):
"""Upload an image file"""
# Validate file type
allowed_extensions = [".jpg", ".jpeg", ".png", ".webp"]
file_ext = os.path.splitext(file.filename)[1].lower()
if file_ext not in allowed_extensions:
raise HTTPException(status_code=400, detail="Invalid file type. Allowed: jpg, jpeg, png, webp")
# Generate unique filename
unique_filename = f"{uuid.uuid4()}{file_ext}"
file_path = UPLOAD_DIR / unique_filename
# Save file
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
# Return URL
file_url = f"/api/uploads/{unique_filename}"
return {"url": file_url, "filename": unique_filename}
@router.get("/uploads/{filename}")
async def get_uploaded_image(filename: str):
"""Serve uploaded images"""
file_path = UPLOAD_DIR / filename
if not file_path.exists():
raise HTTPException(status_code=404, detail="Image not found")
return FileResponse(str(file_path))
@@ -0,0 +1,109 @@
from fastapi import APIRouter, HTTPException, Depends
from typing import List
from models.schemas import Special, SpecialCreate, SpecialUpdate
from auth import get_current_admin
import uuid
from datetime import datetime
router = APIRouter(prefix="/api/specials", tags=["Specials"])
# MongoDB connection will be injected
db = None
def set_db(database):
global db
db = database
@router.get("", response_model=List[Special])
async def get_specials():
"""Get all weekly specials"""
specials = await db.specials.find({}, {'_id': 0}).limit(100).to_list(100)
# Convert MongoDB _id to id for response
for special in specials:
if "_id" in special:
del special["_id"]
return specials
@router.get("/{special_id}", response_model=Special)
async def get_special(special_id: str):
"""Get a single special by ID"""
special = await db.specials.find_one({"id": special_id})
if not special:
raise HTTPException(status_code=404, detail="Special not found")
if "_id" in special:
del special["_id"]
return special
@router.post("", response_model=Special)
async def create_special(
special: SpecialCreate,
admin: dict = Depends(get_current_admin)
):
"""Add a destination to specials (admin only)"""
# Check if destination exists
destination = await db.destinations.find_one({"id": special.destination_id})
if not destination:
raise HTTPException(status_code=404, detail="Destination not found")
# Check if special already exists for this destination
existing = await db.specials.find_one({"destination_id": special.destination_id})
if existing:
raise HTTPException(status_code=400, detail="Special already exists for this destination")
special_data = special.dict()
special_data["id"] = str(uuid.uuid4())
special_data["created_at"] = datetime.utcnow()
await db.specials.insert_one(special_data)
if "_id" in special_data:
del special_data["_id"]
return special_data
@router.put("/{special_id}", response_model=Special)
async def update_special(
special_id: str,
special_update: SpecialUpdate,
admin: dict = Depends(get_current_admin)
):
"""Update a special (admin only)"""
# Check if special exists
existing = await db.specials.find_one({"id": special_id})
if not existing:
raise HTTPException(status_code=404, detail="Special not found")
# Update only provided fields
update_data = {k: v for k, v in special_update.dict().items() if v is not None}
if update_data:
await db.specials.update_one(
{"id": special_id},
{"$set": update_data}
)
# Fetch updated special
updated = await db.specials.find_one({"id": special_id})
if "_id" in updated:
del updated["_id"]
return updated
@router.delete("/destination/{destination_id}")
async def delete_special_by_destination(
destination_id: str,
admin: dict = Depends(get_current_admin)
):
"""Remove a destination from specials (admin only)"""
result = await db.specials.delete_one({"destination_id": destination_id})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail="Special not found for this destination")
return {"message": "Special removed successfully"}
@@ -0,0 +1,262 @@
from fastapi import FastAPI
from dotenv import load_dotenv
from starlette.middleware.cors import CORSMiddleware
from motor.motor_asyncio import AsyncIOMotorClient
import os
import logging
from pathlib import Path
from auth import hash_password
from datetime import datetime
# Import route modules
from routes import auth_routes, destination_routes, special_routes, other_routes, download_routes
ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')
# MongoDB connection
mongo_url = os.environ['MONGO_URL']
client = AsyncIOMotorClient(mongo_url)
db = client[os.environ['DB_NAME']]
# Inject database into route modules
auth_routes.set_db(db)
destination_routes.set_db(db)
special_routes.set_db(db)
other_routes.set_db(db)
# Create the main app
app = FastAPI(title="Epic Travel & Destinations API")
# Include routers
app.include_router(auth_routes.router)
app.include_router(destination_routes.router)
app.include_router(special_routes.router)
app.include_router(other_routes.router)
app.include_router(download_routes.router)
# Health check endpoint
@app.get("/api")
async def root():
return {"message": "Epic Travel API is running", "status": "healthy"}
app.add_middleware(
CORSMiddleware,
allow_credentials=True,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
@app.on_event("startup")
async def startup_db_client():
"""Initialize database with seed data if empty"""
try:
# Check if admin user exists, if not create one
admin_exists = await db.admin_users.find_one({"email": "admin@epictravel.com"})
if not admin_exists:
admin_data = {
"id": "admin-1",
"email": "admin@epictravel.com",
"password_hash": hash_password(os.environ['ADMIN_DEFAULT_PASSWORD']),
"created_at": datetime.utcnow()
}
await db.admin_users.insert_one(admin_data)
logger.info("Default admin user created")
# Check if destinations exist, if not seed initial data
dest_count = await db.destinations.count_documents({})
if dest_count == 0:
# Seed initial destinations
initial_destinations = [
{
"id": "1",
"name": "Paris",
"location": "France",
"description": "Experience the romance and elegance of the City of Light. Visit iconic landmarks like the Eiffel Tower, Louvre Museum, and stroll along the Champs-Élysées.",
"image": "https://images.unsplash.com/photo-1502602898657-3e91760cbb34?w=800&q=80",
"category": "City",
"rating": 4.9,
"price": 1299,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "2",
"name": "Bali",
"location": "Indonesia",
"description": "Discover tropical paradise with stunning beaches, ancient temples, lush rice terraces, and vibrant culture in this Indonesian gem.",
"image": "https://images.unsplash.com/photo-1537996194471-e657df975ab4?w=800&q=80",
"category": "Beach",
"rating": 4.8,
"price": 899,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "3",
"name": "Tokyo",
"location": "Japan",
"description": "Immerse yourself in the perfect blend of ancient tradition and cutting-edge technology in Japan's bustling capital city.",
"image": "https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?w=800&q=80",
"category": "City",
"rating": 4.9,
"price": 1499,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "4",
"name": "Santorini",
"location": "Greece",
"description": "Marvel at breathtaking sunsets, whitewashed buildings, and crystal-clear waters in this stunning Greek island paradise.",
"image": "https://images.unsplash.com/photo-1613395877344-13d4a8e0d49e?w=800&q=80",
"category": "Beach",
"rating": 4.9,
"price": 1199,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "5",
"name": "Iceland",
"location": "Iceland",
"description": "Witness the Northern Lights, explore glaciers, geysers, and volcanic landscapes in this land of fire and ice.",
"image": "https://images.unsplash.com/photo-1504829857797-ddff29c27927?w=800&q=80",
"category": "Adventure",
"rating": 4.8,
"price": 1699,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "6",
"name": "Dubai",
"location": "UAE",
"description": "Experience luxury and innovation in the desert with world-class shopping, stunning architecture, and endless entertainment.",
"image": "https://images.unsplash.com/photo-1512453979798-5ea266f8880c?w=800&q=80",
"category": "City",
"rating": 4.7,
"price": 1399,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "7",
"name": "Maldives",
"location": "Maldives",
"description": "Relax in overwater bungalows, dive in pristine coral reefs, and enjoy the ultimate tropical island getaway.",
"image": "https://images.unsplash.com/photo-1514282401047-d79a71a590e8?w=800&q=80",
"category": "Beach",
"rating": 5.0,
"price": 2199,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "8",
"name": "New York",
"location": "USA",
"description": "Explore the city that never sleeps with iconic landmarks, world-class museums, Broadway shows, and diverse neighborhoods.",
"image": "https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9?w=800&q=80",
"category": "City",
"rating": 4.8,
"price": 1099,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "9",
"name": "Machu Picchu",
"location": "Peru",
"description": "Trek to the ancient Incan citadel nestled high in the Andes Mountains, one of the New Seven Wonders of the World.",
"image": "https://images.unsplash.com/photo-1587595431973-160d0d94add1?w=800&q=80",
"category": "Adventure",
"rating": 4.9,
"price": 1299,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "10",
"name": "Swiss Alps",
"location": "Switzerland",
"description": "Ski pristine slopes, hike mountain trails, and enjoy charming alpine villages with breathtaking mountain vistas.",
"image": "https://images.unsplash.com/photo-1531366936337-7c912a4589a7?w=800&q=80",
"category": "Adventure",
"rating": 4.9,
"price": 1799,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "11",
"name": "Venice",
"location": "Italy",
"description": "Glide through romantic canals, admire Renaissance architecture, and savor authentic Italian cuisine in this unique floating city.",
"image": "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9?w=800&q=80",
"category": "City",
"rating": 4.8,
"price": 1149,
"currency": "USD",
"created_at": datetime.utcnow()
},
{
"id": "12",
"name": "Safari Kenya",
"location": "Kenya",
"description": "Witness the Great Migration, spot the Big Five, and experience the raw beauty of African wilderness.",
"image": "https://images.unsplash.com/photo-1516426122078-c23e76319801?w=800&q=80",
"category": "Adventure",
"rating": 4.9,
"price": 2499,
"currency": "USD",
"created_at": datetime.utcnow()
}
]
await db.destinations.insert_many(initial_destinations)
logger.info(f"Seeded {len(initial_destinations)} initial destinations")
# Seed initial specials
initial_specials = [
{
"id": "special-1",
"destination_id": "2",
"discount": 25,
"end_date": "2025-02-28",
"highlights": ["Free spa treatment", "Complimentary airport transfer", "Sunset dinner cruise"],
"created_at": datetime.utcnow()
},
{
"id": "special-2",
"destination_id": "4",
"discount": 30,
"end_date": "2025-03-15",
"highlights": ["Wine tasting tour", "Private yacht excursion", "Luxury accommodation upgrade"],
"created_at": datetime.utcnow()
},
{
"id": "special-3",
"destination_id": "7",
"discount": 20,
"end_date": "2025-02-20",
"highlights": ["Snorkeling adventure", "Couples massage", "Romantic beach dinner"],
"created_at": datetime.utcnow()
}
]
await db.specials.insert_many(initial_specials)
logger.info(f"Seeded {len(initial_specials)} initial specials")
except Exception as e:
logger.error(f"Error during startup: {str(e)}")
@app.on_event("shutdown")
async def shutdown_db_client():
client.close()
@@ -0,0 +1,41 @@
import asyncio
from motor.motor_asyncio import AsyncIOMotorClient
from passlib.context import CryptContext
import os
from dotenv import load_dotenv
from pathlib import Path
# Load environment variables
ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
async def update_admin_password():
# Connect to MongoDB
mongo_url = os.environ['MONGO_URL']
client = AsyncIOMotorClient(mongo_url)
db = client[os.environ['DB_NAME']]
# New password
new_password = "Joker1974!!!"
new_password_hash = pwd_context.hash(new_password)
# Update admin password
result = await db.admin_users.update_one(
{"email": "admin@epictravel.com"},
{"$set": {"password_hash": new_password_hash}}
)
if result.modified_count > 0:
print(f"✓ Admin password updated successfully!")
print(f"✓ Email: admin@epictravel.com")
print(f"✓ New Password: {new_password}")
else:
print("✗ Failed to update password or admin user not found")
client.close()
if __name__ == "__main__":
asyncio.run(update_admin_password())