Tuesday, December 30, 2025
Now let me create all the files for the restaurant application:
Action: file_editor create /app/backend/server.py --file-text "from fastapi import FastAPI, APIRouter, HTTPException, Request
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 pydantic import BaseModel, Field, ConfigDict
from typing import List, Optional, Dict
import uuid
from datetime import datetime, timezone, date
from emergentintegrations.payments.stripe.checkout import StripeCheckout, CheckoutSessionResponse, CheckoutStatusResponse, CheckoutSessionRequest
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']]
# Stripe setup
stripe_api_key = os.environ.get('STRIPE_API_KEY')
# Create the main app
app = FastAPI()
api_router = APIRouter(prefix=\"/api\")
# ============ MODELS ============
class MenuItem(BaseModel):
model_config = ConfigDict(extra=\"ignore\")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str
description: str
price: float
category: str # appetizers, mains, desserts, drinks
image_url: Optional[str] = None
is_available: bool = True
is_special: bool = False
special_price: Optional[float] = None
class MenuItemCreate(BaseModel):
name: str
description: str
price: float
category: str
image_url: Optional[str] = None
is_available: bool = True
is_special: bool = False
special_price: Optional[float] = None
class Reservation(BaseModel):
model_config = ConfigDict(extra=\"ignore\")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
customer_name: str
customer_email: str
customer_phone: str
date: str
time_slot: str
party_size: int
special_requests: Optional[str] = None
status: str = \"confirmed\"
created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
class ReservationCreate(BaseModel):
customer_name: str
customer_email: str
customer_phone: str
date: str
time_slot: str
party_size: int
special_requests: Optional[str] = None
class CartItem(BaseModel):
menu_item_id: str
name: str
price: float
quantity: int
class Order(BaseModel):
model_config = ConfigDict(extra=\"ignore\")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
items: List[CartItem]
total: float
customer_name: str
customer_email: str
customer_phone: str
order_type: str # pickup or delivery
delivery_address: Optional[str] = None
special_instructions: Optional[str] = None
status: str = \"pending\"
payment_status: str = \"pending\"
session_id: Optional[str] = None
created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
class OrderCreate(BaseModel):
items: List[CartItem]
customer_name: str
customer_email: str
customer_phone: str
order_type: str
delivery_address: Optional[str] = None
special_instructions: Optional[str] = None
class PaymentTransaction(BaseModel):
model_config = ConfigDict(extra=\"ignore\")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
session_id: str
order_id: str
amount: float
currency: str = \"usd\"
payment_status: str = \"initiated\"
metadata: Optional[Dict[str, str]] = None
created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
updated_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
class Special(BaseModel):
model_config = ConfigDict(extra=\"ignore\")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
title: str
description: str
menu_item_id: Optional[str] = None
discount_percent: Optional[int] = None
valid_from: str
valid_until: str
image_url: Optional[str] = None
is_active: bool = True
class SpecialCreate(BaseModel):
title: str
description: str
menu_item_id: Optional[str] = None
discount_percent: Optional[int] = None
valid_from: str
valid_until: str
image_url: Optional[str] = None
class CheckoutRequest(BaseModel):
order_id: str
origin_url: str
# ============ MENU ENDPOINTS ============
@api_router.get(\"/\")
async def root():
return {\"message\": \"Welcome to The Golden Spoon API\"}
@api_router.get(\"/menu\", response_model=List[MenuItem])
async def get_menu():
items = await db.menu_items.find({}, {\"_id\": 0}).to_list(1000)
return items
@api_router.get(\"/menu/category/{category}\", response_model=List[MenuItem])
async def get_menu_by_category(category: str):
items = await db.menu_items.find({\"category\": category}, {\"_id\": 0}).to_list(1000)
return items
@api_router.get(\"/menu/{item_id}\", response_model=MenuItem)
async def get_menu_item(item_id: str):
item = await db.menu_items.find_one({\"id\": item_id}, {\"_id\": 0})
if not item:
raise HTTPException(status_code=404, detail=\"Menu item not found\")
return item
@api_router.post(\"/menu\", response_model=MenuItem)
async def create_menu_item(item: MenuItemCreate):
menu_item = MenuItem(**item.model_dump())
doc = menu_item.model_dump()
await db.menu_items.insert_one(doc)
return menu_item
@api_router.put(\"/menu/{item_id}\", response_model=MenuItem)
async def update_menu_item(item_id: str, item: MenuItemCreate):
existing = await db.menu_items.find_one({\"id\": item_id})
if not existing:
raise HTTPException(status_code=404, detail=\"Menu item not found\")
update_data = item.model_dump()
await db.menu_items.update_one({\"id\": item_id}, {\"$set\": update_data})
updated = await db.menu_items.find_one({\"id\": item_id}, {\"_id\": 0})
return updated
@api_router.delete(\"/menu/{item_id}\")
async def delete_menu_item(item_id: str):
result = await db.menu_items.delete_one({\"id\": item_id})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail=\"Menu item not found\")
return {\"message\": \"Item deleted\"}
# ============ SPECIALS ENDPOINTS ============
@api_router.get(\"/specials\", response_model=List[Special])
async def get_specials():
specials = await db.specials.find({\"is_active\": True}, {\"_id\": 0}).to_list(100)
return specials
@api_router.post(\"/specials\", response_model=Special)
async def create_special(special: SpecialCreate):
special_obj = Special(**special.model_dump())
doc = special_obj.model_dump()
await db.specials.insert_one(doc)
return special_obj
@api_router.delete(\"/specials/{special_id}\")
async def delete_special(special_id: str):
result = await db.specials.delete_one({\"id\": special_id})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail=\"Special not found\")
return {\"message\": \"Special deleted\"}
# ============ RESERVATION ENDPOINTS ============
@api_router.get(\"/reservations/slots/{date_str}\")
async def get_available_slots(date_str: str):
# All possible time slots
all_slots = [
\"11:00 AM\", \"11:30 AM\", \"12:00 PM\", \"12:30 PM\",
\"1:00 PM\", \"1:30 PM\", \"2:00 PM\", \"2:30 PM\",
\"5:00 PM\", \"5:30 PM\", \"6:00 PM\", \"6:30 PM\",
\"7:00 PM\", \"7:30 PM\", \"8:00 PM\", \"8:30 PM\", \"9:00 PM\"
]
# Get booked slots for this date
booked = await db.reservations.find(
{\"date\": date_str, \"status\": {\"$ne\": \"cancelled\"}},
{\"_id\": 0, \"time_slot\": 1}
).to_list(100)
booked_slots = [r[\"time_slot\"] for r in booked]
# Count bookings per slot (max 5 per slot)
slot_counts = {}
for slot in booked_slots:
slot_counts[slot] = slot_counts.get(slot, 0) + 1
available = [s for s in all_slots if slot_counts.get(s, 0) < 5]
return {\"date\": date_str, \"available_slots\": available}
@api_router.get(\"/reservations\", response_model=List[Reservation])
async def get_reservations():
reservations = await db.reservations.find({}, {\"_id\": 0}).to_list(1000)
return reservations
@api_router.post(\"/reservations\", response_model=Reservation)
async def create_reservation(reservation: ReservationCreate):
# Check if slot is available
slots_info = await get_available_slots(reservation.date)
if reservation.time_slot not in slots_info[\"available_slots\"]:
raise HTTPException(status_code=400, detail=\"This time slot is no longer available\")
reservation_obj = Reservation(**reservation.model_dump())
doc = reservation_obj.model_dump()
await db.reservations.insert_one(doc)
return reservation_obj
@api_router.put(\"/reservations/{reservation_id}/cancel\")
async def cancel_reservation(reservation_id: str):
result = await db.reservations.update_one(
{\"id\": reservation_id},
{\"$set\": {\"status\": \"cancelled\"}}
)
if result.matched_count == 0:
raise HTTPException(status_code=404, detail=\"Reservation not found\")
return {\"message\": \"Reservation cancelled\"}
# ============ ORDER ENDPOINTS ============
@api_router.post(\"/orders\", response_model=Order)
async def create_order(order: OrderCreate):
order_obj = Order(**order.model_dump())
doc = order_obj.model_dump()
await db.orders.insert_one(doc)
return order_obj
@api_router.get(\"/orders/{order_id}\", response_model=Order)
async def get_order(order_id: str):
order = await db.orders.find_one({\"id\": order_id}, {\"_id\": 0})
if not order:
raise HTTPException(status_code=404, detail=\"Order not found\")
return order
# ============ PAYMENT ENDPOINTS ============
@api_router.post(\"/checkout\")
async def create_checkout(checkout_req: CheckoutRequest, request: Request):
# Get the order
order = await db.orders.find_one({\"id\": checkout_req.order_id}, {\"_id\": 0})
if not order:
raise HTTPException(status_code=404, detail=\"Order not found\")
# Build URLs from origin
origin = checkout_req.origin_url
success_url = f\"{origin}/order-success?session_id={{CHECKOUT_SESSION_ID}}\"
cancel_url = f\"{origin}/order\"
# Initialize Stripe
host_url = str(request.base_url)
webhook_url = f\"{host_url}api/webhook/stripe\"
stripe_checkout = StripeCheckout(api_key=stripe_api_key, webhook_url=webhook_url)
# Create checkout session
checkout_request = CheckoutSessionRequest(
amount=float(order[\"total\"]),
currency=\"usd\",
success_url=success_url,
cancel_url=cancel_url,
metadata={
\"order_id\": checkout_req.order_id,
\"customer_email\": order[\"customer_email\"]
}
)
session: CheckoutSessionResponse = await stripe_checkout.create_checkout_session(checkout_request)
# Create payment transaction record
payment = PaymentTransaction(
session_id=session.session_id,
order_id=checkout_req.order_id,
amount=float(order[\"total\"]),
currency=\"usd\",
payment_status=\"initiated\",
metadata={\"customer_email\": order[\"customer_email\"]}
)
await db.payment_transactions.insert_one(payment.model_dump())
# Update order with session_id
await db.orders.update_one(
{\"id\": checkout_req.order_id},
{\"$set\": {\"session_id\": session.session_id}}
)
return {\"url\": session.url, \"session_id\": session.session_id}
@api_router.get(\"/checkout/status/{session_id}\")
async def get_checkout_status(session_id: str, request: Request):
host_url = str(request.base_url)
webhook_url = f\"{host_url}api/webhook/stripe\"
stripe_checkout = StripeCheckout(api_key=stripe_api_key, webhook_url=webhook_url)
status: CheckoutStatusResponse = await stripe_checkout.get_checkout_status(session_id)
# Update payment transaction
payment = await db.payment_transactions.find_one({\"session_id\": session_id}, {\"_id\": 0})
if payment and payment[\"payment_status\"] != status.payment_status:
await db.payment_transactions.update_one(
{\"session_id\": session_id},
{\"$set\": {
\"payment_status\": status.payment_status,
\"updated_at\": datetime.now(timezone.utc).isoformat()
}}
)
# Update order status if paid
if status.payment_status == \"paid\":
await db.orders.update_one(
{\"session_id\": session_id},
{\"$set\": {\"payment_status\": \"paid\", \"status\": \"confirmed\"}}
)
return {
\"status\": status.status,
\"payment_status\": status.payment_status,
\"amount_total\": status.amount_total,
\"currency\": status.currency
}
@api_router.post(\"/webhook/stripe\")
async def stripe_webhook(request: Request):
body = await request.body()
signature = request.headers.get(\"Stripe-Signature\")
host_url = str(request.base_url)
webhook_url = f\"{host_url}api/webhook/stripe\"
stripe_checkout = StripeCheckout(api_key=stripe_api_key, webhook_url=webhook_url)
try:
webhook_response = await stripe_checkout.handle_webhook(body, signature)
if webhook_response.payment_status == \"paid\":
await db.payment_transactions.update_one(
{\"session_id\": webhook_response.session_id},
{\"$set\": {
\"payment_status\": \"paid\",
\"updated_at\": datetime.now(timezone.utc).isoformat()
}}
)
await db.orders.update_one(
{\"session_id\": webhook_response.session_id},
{\"$set\": {\"payment_status\": \"paid\", \"status\": \"confirmed\"}}
)
return {\"status\": \"success\"}
except Exception as e:
logging.error(f\"Webhook error: {e}\")
return {\"status\": \"error\", \"message\": str(e)}
# ============ SEED DATA ENDPOINT ============
@api_router.post(\"/seed\")
async def seed_data():
# Clear existing data
await db.menu_items.delete_many({})
await db.specials.delete_many({})
# Seed menu items
menu_items = [
# Appetizers
{\"name\": \"Bruschetta Classica\", \"description\": \"Grilled bread rubbed with garlic, topped with fresh tomatoes, basil, and extra virgin olive oil\", \"price\": 12.00, \"category\": \"appetizers\", \"image_url\": \"https://images.pexels.com/photos/1095550/pexels-photo-1095550.jpeg\"},
{\"name\": \"Carpaccio di Manzo\", \"description\": \"Thinly sliced raw beef with arugula, capers, and shaved Parmigiano-Reggiano\", \"price\": 18.00, \"category\": \"appetizers\", \"image_url\": \"https://images.pexels.com/photos/8753672/pexels-photo-8753672.jpeg\"},
{\"name\": \"Arancini\", \"description\": \"Crispy fried risotto balls filled with mozzarella, served with marinara sauce\", \"price\": 14.00, \"category\": \"appetizers\", \"image_url\": \"https://images.pexels.com/photos/6287537/pexels-photo-6287537.jpeg\"},
{\"name\": \"Calamari Fritti\", \"description\": \"Lightly breaded and fried calamari with lemon aioli\", \"price\": 16.00, \"category\": \"appetizers\", \"image_url\": \"https://images.pexels.com/photos/4553111/pexels-photo-4553111.jpeg\"},
# Mains
{\"name\": \"Spaghetti alla Carbonara\", \"description\": \"Classic Roman pasta with guanciale, egg yolk, Pecorino Romano, and black pepper\", \"price\": 24.00, \"category\": \"mains\", \"image_url\": \"https://images.pexels.com/photos/4518843/pexels-photo-4518843.jpeg\"},
{\"name\": \"Osso Buco\", \"description\": \"Braised veal shanks with gremolata, served with saffron risotto\", \"price\": 42.00, \"category\": \"mains\", \"image_url\": \"https://images.pexels.com/photos/6941010/pexels-photo-6941010.jpeg\"},
{\"name\": \"Risotto ai Funghi Porcini\", \"description\": \"Creamy Arborio rice with wild porcini mushrooms and truffle oil\", \"price\": 28.00, \"category\": \"mains\", \"image_url\": \"https://images.pexels.com/photos/3590401/pexels-photo-3590401.jpeg\"},
{\"name\": \"Pollo alla Parmigiana\", \"description\": \"Breaded chicken cutlet with marinara and melted mozzarella, served with pasta\", \"price\": 26.00, \"category\": \"mains\", \"image_url\": \"https://images.pexels.com/photos/5175621/pexels-photo-5175621.jpeg\"},
{\"name\": \"Branzino al Forno\", \"description\": \"Whole Mediterranean sea bass, oven-roasted with herbs and lemon\", \"price\": 38.00, \"category\": \"mains\", \"image_url\": \"https://images.pexels.com/photos/8963379/pexels-photo-8963379.jpeg\"},
{\"name\": \"Tagliata di Manzo\", \"description\": \"Sliced grilled ribeye with arugula, cherry tomatoes, and aged balsamic\", \"price\": 44.00, \"category\": \"mains\", \"image_url\": \"https://images.pexels.com/photos/3535383/pexels-photo-3535383.jpeg\"},
# Desserts
{\"name\": \"Tiramisu\", \"description\": \"Classic Italian dessert with espresso-soaked ladyfingers and mascarpone cream\", \"price\": 12.00, \"category\": \"desserts\", \"image_url\": \"https://images.pexels.com/photos/6880219/pexels-photo-6880219.jpeg\"},
{\"name\": \"Panna Cotta\", \"description\": \"Silky vanilla cream with seasonal berry compote\", \"price\": 10.00, \"category\": \"desserts\", \"image_url\": \"https://images.pexels.com/photos/8697540/pexels-photo-8697540.jpeg\"},
{\"name\": \"Cannoli Siciliani\", \"description\": \"Crispy pastry shells filled with sweet ricotta and chocolate chips\", \"price\": 11.00, \"category\": \"desserts\", \"image_url\": \"https://images.pexels.com/photos/14471923/pexels-photo-14471923.jpeg\"},
# Drinks
{\"name\": \"Chianti Classico\", \"description\": \"Glass of premium Tuscan red wine\", \"price\": 14.00, \"category\": \"drinks\", \"image_url\": \"https://images.pexels.com/photos/342971/pexels-photo-342971.jpeg\"},
{\"name\": \"Limoncello Spritz\", \"description\": \"Refreshing cocktail with limoncello, prosecco, and soda\", \"price\": 13.00, \"category\": \"drinks\", \"image_url\": \"https://images.pexels.com/photos/338713/pexels-photo-338713.jpeg\"},
{\"name\": \"Espresso\", \"description\": \"Traditional Italian espresso\", \"price\": 4.00, \"category\": \"drinks\", \"image_url\": \"https://images.pexels.com/photos/302899/pexels-photo-302899.jpeg\"},
{\"name\": \"San Pellegrino\", \"description\": \"Sparkling mineral water (750ml)\", \"price\": 6.00, \"category\": \"drinks\", \"image_url\": \"https://images.pexels.com/photos/1337825/pexels-photo-1337825.jpeg\"},
]
for item in menu_items:
menu_item = MenuItem(**item)
await db.menu_items.insert_one(menu_item.model_dump())
# Seed specials
specials = [
{
\"title\": \"Sunday Family Feast\",
\"description\": \"Enjoy our special family-style dinner with antipasti, pasta, and dessert for the whole table\",
\"discount_percent\": 20,
\"valid_from\": \"2024-01-01\",
\"valid_until\": \"2025-12-31\",
\"image_url\": \"https://images.pexels.com/photos/1267320/pexels-photo-1267320.jpeg\"
},
{
\"title\": \"Happy Hour\",
\"description\": \"Half-price appetizers and $10 house wines, Monday-Friday 4-6 PM\",
\"discount_percent\": 50,
\"valid_from\": \"2024-01-01\",
\"valid_until\": \"2025-12-31\",
\"image_url\": \"https://images.pexels.com/photos/696218/pexels-photo-696218.jpeg\"
},
{
\"title\": \"Chef's Tasting Menu\",
\"description\": \"5-course culinary journey featuring seasonal ingredients - $85 per person\",
\"valid_from\": \"2024-01-01\",
\"valid_until\": \"2025-12-31\",
\"image_url\": \"https://images.pexels.com/photos/9888285/pexels-photo-9888285.jpeg\"
}
]
for special in specials:
special_obj = Special(**special)
await db.specials.insert_one(special_obj.model_dump())
return {\"message\": \"Data seeded successfully\", \"menu_items\": len(menu_items), \"specials\": len(specials)}
# Include router
app.include_router(api_router)
app.add_middleware(
CORSMiddleware,
allow_credentials=True,
allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','),
allow_methods=[\"*\"],
allow_headers=[\"*\"],
)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
@app.on_event(\"shutdown\")
async def shutdown_db_client():
client.close()
"
Observation: Overwrite successful: /app/backend/server.py
Subscribe to:
Post Comments (Atom)
የቦ ታክስ
ለዲያስፓራ አባላት በሙሉ እንዲሁም አሁን ኢትዮጵያ ላላችሁ። የአሜሪካ ታክሳችሁን ካላችሁበት ሆናችሁ እንድታሰሩ ነገሮችን ሁሉ አናስተካክላለናል። ያልተሰራ የታክስ ውዝፍ (Back Tax)፣ መስተካከል ያለበት ታክስ (Amendment) እንችላለን። የዚህ አመት ታክስ እና ሌሎችንም እንሰራለን።በViberም ሆነ Whatspp ይደውሉልን። ስልክ ቁጥራችን 619 255 5530 ነው ። YebboTax info@yebbo.com Yebbo.com
Translate
Do you need Ethiopian Power of Attorney where your agent can preform several crucial tasks on your behalf? Such as adoption proceedings, buying movable or immovable properties, paying tax, represent you in governmental and public offices and several others tasks with our your physical presence? If your answer is yes get the Ethiopian Power of Attorney or YEBBO now on sale
Shop Amazon
Call Yebbo
የቦ ኮሚኒኬሽን ኔት ወርክ ፣
ለ25 አመታት በላይ የስራ ልምድ ያካበተው
የእናንተው በእናንተው።
ከምሰጣቸው አገልግሎቶች ውስጥ
የውክልና አገልግሎት መስጠት
የኢትዮጵያ ፓስፖርት አገልግሎት መስጠት
የቢጫ ካርድ የማውጣት አገልግሎት
የታክስ አገልግሎት መስጠት (የትም የኢትዮጵያ ግዛት ይኑሩ)
የጉዞ ወኪል
የትርጉም ስራ አገልግሎት
ለበለጠ መረጃ በስልክ ቁጥር
619-255-5530 ይደውሉ።
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.