Spaces:
Sleeping
Sleeping
Commit ·
830a2d0
1
Parent(s): ab2d06d
appointments
Browse files- .env +5 -0
- app/app.py +15 -0
- app/controllers/appointment_controller.py +13 -0
- app/models/appointment_model.py +41 -0
- app/repositories/appointment_repository.py +26 -0
- app/services/appointment_service.py +34 -0
- app/sql.py +55 -0
- app/views/appointment_view.py +5 -0
.env
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MONGO_URI=mongodb+srv://appliedcommerce:xaqtoj-gikdaw-6giQdy@cluster0.qpc5d.mongodb.net/test??ssl=true&ssl_cert_reqs=CERT_NONE
|
| 2 |
+
|
| 3 |
+
CACHE_URI=redis-13158.c212.ap-south-1-1.ec2.redns.redis-cloud.com:13158
|
| 4 |
+
|
| 5 |
+
CACHE_K=Mi5bLjJHt5KFlGjnx04K269Dt2w7hdup
|
app/app.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from app.sql import connect_to_database, disconnect_from_database
|
| 3 |
+
from app.views.appointment_view import router as appointment_router
|
| 4 |
+
|
| 5 |
+
app = FastAPI(title="Bookings / Appointment Management")
|
| 6 |
+
|
| 7 |
+
@app.on_event("startup")
|
| 8 |
+
async def startup():
|
| 9 |
+
await connect_to_database()
|
| 10 |
+
|
| 11 |
+
@app.on_event("shutdown")
|
| 12 |
+
async def shutdown():
|
| 13 |
+
await disconnect_from_database()
|
| 14 |
+
|
| 15 |
+
app.include_router(appointment_router)
|
app/controllers/appointment_controller.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from app.services.appointment_service import create_new_appointment, reschedule_appointment
|
| 3 |
+
from app.models.appointment_model import Appointment
|
| 4 |
+
|
| 5 |
+
router = APIRouter()
|
| 6 |
+
|
| 7 |
+
@router.post("/appointments/")
|
| 8 |
+
async def create_appointment(appointment: Appointment):
|
| 9 |
+
return await create_new_appointment(appointment)
|
| 10 |
+
|
| 11 |
+
@router.put("/appointments/reschedule/{appointment_id}")
|
| 12 |
+
async def reschedule(appointment_id: str, new_date: str, new_time: str):
|
| 13 |
+
return await reschedule_appointment(appointment_id, new_date, new_time)
|
app/models/appointment_model.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from datetime import date, time, datetime
|
| 3 |
+
from typing import List
|
| 4 |
+
from enum import Enum
|
| 5 |
+
import sqlalchemy
|
| 6 |
+
from app.database import metadata
|
| 7 |
+
|
| 8 |
+
class AppointmentStatus(str, Enum):
|
| 9 |
+
confirmed = "confirmed"
|
| 10 |
+
rescheduled = "rescheduled"
|
| 11 |
+
canceled = "canceled"
|
| 12 |
+
|
| 13 |
+
class Service(BaseModel):
|
| 14 |
+
description: str
|
| 15 |
+
duration: int
|
| 16 |
+
price: float
|
| 17 |
+
|
| 18 |
+
class Appointment(BaseModel):
|
| 19 |
+
appointment_id: str
|
| 20 |
+
merchant_id: str
|
| 21 |
+
customer_id: str
|
| 22 |
+
appointment_date: date
|
| 23 |
+
appointment_time: time
|
| 24 |
+
status: AppointmentStatus
|
| 25 |
+
services: List[Service]
|
| 26 |
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
| 27 |
+
modified_at: datetime = Field(default_factory=datetime.utcnow)
|
| 28 |
+
|
| 29 |
+
appointment_table = sqlalchemy.Table(
|
| 30 |
+
"appointments",
|
| 31 |
+
metadata,
|
| 32 |
+
sqlalchemy.Column("appointment_id", sqlalchemy.String, primary_key=True),
|
| 33 |
+
sqlalchemy.Column("merchant_id", sqlalchemy.String),
|
| 34 |
+
sqlalchemy.Column("customer_id", sqlalchemy.String),
|
| 35 |
+
sqlalchemy.Column("appointment_date", sqlalchemy.Date, nullable=False),
|
| 36 |
+
sqlalchemy.Column("appointment_time", sqlalchemy.Time, nullable=False),
|
| 37 |
+
sqlalchemy.Column("status", sqlalchemy.String(20)),
|
| 38 |
+
sqlalchemy.Column("services", sqlalchemy.JSON),
|
| 39 |
+
sqlalchemy.Column("created_at", sqlalchemy.DateTime(timezone=True), default=datetime.utcnow),
|
| 40 |
+
sqlalchemy.Column("modified_at", sqlalchemy.DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow),
|
| 41 |
+
)
|
app/repositories/appointment_repository.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.models.appointment_model import appointment_table
|
| 2 |
+
from app.database import database
|
| 3 |
+
|
| 4 |
+
async def create_appointment(appointment_data: dict):
|
| 5 |
+
query = appointment_table.insert().values(**appointment_data)
|
| 6 |
+
return await database.execute(query)
|
| 7 |
+
|
| 8 |
+
async def update_appointment(appointment_id: str, update_data: dict):
|
| 9 |
+
query = (
|
| 10 |
+
appointment_table.update()
|
| 11 |
+
.where(appointment_table.c.appointment_id == appointment_id)
|
| 12 |
+
.values(**update_data)
|
| 13 |
+
)
|
| 14 |
+
return await database.execute(query)
|
| 15 |
+
|
| 16 |
+
async def get_appointment_by_id(appointment_id: str):
|
| 17 |
+
query = appointment_table.select().where(appointment_table.c.appointment_id == appointment_id)
|
| 18 |
+
return await database.fetch_one(query)
|
| 19 |
+
|
| 20 |
+
async def get_appointments_by_customer(customer_id: str):
|
| 21 |
+
query = appointment_table.select().where(appointment_table.c.customer_id == customer_id)
|
| 22 |
+
return await database.fetch_all(query)
|
| 23 |
+
|
| 24 |
+
async def get_appointments_by_merchant(merchant_id: str):
|
| 25 |
+
query = appointment_table.select().where(appointment_table.c.merchant_id == merchant_id)
|
| 26 |
+
return await database.fetch_all(query)
|
app/services/appointment_service.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime, timezone
|
| 2 |
+
from fastapi import HTTPException
|
| 3 |
+
from app.repositories.appointment_repository import (
|
| 4 |
+
create_appointment,
|
| 5 |
+
update_appointment,
|
| 6 |
+
get_appointment_by_id,
|
| 7 |
+
)
|
| 8 |
+
from app.models.appointment_model import Appointment, AppointmentStatus
|
| 9 |
+
|
| 10 |
+
async def create_new_appointment(appointment: Appointment):
|
| 11 |
+
now = datetime.now(timezone.utc)
|
| 12 |
+
appointment_data = appointment.dict()
|
| 13 |
+
appointment_data["status"] = AppointmentStatus.confirmed
|
| 14 |
+
appointment_data["created_at"] = now
|
| 15 |
+
appointment_data["modified_at"] = now
|
| 16 |
+
await create_appointment(appointment_data)
|
| 17 |
+
return {"message": "Appointment created successfully"}
|
| 18 |
+
|
| 19 |
+
async def reschedule_appointment(appointment_id: str, new_date: datetime, new_time: datetime):
|
| 20 |
+
existing_appointment = await get_appointment_by_id(appointment_id)
|
| 21 |
+
if not existing_appointment:
|
| 22 |
+
raise HTTPException(status_code=404, detail="Appointment not found")
|
| 23 |
+
|
| 24 |
+
if existing_appointment["status"] == AppointmentStatus.canceled:
|
| 25 |
+
raise HTTPException(status_code=400, detail="Cannot reschedule a canceled appointment")
|
| 26 |
+
|
| 27 |
+
update_data = {
|
| 28 |
+
"appointment_date": new_date.date(),
|
| 29 |
+
"appointment_time": new_time.time(),
|
| 30 |
+
"status": AppointmentStatus.rescheduled,
|
| 31 |
+
"modified_at": datetime.now(timezone.utc),
|
| 32 |
+
}
|
| 33 |
+
await update_appointment(appointment_id, update_data)
|
| 34 |
+
return {"message": "Appointment rescheduled successfully"}
|
app/sql.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import databases
|
| 5 |
+
import sqlalchemy
|
| 6 |
+
|
| 7 |
+
# Load environment variables from .env file
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
# Configure logging
|
| 11 |
+
logging.basicConfig(
|
| 12 |
+
level=logging.INFO,
|
| 13 |
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
| 14 |
+
)
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
# Load the DATABASE_URL from environment variables
|
| 18 |
+
DATABASE_URL = os.getenv("DATABASE_URI")
|
| 19 |
+
|
| 20 |
+
if not DATABASE_URL:
|
| 21 |
+
logger.error("DATABASE_URI not found in environment variables.")
|
| 22 |
+
raise ValueError("DATABASE_URI not found in environment variables. Ensure it is set in the .env file.")
|
| 23 |
+
|
| 24 |
+
# Initialize the database connection and metadata
|
| 25 |
+
try:
|
| 26 |
+
database = databases.Database(DATABASE_URL)
|
| 27 |
+
metadata = sqlalchemy.MetaData()
|
| 28 |
+
logger.info("Database connection initialized successfully.")
|
| 29 |
+
except Exception as e:
|
| 30 |
+
logger.error("Failed to initialize database connection: %s", e)
|
| 31 |
+
raise
|
| 32 |
+
|
| 33 |
+
# Helper function to initialize database
|
| 34 |
+
async def connect_to_database():
|
| 35 |
+
"""
|
| 36 |
+
Connects to the database when the application starts.
|
| 37 |
+
"""
|
| 38 |
+
try:
|
| 39 |
+
await database.connect()
|
| 40 |
+
logger.info("Successfully connected to the database.")
|
| 41 |
+
except Exception as e:
|
| 42 |
+
logger.error("Error connecting to the database: %s", e)
|
| 43 |
+
raise
|
| 44 |
+
|
| 45 |
+
# Helper function to disconnect database
|
| 46 |
+
async def disconnect_from_database():
|
| 47 |
+
"""
|
| 48 |
+
Disconnects from the database when the application shuts down.
|
| 49 |
+
"""
|
| 50 |
+
try:
|
| 51 |
+
await database.disconnect()
|
| 52 |
+
logger.info("Successfully disconnected from the database.")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logger.error("Error disconnecting from the database: %s", e)
|
| 55 |
+
raise
|
app/views/appointment_view.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from app.controllers.appointment_controller import router as appointment_controller
|
| 3 |
+
|
| 4 |
+
router = APIRouter()
|
| 5 |
+
router.include_router(appointment_controller, prefix="/appointments", tags=["Appointments"])
|