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 @@
# 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"}