mirror of
https://github.com/myronblair/epic-download
synced 2026-07-28 04:52:29 -05:00
auto-commit for 01035626-fc86-4553-b85b-3396ef438dce
This commit is contained in:
@@ -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"}
|
||||
Reference in New Issue
Block a user