MukeshKapoor25 commited on
Commit
2faa7f5
·
1 Parent(s): 97d8bb5

cart in redis

Browse files
app/__pycache__/app.cpython-311.pyc CHANGED
Binary files a/app/__pycache__/app.cpython-311.pyc and b/app/__pycache__/app.cpython-311.pyc differ
 
app/app.py CHANGED
@@ -2,7 +2,14 @@ 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
  from app.views.cart_view import router as cart_router
 
5
 
 
 
 
 
 
 
6
 
7
  app = FastAPI(
8
  title="Bookings / Appointment Management",
@@ -15,18 +22,22 @@ app = FastAPI(
15
  async def startup():
16
  try:
17
  await connect_to_database()
18
- print("Database connected successfully.")
 
 
 
 
19
  except Exception as e:
20
- print(f"Error connecting to the database: {e}")
21
 
22
  # Shutdown event to close database connection
23
  @app.on_event("shutdown")
24
  async def shutdown():
25
  try:
26
  await disconnect_from_database()
27
- print("Database disconnected successfully.")
28
  except Exception as e:
29
- print(f"Error disconnecting from the database: {e}")
30
 
31
  # Include appointment router
32
  app.include_router(
 
2
  from app.sql import connect_to_database, disconnect_from_database
3
  from app.views.appointment_view import router as appointment_router
4
  from app.views.cart_view import router as cart_router
5
+ import logging
6
 
7
+ # Configure logging
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format="%(asctime)s - %(levelname)s - %(message)s"
11
+ )
12
+ logger = logging.getLogger(__name__)
13
 
14
  app = FastAPI(
15
  title="Bookings / Appointment Management",
 
22
  async def startup():
23
  try:
24
  await connect_to_database()
25
+ logger.info("Database connected successfully.")
26
+
27
+ # Log all registered routes
28
+ for route in app.router.routes:
29
+ logger.info(f"Registered route: {route.path}")
30
  except Exception as e:
31
+ logger.error(f"Error connecting to the database: {e}")
32
 
33
  # Shutdown event to close database connection
34
  @app.on_event("shutdown")
35
  async def shutdown():
36
  try:
37
  await disconnect_from_database()
38
+ logger.info("Database disconnected successfully.")
39
  except Exception as e:
40
+ logger.error(f"Error disconnecting from the database: {e}")
41
 
42
  # Include appointment router
43
  app.include_router(
app/controllers/__pycache__/cart_controller.cpython-311.pyc CHANGED
Binary files a/app/controllers/__pycache__/cart_controller.cpython-311.pyc and b/app/controllers/__pycache__/cart_controller.cpython-311.pyc differ
 
app/controllers/cart_controller.py CHANGED
@@ -8,23 +8,26 @@ from app.services.cart_service import (
8
 
9
  router = APIRouter()
10
 
11
- @router.post("/cart/appointment/")
12
  async def add_to_cart(appointment_cart: AppointmentCart):
13
- """
14
- API endpoint to add an appointment draft to the cart.
15
- """
16
- return await add_appointment_to_cart(appointment_cart)
 
 
 
17
 
18
- @router.get("/cart/appointment/{user_id}")
19
  async def get_from_cart(user_id: str):
20
- """
21
- API endpoint to retrieve an appointment draft from the cart.
22
- """
23
- return await retrieve_appointment_from_cart(user_id)
24
 
25
- @router.delete("/cart/appointment/{user_id}")
26
  async def delete_from_cart(user_id: str):
27
- """
28
- API endpoint to remove an appointment draft from the cart.
29
- """
30
- return await remove_appointment_from_cart(user_id)
 
8
 
9
  router = APIRouter()
10
 
11
+ @router.post("/appointment")
12
  async def add_to_cart(appointment_cart: AppointmentCart):
13
+ try:
14
+ return await add_appointment_to_cart(
15
+ user_id=appointment_cart.user_id,
16
+ appointment_data=appointment_cart.dict()
17
+ )
18
+ except Exception as e:
19
+ raise HTTPException(status_code=500, detail=f"Failed to add to cart: {e}")
20
 
21
+ @router.get("/appointment/{user_id}")
22
  async def get_from_cart(user_id: str):
23
+ try:
24
+ return await retrieve_appointment_from_cart(user_id)
25
+ except Exception as e:
26
+ raise HTTPException(status_code=500, detail=f"Failed to retrieve from cart: {e}")
27
 
28
+ @router.delete("/appointment/{user_id}")
29
  async def delete_from_cart(user_id: str):
30
+ try:
31
+ return await remove_appointment_from_cart(user_id)
32
+ except Exception as e:
33
+ raise HTTPException(status_code=500, detail=f"Failed to delete from cart: {e}")
app/models/__pycache__/cart_model.cpython-311.pyc CHANGED
Binary files a/app/models/__pycache__/cart_model.cpython-311.pyc and b/app/models/__pycache__/cart_model.cpython-311.pyc differ
 
app/models/cart_model.py CHANGED
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel
2
- from typing import List
3
  from datetime import timedelta
4
 
5
  class Service(BaseModel):
@@ -15,4 +15,13 @@ class AppointmentCart(BaseModel):
15
  appointment_date: str
16
  appointment_time: str
17
  services: List[Service]
18
- ttl: timedelta = timedelta(minutes=30) # Default TTL for drafts
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field, validator
2
+ from typing import List, Union
3
  from datetime import timedelta
4
 
5
  class Service(BaseModel):
 
15
  appointment_date: str
16
  appointment_time: str
17
  services: List[Service]
18
+ ttl: Union[int, str] = Field(default="forever") # Allow integer or "forever"
19
+
20
+ @validator("ttl")
21
+ def validate_ttl(cls, value):
22
+ if isinstance(value, int):
23
+ if value <= 0:
24
+ raise ValueError("TTL must be a positive integer.")
25
+ elif value != "forever":
26
+ raise ValueError("TTL must be a positive integer or 'forever'.")
27
+ return value
app/repositories/__pycache__/appointment_repository.cpython-311.pyc CHANGED
Binary files a/app/repositories/__pycache__/appointment_repository.cpython-311.pyc and b/app/repositories/__pycache__/appointment_repository.cpython-311.pyc differ
 
app/repositories/__pycache__/cart_repository.cpython-311.pyc CHANGED
Binary files a/app/repositories/__pycache__/cart_repository.cpython-311.pyc and b/app/repositories/__pycache__/cart_repository.cpython-311.pyc differ
 
app/repositories/appointment_repository.py CHANGED
@@ -1,26 +1,170 @@
1
  from app.models.appointment_model import appointment_table
2
  from app.sql 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)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from app.models.appointment_model import appointment_table
2
  from app.sql import database
3
+ from app.utils.appointment_utils import (
4
+ serialize_appointment,
5
+ validate_query_result,
6
+ validate_existing_appointment,
7
+ build_filter_query,
8
+ )
9
+ from fastapi import HTTPException
10
+ import logging
11
+
12
+ # Configure logging
13
+ logging.basicConfig(level=logging.INFO)
14
+ logger = logging.getLogger(__name__)
15
 
16
  async def create_appointment(appointment_data: dict):
17
+ """
18
+ Creates a new appointment in the database.
19
+
20
+ Args:
21
+ appointment_data (dict): The appointment details.
22
+
23
+ Returns:
24
+ str: The ID of the created appointment.
25
+ """
26
+ try:
27
+ query = appointment_table.insert().values(**appointment_data)
28
+ appointment_id = await database.execute(query)
29
+ logger.info(f"Appointment created successfully: {appointment_id}")
30
+ return {"message": "Appointment created successfully", "appointment_id": appointment_id}
31
+ except Exception as e:
32
+ logger.error(f"Failed to create appointment: {e}")
33
+ raise HTTPException(status_code=500, detail="Failed to create appointment.")
34
 
35
  async def update_appointment(appointment_id: str, update_data: dict):
36
+ """
37
+ Updates an existing appointment in the database.
38
+
39
+ Args:
40
+ appointment_id (str): The ID of the appointment to update.
41
+ update_data (dict): The updated fields.
42
+
43
+ Returns:
44
+ dict: Confirmation message.
45
+ """
46
+ try:
47
+ # Validate existing appointment
48
+ existing_appointment = await get_appointment_by_id(appointment_id)
49
+ validate_existing_appointment(existing_appointment)
50
+
51
+ query = (
52
+ appointment_table.update()
53
+ .where(appointment_table.c.appointment_id == appointment_id)
54
+ .values(**update_data)
55
+ )
56
+ result = await database.execute(query)
57
+
58
+ if result == 0:
59
+ logger.warning(f"No rows updated for appointment ID: {appointment_id}")
60
+ raise HTTPException(status_code=404, detail="Appointment not found.")
61
+
62
+ logger.info(f"Appointment updated successfully: {appointment_id}")
63
+ return {"message": "Appointment updated successfully"}
64
+ except HTTPException as http_exc:
65
+ raise http_exc
66
+ except Exception as e:
67
+ logger.error(f"Failed to update appointment {appointment_id}: {e}")
68
+ raise HTTPException(status_code=500, detail="Failed to update appointment.")
69
 
70
  async def get_appointment_by_id(appointment_id: str):
71
+ """
72
+ Fetches an appointment by its ID.
73
+
74
+ Args:
75
+ appointment_id (str): The ID of the appointment.
76
+
77
+ Returns:
78
+ dict: The serialized appointment details.
79
+ """
80
+ try:
81
+ query = appointment_table.select().where(appointment_table.c.appointment_id == appointment_id)
82
+ result = await database.fetch_one(query)
83
+
84
+ validate_query_result(result, "Appointment not found.")
85
+ logger.info(f"Retrieved appointment: {appointment_id}")
86
+ return serialize_appointment(result)
87
+ except HTTPException as http_exc:
88
+ raise http_exc
89
+ except Exception as e:
90
+ logger.error(f"Failed to fetch appointment {appointment_id}: {e}")
91
+ raise HTTPException(status_code=500, detail="Failed to fetch appointment.")
92
 
93
  async def get_appointments_by_customer(customer_id: str):
94
+ """
95
+ Fetches all appointments for a specific customer.
96
+
97
+ Args:
98
+ customer_id (str): The ID of the customer.
99
+
100
+ Returns:
101
+ list: List of serialized appointments.
102
+ """
103
+ try:
104
+ query = appointment_table.select().where(appointment_table.c.customer_id == customer_id)
105
+ results = await database.fetch_all(query)
106
+
107
+ if not results:
108
+ logger.warning(f"No appointments found for customer ID: {customer_id}")
109
+ raise HTTPException(status_code=404, detail="No appointments found for this customer ID.")
110
+
111
+ logger.info(f"Retrieved appointments for customer ID: {customer_id}")
112
+ return [serialize_appointment(result) for result in results]
113
+ except HTTPException as http_exc:
114
+ raise http_exc
115
+ except Exception as e:
116
+ logger.error(f"Failed to fetch appointments for customer {customer_id}: {e}")
117
+ raise HTTPException(status_code=500, detail="Failed to fetch appointments.")
118
 
119
  async def get_appointments_by_merchant(merchant_id: str):
120
+ """
121
+ Fetches all appointments for a specific merchant.
122
+
123
+ Args:
124
+ merchant_id (str): The ID of the merchant.
125
+
126
+ Returns:
127
+ list: List of serialized appointments.
128
+ """
129
+ try:
130
+ query = appointment_table.select().where(appointment_table.c.merchant_id == merchant_id)
131
+ results = await database.fetch_all(query)
132
+
133
+ if not results:
134
+ logger.warning(f"No appointments found for merchant ID: {merchant_id}")
135
+ raise HTTPException(status_code=404, detail="No appointments found for this merchant ID.")
136
+
137
+ logger.info(f"Retrieved appointments for merchant ID: {merchant_id}")
138
+ return [serialize_appointment(result) for result in results]
139
+ except HTTPException as http_exc:
140
+ raise http_exc
141
+ except Exception as e:
142
+ logger.error(f"Failed to fetch appointments for merchant {merchant_id}: {e}")
143
+ raise HTTPException(status_code=500, detail="Failed to fetch appointments.")
144
+
145
+ async def get_appointments_with_filters(filters: dict):
146
+ """
147
+ Fetches appointments based on dynamic filters.
148
+
149
+ Args:
150
+ filters (dict): The filter conditions.
151
+
152
+ Returns:
153
+ list: List of serialized appointments.
154
+ """
155
+ try:
156
+ filter_query = build_filter_query(filters)
157
+ query = f"SELECT * FROM appointments WHERE {filter_query}" if filter_query else "SELECT * FROM appointments"
158
+ results = await database.fetch_all(query, filters)
159
+
160
+ if not results:
161
+ logger.warning("No appointments found matching the filters.")
162
+ raise HTTPException(status_code=404, detail="No appointments found.")
163
+
164
+ logger.info("Retrieved appointments with filters.")
165
+ return [serialize_appointment(result) for result in results]
166
+ except HTTPException as http_exc:
167
+ raise http_exc
168
+ except Exception as e:
169
+ logger.error(f"Failed to fetch appointments with filters: {e}")
170
+ raise HTTPException(status_code=500, detail="Failed to fetch appointments.")
app/repositories/cart_repository.py CHANGED
@@ -6,12 +6,35 @@ from app.utils.redis_utils import get_redis_client
6
  # Get the Redis client
7
  redis_client = get_redis_client()
8
 
9
- async def save_cart_draft(cart_key: str, cart_data: dict, ttl: timedelta):
10
  """
11
  Save appointment cart as a draft in Redis.
 
 
 
 
 
 
 
 
12
  """
13
  serialized_data = json.dumps(cart_data)
14
- await redis_client.setex(cart_key, ttl, serialized_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  async def get_cart_draft(cart_key: str):
17
  """
 
6
  # Get the Redis client
7
  redis_client = get_redis_client()
8
 
9
+ async def save_cart_draft(cart_key: str, cart_data: dict, ttl: timedelta = None):
10
  """
11
  Save appointment cart as a draft in Redis.
12
+
13
+ Args:
14
+ cart_key (str): The Redis key for the cart.
15
+ cart_data (dict): The cart data to be saved.
16
+ ttl (timedelta, optional): The time-to-live for the cart key. Defaults to None (forever).
17
+
18
+ Raises:
19
+ ValueError: If ttl is provided and not a positive duration.
20
  """
21
  serialized_data = json.dumps(cart_data)
22
+
23
+ try:
24
+ if ttl is None:
25
+ # Save the key without expiration (forever)
26
+ await redis_client.set(cart_key, serialized_data)
27
+ else:
28
+ # Validate TTL
29
+ if not isinstance(ttl, timedelta):
30
+ raise ValueError("TTL must be a timedelta object.")
31
+ if ttl.total_seconds() <= 0:
32
+ raise ValueError("TTL must be a positive duration.")
33
+
34
+ # Save the key with an expiration
35
+ await redis_client.setex(cart_key, int(ttl.total_seconds()), serialized_data)
36
+ except Exception as e:
37
+ raise Exception(f"Failed to save cart draft in Redis: {e}")
38
 
39
  async def get_cart_draft(cart_key: str):
40
  """
app/services/__pycache__/appointment_service.cpython-311.pyc CHANGED
Binary files a/app/services/__pycache__/appointment_service.cpython-311.pyc and b/app/services/__pycache__/appointment_service.cpython-311.pyc differ
 
app/services/__pycache__/cart_service.cpython-311.pyc CHANGED
Binary files a/app/services/__pycache__/cart_service.cpython-311.pyc and b/app/services/__pycache__/cart_service.cpython-311.pyc differ
 
app/services/appointment_service.py CHANGED
@@ -1,5 +1,6 @@
1
  from datetime import datetime, timezone
2
  from fastapi import HTTPException
 
3
  from app.repositories.appointment_repository import (
4
  create_appointment,
5
  update_appointment,
@@ -7,28 +8,93 @@ from app.repositories.appointment_repository import (
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"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from datetime import datetime, timezone
2
  from fastapi import HTTPException
3
+ from logging import getLogger
4
  from app.repositories.appointment_repository import (
5
  create_appointment,
6
  update_appointment,
 
8
  )
9
  from app.models.appointment_model import Appointment, AppointmentStatus
10
 
11
+ # Initialize logger
12
+ logger = getLogger(__name__)
13
+
14
  async def create_new_appointment(appointment: Appointment):
15
+ """
16
+ Creates a new appointment in the database.
17
+
18
+ Args:
19
+ appointment (Appointment): The appointment details as an Appointment object.
20
+
21
+ Returns:
22
+ dict: Confirmation message.
23
+ """
24
+ try:
25
+ # Get the current timestamp
26
+ now = datetime.now(timezone.utc)
27
+ logger.info("Creating new appointment...")
28
+
29
+ # Prepare appointment data
30
+ appointment_data = appointment.dict()
31
+ appointment_data["status"] = AppointmentStatus.confirmed # Default status
32
+ appointment_data["created_at"] = now
33
+ appointment_data["modified_at"] = now
34
+
35
+ # Save the appointment to the database
36
+ await create_appointment(appointment_data)
37
+ logger.info(f"Appointment created successfully: {appointment_data['appointment_id']}")
38
+ return {"message": "Appointment created successfully"}
39
+ except Exception as e:
40
+ logger.error(f"Error while creating appointment: {e}")
41
+ raise HTTPException(status_code=500, detail=f"Failed to create appointment: {e}")
42
+
43
+
44
+ async def reschedule_appointment(appointment_id: str, new_date: str, new_time: str):
45
+ """
46
+ Reschedules an existing appointment.
47
+
48
+ Args:
49
+ appointment_id (str): The ID of the appointment to reschedule.
50
+ new_date (str): The new date for the appointment in string format (YYYY-MM-DD).
51
+ new_time (str): The new time for the appointment in string format (HH:MM:SS).
52
+
53
+ Returns:
54
+ dict: Confirmation message.
55
+ """
56
+ try:
57
+ logger.info(f"Rescheduling appointment with ID: {appointment_id}")
58
+
59
+ # Validate and parse new_date and new_time into datetime objects
60
+ new_date_parsed = datetime.strptime(new_date, "%Y-%m-%d")
61
+ new_time_parsed = datetime.strptime(new_time, "%H:%M:%S").time()
62
+
63
+ # Fetch the existing appointment
64
+ existing_appointment = await get_appointment_by_id(appointment_id)
65
+ if not existing_appointment:
66
+ logger.warning(f"Appointment not found: {appointment_id}")
67
+ raise HTTPException(status_code=404, detail="Appointment not found")
68
+
69
+ # Check if the appointment is canceled
70
+ if existing_appointment["status"] == AppointmentStatus.canceled:
71
+ logger.warning(f"Attempted to reschedule a canceled appointment: {appointment_id}")
72
+ raise HTTPException(status_code=400, detail="Cannot reschedule a canceled appointment")
73
+
74
+ # Validate reschedule time (e.g., cannot reschedule to a past time)
75
+ now = datetime.now(timezone.utc)
76
+ reschedule_datetime = datetime.combine(new_date_parsed.date(), new_time_parsed).replace(tzinfo=timezone.utc)
77
+ if reschedule_datetime <= now:
78
+ logger.warning(f"Attempted to reschedule appointment to a past time: {reschedule_datetime}")
79
+ raise HTTPException(status_code=400, detail="Cannot reschedule to a past time")
80
+
81
+ # Update the appointment
82
+ update_data = {
83
+ "appointment_date": new_date_parsed.date(),
84
+ "appointment_time": new_time_parsed,
85
+ "status": AppointmentStatus.rescheduled,
86
+ "modified_at": datetime.now(timezone.utc),
87
+ }
88
+ await update_appointment(appointment_id, update_data)
89
+ logger.info(f"Appointment rescheduled successfully: {appointment_id}")
90
+
91
+ return {"message": "Appointment rescheduled successfully"}
92
+ except ValueError as ve:
93
+ logger.error(f"Invalid date or time format for appointment {appointment_id}: {ve}")
94
+ raise HTTPException(status_code=400, detail=f"Invalid date or time format: {ve}")
95
+ except HTTPException as http_exc:
96
+ logger.error(f"HTTP exception occurred while rescheduling appointment {appointment_id}: {http_exc.detail}")
97
+ raise http_exc
98
+ except Exception as e:
99
+ logger.error(f"Unexpected error while rescheduling appointment {appointment_id}: {e}")
100
+ raise HTTPException(status_code=500, detail=f"Failed to reschedule appointment: {e}")
app/services/cart_service.py CHANGED
@@ -1,31 +1,104 @@
1
  from datetime import timedelta
 
 
 
2
  from app.models.cart_model import AppointmentCart
3
  from app.repositories.cart_repository import save_cart_draft, get_cart_draft, delete_cart_draft
4
 
5
- async def add_appointment_to_cart(appointment_cart: AppointmentCart):
 
 
 
 
6
  """
7
- Add an appointment draft to the cart.
 
 
 
 
 
 
 
8
  """
9
- cart_key = f"cart:{appointment_cart.user_id}"
10
- await save_cart_draft(cart_key, appointment_cart.dict(), appointment_cart.ttl)
11
- return {"message": "Appointment added to cart as draft.", "cart_id": cart_key}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  async def retrieve_appointment_from_cart(user_id: str):
14
  """
15
  Retrieve an appointment draft from the cart.
 
 
 
 
 
 
16
  """
17
  cart_key = f"cart:{user_id}"
18
- cart_data = await get_cart_draft(cart_key)
19
- if cart_data:
20
- return {"user_id": user_id, "appointment": cart_data}
21
- return {"message": "No appointment draft found in the cart."}
 
 
 
 
 
 
 
22
 
23
  async def remove_appointment_from_cart(user_id: str):
24
  """
25
  Remove an appointment draft from the cart.
 
 
 
 
 
 
26
  """
27
  cart_key = f"cart:{user_id}"
28
- result = await delete_cart_draft(cart_key)
29
- if result:
30
- return {"message": "Appointment draft removed from cart."}
31
- return {"message": "No appointment draft found to delete."}
 
 
 
 
 
 
 
 
1
  from datetime import timedelta
2
+ import json
3
+ import logging
4
+ from fastapi import HTTPException
5
  from app.models.cart_model import AppointmentCart
6
  from app.repositories.cart_repository import save_cart_draft, get_cart_draft, delete_cart_draft
7
 
8
+ # Configure logging
9
+ logger = logging.getLogger(__name__)
10
+ logging.basicConfig(level=logging.INFO)
11
+
12
+ async def add_appointment_to_cart(user_id: str, appointment_data: dict):
13
  """
14
+ Add an appointment draft to the cart with optional TTL.
15
+
16
+ Args:
17
+ user_id (str): The ID of the user.
18
+ appointment_data (dict): The appointment details, including TTL.
19
+
20
+ Returns:
21
+ dict: Confirmation message with cart ID.
22
  """
23
+ cart_key = f"cart:{user_id}"
24
+ ttl = appointment_data.get("ttl", "forever")
25
+
26
+ try:
27
+ # Validate user_id
28
+ if not user_id:
29
+ raise ValueError("User ID is required.")
30
+
31
+ # Validate appointment data
32
+ if not appointment_data:
33
+ raise ValueError("Appointment data is required.")
34
+
35
+ logger.info(f"Adding appointment to cart for user: {user_id} with TTL: {ttl}")
36
+
37
+ # Serialize the data to JSON
38
+ serialized_data = json.dumps(appointment_data)
39
+
40
+ if ttl == "forever":
41
+ # No expiration for the key
42
+ await save_cart_draft(cart_key, serialized_data)
43
+ else:
44
+ # Validate TTL
45
+ if not isinstance(ttl, int) or ttl <= 0:
46
+ raise ValueError("TTL must be a positive integer or 'forever'.")
47
+
48
+ # Set key with expiration
49
+ await save_cart_draft(cart_key, serialized_data, ttl=ttl)
50
+
51
+ logger.info(f"Appointment draft added successfully for user: {user_id}")
52
+ return {"message": "Appointment added to cart successfully", "cart_id": cart_key}
53
+ except ValueError as e:
54
+ logger.error(f"Validation error: {e}")
55
+ raise HTTPException(status_code=400, detail=str(e))
56
+ except Exception as e:
57
+ logger.error(f"Failed to add appointment to cart for user {user_id}: {e}")
58
+ raise HTTPException(status_code=500, detail=f"Failed to add appointment to cart: {e}")
59
 
60
  async def retrieve_appointment_from_cart(user_id: str):
61
  """
62
  Retrieve an appointment draft from the cart.
63
+
64
+ Args:
65
+ user_id (str): The ID of the user.
66
+
67
+ Returns:
68
+ dict: The appointment draft or a message if not found.
69
  """
70
  cart_key = f"cart:{user_id}"
71
+ try:
72
+ logger.info(f"Retrieving appointment draft for user: {user_id}")
73
+ cart_data = await get_cart_draft(cart_key)
74
+ if cart_data:
75
+ logger.info(f"Appointment draft found for user: {user_id}")
76
+ return {"user_id": user_id, "appointment": json.loads(cart_data)}
77
+ logger.info(f"No appointment draft found for user: {user_id}")
78
+ return {"message": "No appointment draft found in the cart."}
79
+ except Exception as e:
80
+ logger.error(f"Failed to retrieve appointment draft for user {user_id}: {e}")
81
+ raise HTTPException(status_code=500, detail=f"Failed to retrieve appointment draft: {e}")
82
 
83
  async def remove_appointment_from_cart(user_id: str):
84
  """
85
  Remove an appointment draft from the cart.
86
+
87
+ Args:
88
+ user_id (str): The ID of the user.
89
+
90
+ Returns:
91
+ dict: Confirmation message or an error message if not found.
92
  """
93
  cart_key = f"cart:{user_id}"
94
+ try:
95
+ logger.info(f"Removing appointment draft for user: {user_id}")
96
+ result = await delete_cart_draft(cart_key)
97
+ if result:
98
+ logger.info(f"Appointment draft removed successfully for user: {user_id}")
99
+ return {"message": "Appointment draft removed from cart."}
100
+ logger.info(f"No appointment draft found to delete for user: {user_id}")
101
+ return {"message": "No appointment draft found to delete."}
102
+ except Exception as e:
103
+ logger.error(f"Failed to remove appointment draft for user {user_id}: {e}")
104
+ raise HTTPException(status_code=500, detail=f"Failed to remove appointment draft: {e}")
app/utils/__pycache__/appointment_utils.cpython-311.pyc ADDED
Binary file (4.67 kB). View file
 
app/utils/appointment_utils.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timezone
2
+ from sqlalchemy.sql import text
3
+ from fastapi import HTTPException
4
+
5
+ def build_filter_query(filters: dict):
6
+ """
7
+ Builds a dynamic SQL filter query based on the provided filter parameters.
8
+
9
+ Args:
10
+ filters (dict): A dictionary containing filter conditions.
11
+
12
+ Returns:
13
+ str: The WHERE clause to append to a query.
14
+ """
15
+ conditions = []
16
+ for key, value in filters.items():
17
+ if value:
18
+ conditions.append(f"{key} = :{key}")
19
+ return " AND ".join(conditions)
20
+
21
+ def validate_query_result(result, error_message="Item not found"):
22
+ """
23
+ Validates the query result and raises an HTTPException if no result is found.
24
+
25
+ Args:
26
+ result (dict): The result of the query.
27
+ error_message (str): The error message to return if the result is None.
28
+
29
+ Raises:
30
+ HTTPException: If the result is None.
31
+ """
32
+ if not result:
33
+ raise HTTPException(status_code=404, detail=error_message)
34
+
35
+ def validate_datetime_format(datetime_str: str):
36
+ """
37
+ Validates if the given string is in proper datetime format (ISO 8601).
38
+
39
+ Args:
40
+ datetime_str (str): The datetime string to validate.
41
+
42
+ Raises:
43
+ HTTPException: If the datetime string is invalid.
44
+ """
45
+ try:
46
+ datetime.fromisoformat(datetime_str)
47
+ except ValueError:
48
+ raise HTTPException(status_code=400, detail="Invalid datetime format. Use ISO 8601 format.")
49
+
50
+ def serialize_appointment(appointment):
51
+ """
52
+ Serializes an appointment database row into a dictionary.
53
+
54
+ Args:
55
+ appointment (RowProxy): The SQLAlchemy RowProxy object.
56
+
57
+ Returns:
58
+ dict: A serialized dictionary representation of the appointment.
59
+ """
60
+ if appointment is None:
61
+ return None
62
+
63
+ return {
64
+ "appointment_id": appointment["appointment_id"],
65
+ "merchant_id": appointment["merchant_id"],
66
+ "customer_id": appointment["customer_id"],
67
+ "appointment_date": appointment["appointment_date"].isoformat(),
68
+ "appointment_time": appointment["appointment_time"].isoformat(),
69
+ "status": appointment["status"],
70
+ "services": appointment["services"],
71
+ "created_at": appointment["created_at"].isoformat(),
72
+ "modified_at": appointment["modified_at"].isoformat(),
73
+ }
74
+
75
+ def validate_existing_appointment(appointment):
76
+ """
77
+ Validates the existing appointment's status for operations.
78
+
79
+ Args:
80
+ appointment (dict): The appointment data.
81
+
82
+ Raises:
83
+ HTTPException: If the appointment is not found or cannot be modified.
84
+ """
85
+ if not appointment:
86
+ raise HTTPException(status_code=404, detail="Appointment not found.")
87
+
88
+ if appointment["status"] == "canceled":
89
+ raise HTTPException(status_code=400, detail="Cannot modify a canceled appointment.")
90
+
91
+ def calculate_appointment_duration(services):
92
+ """
93
+ Calculates the total duration of the appointment based on its services.
94
+
95
+ Args:
96
+ services (list): A list of services in the appointment.
97
+
98
+ Returns:
99
+ int: Total duration in minutes.
100
+ """
101
+ return sum(service.get("duration", 0) for service in services)
app/views/__pycache__/appointment_view.cpython-311.pyc CHANGED
Binary files a/app/views/__pycache__/appointment_view.cpython-311.pyc and b/app/views/__pycache__/appointment_view.cpython-311.pyc differ
 
app/views/__pycache__/cart_view.cpython-311.pyc CHANGED
Binary files a/app/views/__pycache__/cart_view.cpython-311.pyc and b/app/views/__pycache__/cart_view.cpython-311.pyc differ
 
app/views/cart_view.py CHANGED
@@ -1,5 +1,5 @@
1
  from fastapi import APIRouter
2
- from app.controllers.cart_controller import router as cart_router
3
 
4
  router = APIRouter()
5
- router.include_router(cart_router, prefix="/cart", tags=["Cart Management"])
 
1
  from fastapi import APIRouter
2
+ from app.controllers.cart_controller import router as cart_controller
3
 
4
  router = APIRouter()
5
+ router.include_router(cart_controller, prefix="", tags=["Cart Management"])