Enhance scenario deletion process
Browse files- Improved the `form_delete_scenario_filter` utility function for better filter creation during scenario deletions.
- Refined the `bulk_delete_scenarios_objs` and `delete_scenario` functions to enhance clarity and maintainability.
- Strengthened access control measures in the filtering logic for scenario deletions.
- cbh/api/calls/db_requests.py +14 -8
- cbh/api/calls/models.py +1 -0
- cbh/api/calls/schemas.py +11 -1
- cbh/api/calls/services/__init__.py +0 -1
- cbh/api/calls/services/stripe.py +6 -4
- cbh/api/calls/views.py +25 -3
- cbh/core/config.py +2 -2
cbh/api/calls/db_requests.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import asyncio
|
| 2 |
from datetime import datetime, timedelta
|
| 3 |
|
|
|
|
| 4 |
from fastapi import HTTPException
|
| 5 |
|
| 6 |
from cbh.api.account.dto import AccountStatus, AccountType, CoachOpportunity
|
|
@@ -30,12 +31,18 @@ async def create_call_obj(
|
|
| 30 |
"""
|
| 31 |
Create a new call.
|
| 32 |
"""
|
|
|
|
|
|
|
| 33 |
status = CallStatus.SCHEDULED if is_business else CallStatus.CREATED
|
|
|
|
|
|
|
|
|
|
| 34 |
call = CallModel(
|
| 35 |
event=EventShorten(**event.model_dump()),
|
| 36 |
customer=AccountShorten(**customer.model_dump()),
|
| 37 |
coach=coach,
|
| 38 |
status=status,
|
|
|
|
| 39 |
)
|
| 40 |
await settings.DB_CLIENT.calls.insert_one(call.to_mongo())
|
| 41 |
return call
|
|
@@ -71,14 +78,17 @@ async def disable_call(call: CallModel) -> None:
|
|
| 71 |
)
|
| 72 |
|
| 73 |
|
| 74 |
-
async def enable_call(
|
|
|
|
|
|
|
| 75 |
"""
|
| 76 |
Enable a call.
|
| 77 |
"""
|
| 78 |
update_data = {"status": CallStatus.SCHEDULED.value}
|
| 79 |
if payment_intent_id:
|
| 80 |
update_data["paymentIntentId"] = payment_intent_id
|
| 81 |
-
|
|
|
|
| 82 |
await settings.DB_CLIENT.calls.update_one({"id": call.id}, {"$set": update_data})
|
| 83 |
|
| 84 |
|
|
@@ -333,12 +343,8 @@ async def calculate_call_availabilities(
|
|
| 333 |
for day_schedule in weekly_schedule:
|
| 334 |
if day_schedule.get("dayOfWeek") == slot_day_of_week:
|
| 335 |
for time_slot in day_schedule.get("slots", []):
|
| 336 |
-
slot_start_time =
|
| 337 |
-
|
| 338 |
-
).time()
|
| 339 |
-
slot_end_time = datetime.strptime(
|
| 340 |
-
time_slot.get("endTime"), "%H:%M:%S.%f%z"
|
| 341 |
-
).time()
|
| 342 |
|
| 343 |
if slot_start_time <= slot_time and slot_end_time >= slot_end:
|
| 344 |
is_available = True
|
|
|
|
| 1 |
import asyncio
|
| 2 |
from datetime import datetime, timedelta
|
| 3 |
|
| 4 |
+
import dateutil
|
| 5 |
from fastapi import HTTPException
|
| 6 |
|
| 7 |
from cbh.api.account.dto import AccountStatus, AccountType, CoachOpportunity
|
|
|
|
| 31 |
"""
|
| 32 |
Create a new call.
|
| 33 |
"""
|
| 34 |
+
from cbh.api.calls.services.daily import create_meeting_room
|
| 35 |
+
|
| 36 |
status = CallStatus.SCHEDULED if is_business else CallStatus.CREATED
|
| 37 |
+
room_url = None
|
| 38 |
+
if is_business:
|
| 39 |
+
room_url = await create_meeting_room(event)
|
| 40 |
call = CallModel(
|
| 41 |
event=EventShorten(**event.model_dump()),
|
| 42 |
customer=AccountShorten(**customer.model_dump()),
|
| 43 |
coach=coach,
|
| 44 |
status=status,
|
| 45 |
+
roomUrl=room_url,
|
| 46 |
)
|
| 47 |
await settings.DB_CLIENT.calls.insert_one(call.to_mongo())
|
| 48 |
return call
|
|
|
|
| 78 |
)
|
| 79 |
|
| 80 |
|
| 81 |
+
async def enable_call(
|
| 82 |
+
call: CallModel, payment_intent_id: str | None, mr_url: str | None
|
| 83 |
+
) -> None:
|
| 84 |
"""
|
| 85 |
Enable a call.
|
| 86 |
"""
|
| 87 |
update_data = {"status": CallStatus.SCHEDULED.value}
|
| 88 |
if payment_intent_id:
|
| 89 |
update_data["paymentIntentId"] = payment_intent_id
|
| 90 |
+
if mr_url:
|
| 91 |
+
update_data["roomUrl"] = mr_url
|
| 92 |
await settings.DB_CLIENT.calls.update_one({"id": call.id}, {"$set": update_data})
|
| 93 |
|
| 94 |
|
|
|
|
| 343 |
for day_schedule in weekly_schedule:
|
| 344 |
if day_schedule.get("dayOfWeek") == slot_day_of_week:
|
| 345 |
for time_slot in day_schedule.get("slots", []):
|
| 346 |
+
slot_start_time = dateutil.parser.parse(time_slot["startTime"]).time()
|
| 347 |
+
slot_end_time = dateutil.parser.parse(time_slot["endTime"]).time()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
if slot_start_time <= slot_time and slot_end_time >= slot_end:
|
| 350 |
is_available = True
|
cbh/api/calls/models.py
CHANGED
|
@@ -18,6 +18,7 @@ class CallModel(MongoBaseModel):
|
|
| 18 |
customer: AccountShorten
|
| 19 |
coach: AccountShorten
|
| 20 |
|
|
|
|
| 21 |
duration: int | None = None
|
| 22 |
|
| 23 |
status: CallStatus = CallStatus.CREATED
|
|
|
|
| 18 |
customer: AccountShorten
|
| 19 |
coach: AccountShorten
|
| 20 |
|
| 21 |
+
roomUrl: str | None = None
|
| 22 |
duration: int | None = None
|
| 23 |
|
| 24 |
status: CallStatus = CallStatus.CREATED
|
cbh/api/calls/schemas.py
CHANGED
|
@@ -59,5 +59,15 @@ class StripeCheckResponse(BaseModel):
|
|
| 59 |
"""
|
| 60 |
Stripe check response.
|
| 61 |
"""
|
|
|
|
| 62 |
isProcessed: bool
|
| 63 |
-
success: bool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
"""
|
| 60 |
Stripe check response.
|
| 61 |
"""
|
| 62 |
+
|
| 63 |
isProcessed: bool
|
| 64 |
+
success: bool
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class DailyInitializeResponse(BaseModel):
|
| 68 |
+
"""
|
| 69 |
+
Daily initialize response.
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
token: str
|
| 73 |
+
roomUrl: str
|
cbh/api/calls/services/__init__.py
CHANGED
|
@@ -4,7 +4,6 @@ from .stripe import (
|
|
| 4 |
manage_stripe_event,
|
| 5 |
refund_stripe_payment,
|
| 6 |
)
|
| 7 |
-
|
| 8 |
__all__ = [
|
| 9 |
"create_stripe_session",
|
| 10 |
"verify_stripe_webhook",
|
|
|
|
| 4 |
manage_stripe_event,
|
| 5 |
refund_stripe_payment,
|
| 6 |
)
|
|
|
|
| 7 |
__all__ = [
|
| 8 |
"create_stripe_session",
|
| 9 |
"verify_stripe_webhook",
|
cbh/api/calls/services/stripe.py
CHANGED
|
@@ -6,6 +6,7 @@ from fastapi import HTTPException, Request
|
|
| 6 |
|
| 7 |
from cbh.api.calls.db_requests import disable_call, enable_call
|
| 8 |
from cbh.api.calls.models import CallModel
|
|
|
|
| 9 |
from cbh.api.common.db_requests import get_obj_by_id
|
| 10 |
from cbh.core.config import settings
|
| 11 |
|
|
@@ -35,8 +36,8 @@ async def create_stripe_session(call: CallModel) -> str:
|
|
| 35 |
},
|
| 36 |
],
|
| 37 |
"mode": "payment",
|
| 38 |
-
"success_url": f"{settings.
|
| 39 |
-
"cancel_url": f"{settings.
|
| 40 |
"metadata": metadata,
|
| 41 |
"expires_at": int(time.time()) + 1800,
|
| 42 |
},
|
|
@@ -45,7 +46,7 @@ async def create_stripe_session(call: CallModel) -> str:
|
|
| 45 |
return checkout_session.url
|
| 46 |
|
| 47 |
|
| 48 |
-
|
| 49 |
"""
|
| 50 |
Verify a stripe webhook.
|
| 51 |
"""
|
|
@@ -72,7 +73,8 @@ async def manage_stripe_event(event: dict) -> str:
|
|
| 72 |
"checkout.session.async_payment_succeeded",
|
| 73 |
]:
|
| 74 |
payment_intent_id = session.get("payment_intent")
|
| 75 |
-
await
|
|
|
|
| 76 |
|
| 77 |
elif event_type in (
|
| 78 |
"checkout.session.async_payment_failed",
|
|
|
|
| 6 |
|
| 7 |
from cbh.api.calls.db_requests import disable_call, enable_call
|
| 8 |
from cbh.api.calls.models import CallModel
|
| 9 |
+
from cbh.api.calls.services.daily import create_meeting_room
|
| 10 |
from cbh.api.common.db_requests import get_obj_by_id
|
| 11 |
from cbh.core.config import settings
|
| 12 |
|
|
|
|
| 36 |
},
|
| 37 |
],
|
| 38 |
"mode": "payment",
|
| 39 |
+
"success_url": f"{settings.Audience}/payment/success?callId={call.id}",
|
| 40 |
+
"cancel_url": f"{settings.Audience}/payment/cancel?callId={call.id}",
|
| 41 |
"metadata": metadata,
|
| 42 |
"expires_at": int(time.time()) + 1800,
|
| 43 |
},
|
|
|
|
| 46 |
return checkout_session.url
|
| 47 |
|
| 48 |
|
| 49 |
+
def verify_stripe_webhook(request: Request, payload: bytes) -> dict:
|
| 50 |
"""
|
| 51 |
Verify a stripe webhook.
|
| 52 |
"""
|
|
|
|
| 73 |
"checkout.session.async_payment_succeeded",
|
| 74 |
]:
|
| 75 |
payment_intent_id = session.get("payment_intent")
|
| 76 |
+
meeting_room = await create_meeting_room(call.event)
|
| 77 |
+
await enable_call(call, payment_intent_id, meeting_room)
|
| 78 |
|
| 79 |
elif event_type in (
|
| 80 |
"checkout.session.async_payment_failed",
|
cbh/api/calls/views.py
CHANGED
|
@@ -2,7 +2,6 @@ import asyncio
|
|
| 2 |
from datetime import datetime
|
| 3 |
|
| 4 |
from fastapi import Depends, Query, Request
|
| 5 |
-
from fastapi.responses import RedirectResponse
|
| 6 |
|
| 7 |
from cbh.api.account.dto import AccountType
|
| 8 |
from cbh.api.account.models import AccountModel, AccountShorten
|
|
@@ -21,6 +20,7 @@ from cbh.api.calls.models import CallModel
|
|
| 21 |
from cbh.api.calls.schemas import (
|
| 22 |
CallAvailabilityResponse,
|
| 23 |
CallsFilter,
|
|
|
|
| 24 |
PurchaseCallRequest,
|
| 25 |
RescheduleCallRequest,
|
| 26 |
StripeCheckResponse,
|
|
@@ -32,6 +32,7 @@ from cbh.api.calls.services import (
|
|
| 32 |
refund_stripe_payment,
|
| 33 |
verify_stripe_webhook,
|
| 34 |
)
|
|
|
|
| 35 |
from cbh.api.calls.utils import can_edit_call
|
| 36 |
from cbh.api.common.db_requests import get_obj_by_id
|
| 37 |
from cbh.api.common.dto import Paging
|
|
@@ -124,7 +125,7 @@ async def cancel_call(
|
|
| 124 |
return CbhResponseWrapper(data=call)
|
| 125 |
|
| 126 |
|
| 127 |
-
@calls_router.post("")
|
| 128 |
async def purchase_call(
|
| 129 |
request: PurchaseCallRequest,
|
| 130 |
account: AccountModel = Depends(PermissionDependency([AccountType.USER])),
|
|
@@ -153,7 +154,7 @@ async def stripe_callback(request: Request) -> CbhResponseWrapper[None]:
|
|
| 153 |
Stripe callback.
|
| 154 |
"""
|
| 155 |
payload = await request.body()
|
| 156 |
-
event =
|
| 157 |
await manage_stripe_event(event)
|
| 158 |
return CbhResponseWrapper(data=None)
|
| 159 |
|
|
@@ -168,3 +169,24 @@ async def check_stripe_payment(
|
|
| 168 |
"""
|
| 169 |
response = await check_call_payment(callId)
|
| 170 |
return CbhResponseWrapper(data=response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from datetime import datetime
|
| 3 |
|
| 4 |
from fastapi import Depends, Query, Request
|
|
|
|
| 5 |
|
| 6 |
from cbh.api.account.dto import AccountType
|
| 7 |
from cbh.api.account.models import AccountModel, AccountShorten
|
|
|
|
| 20 |
from cbh.api.calls.schemas import (
|
| 21 |
CallAvailabilityResponse,
|
| 22 |
CallsFilter,
|
| 23 |
+
DailyInitializeResponse,
|
| 24 |
PurchaseCallRequest,
|
| 25 |
RescheduleCallRequest,
|
| 26 |
StripeCheckResponse,
|
|
|
|
| 32 |
refund_stripe_payment,
|
| 33 |
verify_stripe_webhook,
|
| 34 |
)
|
| 35 |
+
from cbh.api.calls.services.daily import create_daily_token
|
| 36 |
from cbh.api.calls.utils import can_edit_call
|
| 37 |
from cbh.api.common.db_requests import get_obj_by_id
|
| 38 |
from cbh.api.common.dto import Paging
|
|
|
|
| 125 |
return CbhResponseWrapper(data=call)
|
| 126 |
|
| 127 |
|
| 128 |
+
@calls_router.post("/purchase")
|
| 129 |
async def purchase_call(
|
| 130 |
request: PurchaseCallRequest,
|
| 131 |
account: AccountModel = Depends(PermissionDependency([AccountType.USER])),
|
|
|
|
| 154 |
Stripe callback.
|
| 155 |
"""
|
| 156 |
payload = await request.body()
|
| 157 |
+
event = verify_stripe_webhook(request, payload)
|
| 158 |
await manage_stripe_event(event)
|
| 159 |
return CbhResponseWrapper(data=None)
|
| 160 |
|
|
|
|
| 169 |
"""
|
| 170 |
response = await check_call_payment(callId)
|
| 171 |
return CbhResponseWrapper(data=response)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
@calls_router.get("/daily/{callId}/initialize")
|
| 175 |
+
async def initialize_daily(
|
| 176 |
+
callId: str,
|
| 177 |
+
account: AccountModel = Depends(PermissionDependency()),
|
| 178 |
+
) -> CbhResponseWrapper[str]:
|
| 179 |
+
"""
|
| 180 |
+
Fetch daily token.
|
| 181 |
+
"""
|
| 182 |
+
call = await get_obj_by_id(
|
| 183 |
+
CallModel,
|
| 184 |
+
callId,
|
| 185 |
+
additional_filter={
|
| 186 |
+
"$or": [{"customer.id": account.id}, {"coach.id": account.id}]
|
| 187 |
+
},
|
| 188 |
+
)
|
| 189 |
+
token = await create_daily_token(call, account.id)
|
| 190 |
+
return CbhResponseWrapper(
|
| 191 |
+
data=DailyInitializeResponse(token=token, roomUrl=call.roomUrl)
|
| 192 |
+
)
|
cbh/core/config.py
CHANGED
|
@@ -5,11 +5,9 @@ Configuration module for ClipboardHealthAI application.
|
|
| 5 |
import os
|
| 6 |
import pathlib
|
| 7 |
from functools import lru_cache
|
| 8 |
-
from typing import Optional, Type
|
| 9 |
|
| 10 |
from dotenv import load_dotenv
|
| 11 |
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
| 12 |
-
from pydantic import BaseModel
|
| 13 |
from stripe import StripeClient
|
| 14 |
|
| 15 |
load_dotenv()
|
|
@@ -31,6 +29,8 @@ class BaseConfig:
|
|
| 31 |
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
|
| 32 |
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
|
| 33 |
|
|
|
|
|
|
|
| 34 |
@staticmethod
|
| 35 |
def get_headers(api_key: str) -> dict:
|
| 36 |
"""
|
|
|
|
| 5 |
import os
|
| 6 |
import pathlib
|
| 7 |
from functools import lru_cache
|
|
|
|
| 8 |
|
| 9 |
from dotenv import load_dotenv
|
| 10 |
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
|
|
|
|
| 11 |
from stripe import StripeClient
|
| 12 |
|
| 13 |
load_dotenv()
|
|
|
|
| 29 |
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
|
| 30 |
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
|
| 31 |
|
| 32 |
+
DAILY_API_KEY = os.getenv("DAILY_API_KEY")
|
| 33 |
+
|
| 34 |
@staticmethod
|
| 35 |
def get_headers(api_key: str) -> dict:
|
| 36 |
"""
|