auto-commit for 020628f5-4bfa-4157-a41e-90eec0ddfeec

This commit is contained in:
emergent-agent-e1
2026-05-06 03:47:14 +00:00
parent 6f31e2d1bf
commit 39fbd407ec
33 changed files with 2887 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,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"}