Update app.py
Browse files
app.py
CHANGED
|
@@ -1,12 +1,9 @@
|
|
| 1 |
from fastapi import FastAPI, Request
|
| 2 |
-
from fastapi.responses import HTMLResponse, JSONResponse
|
| 3 |
from fastapi.templating import Jinja2Templates
|
| 4 |
-
from fastapi.responses import FileResponse
|
| 5 |
-
from fastapi import HTTPException
|
| 6 |
import requests
|
| 7 |
-
import json
|
| 8 |
import logging
|
| 9 |
-
import os
|
| 10 |
|
| 11 |
# ------------------------------
|
| 12 |
# Setup basic logging
|
|
@@ -18,87 +15,49 @@ logging.basicConfig(
|
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
app = FastAPI()
|
| 21 |
-
|
| 22 |
templates = Jinja2Templates(directory=".")
|
| 23 |
SECURE_API_KEY = os.getenv("SECURE_API_KEY")
|
| 24 |
|
|
|
|
|
|
|
| 25 |
@app.get("/style.css")
|
| 26 |
async def get_style():
|
| 27 |
-
logger.info("Serving style.css")
|
| 28 |
return FileResponse("style.css", media_type="text/css")
|
| 29 |
|
| 30 |
-
BASE_API_URL = "https://script.google.com/macros/s/AKfycbwuRcqUlVroX4qGzBKNpC9mVa6ftGlNL2adJmVBxNW5-VFhj67WRTbFYh-QYXxobCC5ew/exec"
|
| 31 |
-
|
| 32 |
@app.get("/", response_class=HTMLResponse)
|
| 33 |
-
async def index(request: Request):
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
@app.get("/available")
|
| 39 |
-
async def available(name: str, email: str):
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
try:
|
| 42 |
-
|
| 43 |
-
rep_data = rep_resp.json()
|
| 44 |
-
logger.debug(f"Sales rep response: {rep_data}")
|
| 45 |
-
|
| 46 |
-
if "error" in rep_data:
|
| 47 |
-
logger.error(f"Sales rep error: {rep_data['error']}")
|
| 48 |
-
return {
|
| 49 |
-
"message": (
|
| 50 |
-
"We couldn't find an available sales representative at the moment. "
|
| 51 |
-
"Our team will reach out to you shortly."
|
| 52 |
-
)
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
rep_email = rep_data.get("email")
|
| 56 |
-
if not rep_email:
|
| 57 |
-
logger.error("Sales rep email missing from response")
|
| 58 |
-
return {
|
| 59 |
-
"message": (
|
| 60 |
-
"We couldn't assign a sales representative right now. "
|
| 61 |
-
"Our team will contact you shortly."
|
| 62 |
-
)
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
slot_resp = requests.get(f"{BASE_API_URL}?action=getAvailableSlots&salesRepEmail={rep_email}")
|
| 66 |
slots = slot_resp.json()
|
| 67 |
-
logger.debug(f"Slots response: {slots}")
|
| 68 |
-
|
| 69 |
if isinstance(slots, dict) and "error" in slots:
|
| 70 |
-
|
| 71 |
-
return {
|
| 72 |
-
"message": (
|
| 73 |
-
"We currently don't have any available time slots for booking a meeting. "
|
| 74 |
-
"Our team will reach out to you to find a suitable time."
|
| 75 |
-
)
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
if not slots:
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
"message": (
|
| 82 |
-
"There are no open time slots at the moment. "
|
| 83 |
-
"Our team will be in touch to coordinate a meeting."
|
| 84 |
-
)
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
logger.info(f"Returning {len(slots)} available slots for {rep_email}")
|
| 88 |
-
return {
|
| 89 |
-
"slots": slots,
|
| 90 |
-
"repEmail": rep_email
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
except Exception as e:
|
| 94 |
-
logger.exception("Error
|
| 95 |
-
return {
|
| 96 |
-
"message": (
|
| 97 |
-
"Something went wrong while checking availability. "
|
| 98 |
-
"Our team has been notified and will follow up with you."
|
| 99 |
-
),
|
| 100 |
-
"error": str(e)
|
| 101 |
-
}
|
| 102 |
|
| 103 |
@app.post("/book")
|
| 104 |
async def book_meeting(booking: dict):
|
|
@@ -107,67 +66,15 @@ async def book_meeting(booking: dict):
|
|
| 107 |
salesRepEmail = booking.get("salesRepEmail")
|
| 108 |
selectedSlot = booking.get("selectedSlot")
|
| 109 |
|
| 110 |
-
logger.info(f"Booking request received: name={name}, email={email}, slot={selectedSlot}")
|
| 111 |
try:
|
| 112 |
-
response = requests.get(
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
else
|
| 117 |
-
logger.error("Empty response from Apps Script")
|
| 118 |
-
return JSONResponse(status_code=500, content={"error": "Empty response from booking script"})
|
| 119 |
-
logger.debug(f"Create booking response: {response_data}")
|
| 120 |
-
|
| 121 |
if "error" in response_data:
|
| 122 |
-
logger.error(f"Booking error: {response_data['error']}")
|
| 123 |
return JSONResponse(status_code=400, content={"message": response_data["error"]})
|
| 124 |
-
|
| 125 |
-
logger.info("Booking confirmed")
|
| 126 |
return JSONResponse(status_code=200, content={"message": "Booking confirmed!", "data": response_data})
|
| 127 |
-
|
| 128 |
except Exception as e:
|
| 129 |
logger.exception("Error while creating booking")
|
| 130 |
return JSONResponse(status_code=500, content={"message": f"Failed to create booking. {str(e)}"})
|
| 131 |
-
|
| 132 |
-
@app.post("/createBooking")
|
| 133 |
-
async def create_booking(firstName: str, email: str, salesRepEmail: str, selectedSlot: str):
|
| 134 |
-
logger.info(f"createBooking called for {email}")
|
| 135 |
-
try:
|
| 136 |
-
response = requests.post(f"{BASE_API_URL}?action=createBooking", data={
|
| 137 |
-
'customerName': firstName,
|
| 138 |
-
'customerEmail': email,
|
| 139 |
-
'salesRepEmail': salesRepEmail,
|
| 140 |
-
'selectedSlot': selectedSlot
|
| 141 |
-
})
|
| 142 |
-
logger.debug(f"createBooking response: {response.text}")
|
| 143 |
-
return response.text
|
| 144 |
-
except Exception as e:
|
| 145 |
-
logger.exception("Error in createBooking")
|
| 146 |
-
return {"error": f"Failed to create booking: {str(e)}"}
|
| 147 |
-
|
| 148 |
-
@app.post("/rescheduleBooking")
|
| 149 |
-
async def reschedule_booking(bookingId: int, newSlot: str):
|
| 150 |
-
logger.info(f"Rescheduling bookingId={bookingId} to newSlot={newSlot}")
|
| 151 |
-
try:
|
| 152 |
-
response = requests.post(f"{BASE_API_URL}?action=rescheduleBooking", data={
|
| 153 |
-
'bookingId': bookingId,
|
| 154 |
-
'newSlot': newSlot
|
| 155 |
-
})
|
| 156 |
-
logger.debug(f"Reschedule response: {response.text}")
|
| 157 |
-
return response.text
|
| 158 |
-
except Exception as e:
|
| 159 |
-
logger.exception("Error in rescheduleBooking")
|
| 160 |
-
return {"error": f"Failed to reschedule booking: {str(e)}"}
|
| 161 |
-
|
| 162 |
-
@app.post("/cancelBooking")
|
| 163 |
-
async def cancel_booking(bookingId: int):
|
| 164 |
-
logger.info(f"Cancelling bookingId={bookingId}")
|
| 165 |
-
try:
|
| 166 |
-
response = requests.post(f"{BASE_API_URL}?action=cancelBooking", data={
|
| 167 |
-
'bookingId': bookingId
|
| 168 |
-
})
|
| 169 |
-
logger.debug(f"Cancel response: {response.text}")
|
| 170 |
-
return response.text
|
| 171 |
-
except Exception as e:
|
| 172 |
-
logger.exception("Error in cancelBooking")
|
| 173 |
-
return {"error": f"Failed to cancel booking: {str(e)}"}
|
|
|
|
| 1 |
from fastapi import FastAPI, Request
|
| 2 |
+
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
|
| 3 |
from fastapi.templating import Jinja2Templates
|
|
|
|
|
|
|
| 4 |
import requests
|
|
|
|
| 5 |
import logging
|
| 6 |
+
import os
|
| 7 |
|
| 8 |
# ------------------------------
|
| 9 |
# Setup basic logging
|
|
|
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
app = FastAPI()
|
|
|
|
| 18 |
templates = Jinja2Templates(directory=".")
|
| 19 |
SECURE_API_KEY = os.getenv("SECURE_API_KEY")
|
| 20 |
|
| 21 |
+
BASE_API_URL = "https://script.google.com/macros/s/AKfycbwuRcqUlVroX4qGzBKNpC9mVa6ftGlNL2adJmVBxNW5-VFhj67WRTbFYh-QYXxobCC5ew/exec"
|
| 22 |
+
|
| 23 |
@app.get("/style.css")
|
| 24 |
async def get_style():
|
|
|
|
| 25 |
return FileResponse("style.css", media_type="text/css")
|
| 26 |
|
|
|
|
|
|
|
| 27 |
@app.get("/", response_class=HTMLResponse)
|
| 28 |
+
async def index(request: Request, ln: str = "", le: str = "", oe: str = ""):
|
| 29 |
+
"""
|
| 30 |
+
Pass query parameters to the front-end to pre-fill the form.
|
| 31 |
+
ln = lead name
|
| 32 |
+
le = lead email
|
| 33 |
+
oe = owner / sales rep email
|
| 34 |
+
"""
|
| 35 |
+
return templates.TemplateResponse(
|
| 36 |
+
"index.html",
|
| 37 |
+
{
|
| 38 |
+
"request": request,
|
| 39 |
+
"lead_name": ln,
|
| 40 |
+
"lead_email": le,
|
| 41 |
+
"sales_rep_email": oe
|
| 42 |
+
}
|
| 43 |
+
)
|
| 44 |
|
| 45 |
@app.get("/available")
|
| 46 |
+
async def available(name: str, email: str, salesRepEmail: str):
|
| 47 |
+
"""
|
| 48 |
+
Return available slots for a given salesRepEmail
|
| 49 |
+
"""
|
| 50 |
try:
|
| 51 |
+
slot_resp = requests.get(f"{BASE_API_URL}?action=getAvailableSlots&salesRepEmail={salesRepEmail}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
slots = slot_resp.json()
|
|
|
|
|
|
|
| 53 |
if isinstance(slots, dict) and "error" in slots:
|
| 54 |
+
return {"error": slots["error"]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
if not slots:
|
| 56 |
+
return {"slots": [], "repEmail": salesRepEmail}
|
| 57 |
+
return {"slots": slots, "repEmail": salesRepEmail}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
except Exception as e:
|
| 59 |
+
logger.exception("Error fetching slots")
|
| 60 |
+
return {"error": str(e)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
@app.post("/book")
|
| 63 |
async def book_meeting(booking: dict):
|
|
|
|
| 66 |
salesRepEmail = booking.get("salesRepEmail")
|
| 67 |
selectedSlot = booking.get("selectedSlot")
|
| 68 |
|
|
|
|
| 69 |
try:
|
| 70 |
+
response = requests.get(
|
| 71 |
+
f"{BASE_API_URL}?action=createBooking&customerName={name}&customerEmail={email}"
|
| 72 |
+
f"&salesRepEmail={salesRepEmail}&selectedSlot={selectedSlot}"
|
| 73 |
+
)
|
| 74 |
+
response_data = response.json() if response.content else {}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
if "error" in response_data:
|
|
|
|
| 76 |
return JSONResponse(status_code=400, content={"message": response_data["error"]})
|
|
|
|
|
|
|
| 77 |
return JSONResponse(status_code=200, content={"message": "Booking confirmed!", "data": response_data})
|
|
|
|
| 78 |
except Exception as e:
|
| 79 |
logger.exception("Error while creating booking")
|
| 80 |
return JSONResponse(status_code=500, content={"message": f"Failed to create booking. {str(e)}"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|