init
Browse files- cbh/api/account/dto.py +1 -1
- cbh/api/availability/__init__.py +1 -1
- cbh/api/availability/db_requests.py +21 -0
- cbh/api/availability/models.py +0 -3
- cbh/api/availability/schemas.py +0 -12
- cbh/api/availability/views.py +25 -7
- cbh/api/calls/__init__.py +1 -1
- cbh/api/calls/db_requests.py +10 -15
- cbh/api/calls/schemas.py +7 -7
- cbh/api/calls/services/stripe.py +20 -17
- cbh/api/calls/views.py +3 -4
- cbh/api/common/db_requests.py +6 -22
- cbh/api/discountcode/__init__.py +1 -1
- cbh/api/events/__init__.py +1 -1
- cbh/api/security/db_requests.py +21 -1
- cbh/core/config.py +5 -6
- cbh/core/database.py +2 -2
- requirements.txt +121 -63
cbh/api/account/dto.py
CHANGED
|
@@ -29,6 +29,6 @@ class CoachOpportunity(Enum):
|
|
| 29 |
Coach opportunity.
|
| 30 |
"""
|
| 31 |
|
| 32 |
-
|
| 33 |
BUSINESS = 2
|
| 34 |
BOTH = 3
|
|
|
|
| 29 |
Coach opportunity.
|
| 30 |
"""
|
| 31 |
|
| 32 |
+
GENERAL = 1
|
| 33 |
BUSINESS = 2
|
| 34 |
BOTH = 3
|
cbh/api/availability/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
-
availability_router = APIRouter(prefix="/availability", tags=["availability"])
|
| 4 |
|
| 5 |
from . import views
|
|
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
+
availability_router = APIRouter(prefix="/api/availability", tags=["availability"])
|
| 4 |
|
| 5 |
from . import views
|
cbh/api/availability/db_requests.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from cbh.api.availability.schemas import UpdateAvailabilityRequest
|
| 2 |
+
from cbh.api.availability.models import AvailabilityModel
|
| 3 |
+
from cbh.api.common.db_requests import get_obj_by_id
|
| 4 |
+
from cbh.core.config import settings
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
async def update_availability_obj(
|
| 8 |
+
availability: UpdateAvailabilityRequest, coach_id: str
|
| 9 |
+
) -> AvailabilityModel:
|
| 10 |
+
"""
|
| 11 |
+
Update availability object.
|
| 12 |
+
"""
|
| 13 |
+
availability_obj = await get_obj_by_id(
|
| 14 |
+
AvailabilityModel, None, additional_filter={"coach.id": coach_id}
|
| 15 |
+
)
|
| 16 |
+
availability_obj.weeklySchedule = availability.weeklySchedule
|
| 17 |
+
await settings.DB_CLIENT.availabilities.update_one(
|
| 18 |
+
{"coach.id": coach_id},
|
| 19 |
+
{"$set": availability_obj.to_mongo()},
|
| 20 |
+
)
|
| 21 |
+
return availability_obj
|
cbh/api/availability/models.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
from cbh.api.account.models import AccountShorten
|
| 2 |
from cbh.api.availability.dto import DaySchedule
|
| 3 |
-
from cbh.api.timezone.models import TimezoneModel
|
| 4 |
from cbh.core.database import MongoBaseModel
|
| 5 |
|
| 6 |
|
|
@@ -10,6 +9,4 @@ class AvailabilityModel(MongoBaseModel):
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
coach: AccountShorten
|
| 13 |
-
timezone: TimezoneModel
|
| 14 |
-
|
| 15 |
weeklySchedule: list[DaySchedule] | None = None
|
|
|
|
| 1 |
from cbh.api.account.models import AccountShorten
|
| 2 |
from cbh.api.availability.dto import DaySchedule
|
|
|
|
| 3 |
from cbh.core.database import MongoBaseModel
|
| 4 |
|
| 5 |
|
|
|
|
| 9 |
"""
|
| 10 |
|
| 11 |
coach: AccountShorten
|
|
|
|
|
|
|
| 12 |
weeklySchedule: list[DaySchedule] | None = None
|
cbh/api/availability/schemas.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
| 1 |
from pydantic import BaseModel
|
| 2 |
from cbh.api.availability.dto import DaySchedule
|
| 3 |
-
from cbh.api.availability.models import AvailabilityModel
|
| 4 |
-
from cbh.api.events.models import EventShorten
|
| 5 |
|
| 6 |
|
| 7 |
class UpdateAvailabilityRequest(BaseModel):
|
|
@@ -9,14 +7,4 @@ class UpdateAvailabilityRequest(BaseModel):
|
|
| 9 |
Update availability request.
|
| 10 |
"""
|
| 11 |
|
| 12 |
-
timezoneId: str
|
| 13 |
weeklySchedule: list[DaySchedule]
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
class ExtendedAvailabilityResponse(BaseModel):
|
| 17 |
-
"""
|
| 18 |
-
Extended availability response.
|
| 19 |
-
"""
|
| 20 |
-
|
| 21 |
-
availability: AvailabilityModel
|
| 22 |
-
events: list[EventShorten]
|
|
|
|
| 1 |
from pydantic import BaseModel
|
| 2 |
from cbh.api.availability.dto import DaySchedule
|
|
|
|
|
|
|
| 3 |
|
| 4 |
|
| 5 |
class UpdateAvailabilityRequest(BaseModel):
|
|
|
|
| 7 |
Update availability request.
|
| 8 |
"""
|
| 9 |
|
|
|
|
| 10 |
weeklySchedule: list[DaySchedule]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cbh/api/availability/views.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
from fastapi import Depends
|
| 2 |
from cbh.api.account.models import AccountModel
|
|
|
|
| 3 |
from cbh.api.availability.schemas import (
|
| 4 |
UpdateAvailabilityRequest,
|
| 5 |
-
ExtendedAvailabilityResponse,
|
| 6 |
)
|
|
|
|
| 7 |
from cbh.core.security import PermissionDependency
|
| 8 |
from cbh.api.account.dto import AccountType
|
| 9 |
from cbh.core.wrappers import CbhResponseWrapper
|
|
@@ -16,21 +17,25 @@ async def get_own_availability(
|
|
| 16 |
account: AccountModel = Depends(PermissionDependency([AccountType.COACH])),
|
| 17 |
) -> CbhResponseWrapper[AvailabilityModel]:
|
| 18 |
|
| 19 |
-
availability = await
|
|
|
|
|
|
|
| 20 |
return CbhResponseWrapper(data=availability)
|
| 21 |
|
| 22 |
|
| 23 |
@availability_router.get("/{coachId}")
|
| 24 |
-
async def
|
| 25 |
coachId: str,
|
| 26 |
-
|
| 27 |
PermissionDependency([AccountType.ADMIN, AccountType.USER])
|
| 28 |
),
|
| 29 |
-
) -> CbhResponseWrapper[
|
| 30 |
"""
|
| 31 |
Get availability by coach ID.
|
| 32 |
"""
|
| 33 |
-
availability = await
|
|
|
|
|
|
|
| 34 |
return CbhResponseWrapper(data=availability)
|
| 35 |
|
| 36 |
|
|
@@ -42,5 +47,18 @@ async def update_own_availability(
|
|
| 42 |
"""
|
| 43 |
Update own availability.
|
| 44 |
"""
|
| 45 |
-
availability = await
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
return CbhResponseWrapper(data=availability)
|
|
|
|
| 1 |
from fastapi import Depends
|
| 2 |
from cbh.api.account.models import AccountModel
|
| 3 |
+
from cbh.api.availability.db_requests import update_availability_obj
|
| 4 |
from cbh.api.availability.schemas import (
|
| 5 |
UpdateAvailabilityRequest,
|
|
|
|
| 6 |
)
|
| 7 |
+
from cbh.api.common.db_requests import get_obj_by_id
|
| 8 |
from cbh.core.security import PermissionDependency
|
| 9 |
from cbh.api.account.dto import AccountType
|
| 10 |
from cbh.core.wrappers import CbhResponseWrapper
|
|
|
|
| 17 |
account: AccountModel = Depends(PermissionDependency([AccountType.COACH])),
|
| 18 |
) -> CbhResponseWrapper[AvailabilityModel]:
|
| 19 |
|
| 20 |
+
availability = await get_obj_by_id(
|
| 21 |
+
AvailabilityModel, None, additional_filter={"coach.id": account.id}
|
| 22 |
+
)
|
| 23 |
return CbhResponseWrapper(data=availability)
|
| 24 |
|
| 25 |
|
| 26 |
@availability_router.get("/{coachId}")
|
| 27 |
+
async def get_availability(
|
| 28 |
coachId: str,
|
| 29 |
+
_: AccountModel = Depends(
|
| 30 |
PermissionDependency([AccountType.ADMIN, AccountType.USER])
|
| 31 |
),
|
| 32 |
+
) -> CbhResponseWrapper[AvailabilityModel]:
|
| 33 |
"""
|
| 34 |
Get availability by coach ID.
|
| 35 |
"""
|
| 36 |
+
availability = await get_obj_by_id(
|
| 37 |
+
AvailabilityModel, None, additional_filter={"coach.id": coachId}
|
| 38 |
+
)
|
| 39 |
return CbhResponseWrapper(data=availability)
|
| 40 |
|
| 41 |
|
|
|
|
| 47 |
"""
|
| 48 |
Update own availability.
|
| 49 |
"""
|
| 50 |
+
availability = await update_availability_obj(availability, account.id)
|
| 51 |
+
return CbhResponseWrapper(data=availability)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@availability_router.put("/{coachId}")
|
| 55 |
+
async def update_availability(
|
| 56 |
+
coachId: str,
|
| 57 |
+
availability: UpdateAvailabilityRequest,
|
| 58 |
+
_: AccountModel = Depends(PermissionDependency([AccountType.ADMIN])),
|
| 59 |
+
) -> CbhResponseWrapper[AvailabilityModel]:
|
| 60 |
+
"""
|
| 61 |
+
Update availability.
|
| 62 |
+
"""
|
| 63 |
+
availability = await update_availability_obj(availability, coachId)
|
| 64 |
return CbhResponseWrapper(data=availability)
|
cbh/api/calls/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
-
calls_router = APIRouter(prefix="/calls", tags=["calls"])
|
| 4 |
|
| 5 |
from . import views
|
|
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
+
calls_router = APIRouter(prefix="/api/calls", tags=["calls"])
|
| 4 |
|
| 5 |
from . import views
|
cbh/api/calls/db_requests.py
CHANGED
|
@@ -3,8 +3,8 @@ from datetime import datetime, timedelta
|
|
| 3 |
|
| 4 |
from fastapi import HTTPException
|
| 5 |
|
| 6 |
-
from cbh.api.account.models import AccountModel, AccountShorten
|
| 7 |
from cbh.api.account.dto import AccountStatus, AccountType, CoachOpportunity
|
|
|
|
| 8 |
from cbh.api.calls.dto import CallAvailabilityOption, CallStatus
|
| 9 |
from cbh.api.calls.models import CallModel
|
| 10 |
from cbh.api.calls.schemas import (
|
|
@@ -24,12 +24,12 @@ async def create_call_obj(
|
|
| 24 |
event: EventModel,
|
| 25 |
coach: AccountShorten,
|
| 26 |
customer: AccountModel,
|
| 27 |
-
|
| 28 |
) -> CallModel:
|
| 29 |
"""
|
| 30 |
Create a new call.
|
| 31 |
"""
|
| 32 |
-
status = CallStatus.SCHEDULED if
|
| 33 |
call = CallModel(
|
| 34 |
event=EventShorten(**event.model_dump()),
|
| 35 |
customer=AccountShorten(**customer.model_dump()),
|
|
@@ -200,7 +200,7 @@ async def calculate_call_availabilities(
|
|
| 200 |
opportunity_filter = (
|
| 201 |
[CoachOpportunity.BUSINESS.value, CoachOpportunity.BOTH.value]
|
| 202 |
if isBusiness
|
| 203 |
-
else [CoachOpportunity.
|
| 204 |
)
|
| 205 |
|
| 206 |
pipeline = [
|
|
@@ -332,17 +332,12 @@ async def calculate_call_availabilities(
|
|
| 332 |
for day_schedule in weekly_schedule:
|
| 333 |
if day_schedule.get("dayOfWeek") == slot_day_of_week:
|
| 334 |
for time_slot in day_schedule.get("slots", []):
|
| 335 |
-
slot_start_time =
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
).time()
|
| 342 |
-
if isinstance(slot_end_time, str):
|
| 343 |
-
slot_end_time = datetime.strptime(
|
| 344 |
-
slot_end_time, "%H:%M:%S"
|
| 345 |
-
).time()
|
| 346 |
|
| 347 |
if slot_start_time <= slot_time and slot_end_time >= slot_end:
|
| 348 |
is_available = True
|
|
|
|
| 3 |
|
| 4 |
from fastapi import HTTPException
|
| 5 |
|
|
|
|
| 6 |
from cbh.api.account.dto import AccountStatus, AccountType, CoachOpportunity
|
| 7 |
+
from cbh.api.account.models import AccountModel, AccountShorten
|
| 8 |
from cbh.api.calls.dto import CallAvailabilityOption, CallStatus
|
| 9 |
from cbh.api.calls.models import CallModel
|
| 10 |
from cbh.api.calls.schemas import (
|
|
|
|
| 24 |
event: EventModel,
|
| 25 |
coach: AccountShorten,
|
| 26 |
customer: AccountModel,
|
| 27 |
+
is_business: bool,
|
| 28 |
) -> CallModel:
|
| 29 |
"""
|
| 30 |
Create a new call.
|
| 31 |
"""
|
| 32 |
+
status = CallStatus.SCHEDULED if is_business else CallStatus.CREATED
|
| 33 |
call = CallModel(
|
| 34 |
event=EventShorten(**event.model_dump()),
|
| 35 |
customer=AccountShorten(**customer.model_dump()),
|
|
|
|
| 200 |
opportunity_filter = (
|
| 201 |
[CoachOpportunity.BUSINESS.value, CoachOpportunity.BOTH.value]
|
| 202 |
if isBusiness
|
| 203 |
+
else [CoachOpportunity.GENERAL.value, CoachOpportunity.BOTH.value]
|
| 204 |
)
|
| 205 |
|
| 206 |
pipeline = [
|
|
|
|
| 332 |
for day_schedule in weekly_schedule:
|
| 333 |
if day_schedule.get("dayOfWeek") == slot_day_of_week:
|
| 334 |
for time_slot in day_schedule.get("slots", []):
|
| 335 |
+
slot_start_time = datetime.strptime(
|
| 336 |
+
time_slot.get("startTime"), "%H:%M:%S.%f%z"
|
| 337 |
+
).time()
|
| 338 |
+
slot_end_time = datetime.strptime(
|
| 339 |
+
time_slot.get("endTime"), "%H:%M:%S.%f%z"
|
| 340 |
+
).time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
|
| 342 |
if slot_start_time <= slot_time and slot_end_time >= slot_end:
|
| 343 |
is_available = True
|
cbh/api/calls/schemas.py
CHANGED
|
@@ -38,12 +38,12 @@ class CallsFilter(BaseModel):
|
|
| 38 |
Calls filter.
|
| 39 |
"""
|
| 40 |
|
| 41 |
-
customerIds: list[str]
|
| 42 |
-
coachIds: list[str]
|
| 43 |
-
statuses: list[int]
|
| 44 |
-
minDate: datetime
|
| 45 |
-
maxDate: datetime
|
| 46 |
-
searchTerm: str
|
| 47 |
|
| 48 |
|
| 49 |
class CallAvailabilityResponse(BaseModel):
|
|
@@ -51,5 +51,5 @@ class CallAvailabilityResponse(BaseModel):
|
|
| 51 |
Call availability response.
|
| 52 |
"""
|
| 53 |
|
| 54 |
-
closestOption: CallAvailabilityOption
|
| 55 |
options: list[CallAvailabilityOption]
|
|
|
|
| 38 |
Calls filter.
|
| 39 |
"""
|
| 40 |
|
| 41 |
+
customerIds: list[str] | None = None
|
| 42 |
+
coachIds: list[str] | None = None
|
| 43 |
+
statuses: list[int] | None = None
|
| 44 |
+
minDate: datetime | None = None
|
| 45 |
+
maxDate: datetime | None = None
|
| 46 |
+
searchTerm: str | None = None
|
| 47 |
|
| 48 |
|
| 49 |
class CallAvailabilityResponse(BaseModel):
|
|
|
|
| 51 |
Call availability response.
|
| 52 |
"""
|
| 53 |
|
| 54 |
+
closestOption: CallAvailabilityOption | None
|
| 55 |
options: list[CallAvailabilityOption]
|
cbh/api/calls/services/stripe.py
CHANGED
|
@@ -21,25 +21,28 @@ async def create_stripe_session(
|
|
| 21 |
}
|
| 22 |
|
| 23 |
checkout_session = await settings.STRIPE_CLIENT.checkout.sessions.create_async(
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
"
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
"
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
},
|
|
|
|
| 34 |
},
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
)
|
| 44 |
return checkout_session.url
|
| 45 |
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
checkout_session = await settings.STRIPE_CLIENT.checkout.sessions.create_async(
|
| 24 |
+
params={
|
| 25 |
+
"customer_email": call.customer.email,
|
| 26 |
+
"line_items": [
|
| 27 |
+
{
|
| 28 |
+
"price_data": {
|
| 29 |
+
"currency": "usd",
|
| 30 |
+
"unit_amount": 150 * 100,
|
| 31 |
+
"product_data": {
|
| 32 |
+
"name": "Coaching Session",
|
| 33 |
+
"description": f"1-on-1 coaching session with {call.coach.name}",
|
| 34 |
+
},
|
| 35 |
},
|
| 36 |
+
"quantity": 1,
|
| 37 |
},
|
| 38 |
+
],
|
| 39 |
+
"mode": "payment",
|
| 40 |
+
"success_url": f"{settings.Issuer}/payment/success?callId={call.id}",
|
| 41 |
+
"cancel_url": f"{settings.Issuer}/payment/cancel",
|
| 42 |
+
"metadata": metadata,
|
| 43 |
+
"expires_at": int(time.time()) + 1800,
|
| 44 |
+
},
|
| 45 |
+
options={"api_key": settings.STRIPE_API_KEY}
|
| 46 |
)
|
| 47 |
return checkout_session.url
|
| 48 |
|
cbh/api/calls/views.py
CHANGED
|
@@ -34,7 +34,6 @@ from cbh.api.calls.utils import can_edit_call
|
|
| 34 |
from cbh.api.common.db_requests import get_obj_by_id
|
| 35 |
from cbh.api.common.dto import Paging
|
| 36 |
from cbh.api.common.schemas import AllObjectsResponse, FilterRequest
|
| 37 |
-
from cbh.api.discountcode.models import DiscountCodeModel
|
| 38 |
from cbh.core.security import PermissionDependency
|
| 39 |
from cbh.core.wrappers import CbhResponseWrapper
|
| 40 |
|
|
@@ -47,17 +46,17 @@ async def purchase_call(
|
|
| 47 |
"""
|
| 48 |
Purchase a call.
|
| 49 |
"""
|
| 50 |
-
coach,
|
| 51 |
get_obj_by_id(
|
| 52 |
AccountShorten, request.coachId, projection=AccountShorten.to_mongo_fields()
|
| 53 |
),
|
| 54 |
verify_discount_code(request.discountCode),
|
| 55 |
)
|
| 56 |
event = await create_event_obj(request, coach)
|
| 57 |
-
call = await create_call_obj(event, coach, account)
|
| 58 |
|
| 59 |
session_url = None
|
| 60 |
-
if
|
| 61 |
session_url = await create_stripe_session(call)
|
| 62 |
return CbhResponseWrapper(data=StripeSessionResponse(sessionUrl=session_url))
|
| 63 |
|
|
|
|
| 34 |
from cbh.api.common.db_requests import get_obj_by_id
|
| 35 |
from cbh.api.common.dto import Paging
|
| 36 |
from cbh.api.common.schemas import AllObjectsResponse, FilterRequest
|
|
|
|
| 37 |
from cbh.core.security import PermissionDependency
|
| 38 |
from cbh.core.wrappers import CbhResponseWrapper
|
| 39 |
|
|
|
|
| 46 |
"""
|
| 47 |
Purchase a call.
|
| 48 |
"""
|
| 49 |
+
coach, is_business = await asyncio.gather(
|
| 50 |
get_obj_by_id(
|
| 51 |
AccountShorten, request.coachId, projection=AccountShorten.to_mongo_fields()
|
| 52 |
),
|
| 53 |
verify_discount_code(request.discountCode),
|
| 54 |
)
|
| 55 |
event = await create_event_obj(request, coach)
|
| 56 |
+
call = await create_call_obj(event, coach, account, is_business)
|
| 57 |
|
| 58 |
session_url = None
|
| 59 |
+
if not is_business:
|
| 60 |
session_url = await create_stripe_session(call)
|
| 61 |
return CbhResponseWrapper(data=StripeSessionResponse(sessionUrl=session_url))
|
| 62 |
|
cbh/api/common/db_requests.py
CHANGED
|
@@ -20,28 +20,12 @@ T = TypeVar("T", bound=BaseModel)
|
|
| 20 |
collection_map = {
|
| 21 |
"AccountModel": "accounts",
|
| 22 |
"AccountShorten": "accounts",
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
"
|
| 26 |
-
"
|
| 27 |
-
"
|
| 28 |
-
|
| 29 |
-
"OrganizationModel": "organizations",
|
| 30 |
-
"OrganizationShorten": "organizations",
|
| 31 |
-
"OrganizationDocumentModel": "organizationdocuments",
|
| 32 |
-
"OrganizationSettingsModel": "organizationsettings",
|
| 33 |
-
"UserInsightModel": "userinsights",
|
| 34 |
-
"UserInsightShorten": "userinsights",
|
| 35 |
-
"VoiceModel": "voices",
|
| 36 |
-
"VoiceModelShorten": "voices",
|
| 37 |
-
"ActivityLogModel": "activitylogs",
|
| 38 |
-
"AccountNotificationModel": "accountnotifications",
|
| 39 |
-
"FeedbackModel": "generalfeedbacks",
|
| 40 |
-
"FeedbackExtendedModel": "extendedfeedbacks",
|
| 41 |
-
"ScenarioChangeModel": "scenariochanges",
|
| 42 |
-
"WaitlistModel": "waitlists",
|
| 43 |
-
"NotificationModel": "notifications",
|
| 44 |
-
"NotificationModelShorten": "notifications",
|
| 45 |
}
|
| 46 |
|
| 47 |
|
|
|
|
| 20 |
collection_map = {
|
| 21 |
"AccountModel": "accounts",
|
| 22 |
"AccountShorten": "accounts",
|
| 23 |
+
"AvailabilityModel": "availabilities",
|
| 24 |
+
"CallModel": "calls",
|
| 25 |
+
"DiscountCodeModel": "discountcodes",
|
| 26 |
+
"EventModel": "events",
|
| 27 |
+
"EventShorten": "events",
|
| 28 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
|
cbh/api/discountcode/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
-
discountcode_router = APIRouter(prefix="/discountcodes", tags=["discount-code"])
|
| 4 |
|
| 5 |
from . import views
|
|
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
+
discountcode_router = APIRouter(prefix="/api/discountcodes", tags=["discount-code"])
|
| 4 |
|
| 5 |
from . import views
|
cbh/api/events/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
-
events_router = APIRouter(prefix="/events", tags=["events"])
|
| 4 |
|
| 5 |
from . import views
|
|
|
|
| 1 |
from fastapi import APIRouter
|
| 2 |
|
| 3 |
+
events_router = APIRouter(prefix="/api/events", tags=["events"])
|
| 4 |
|
| 5 |
from . import views
|
cbh/api/security/db_requests.py
CHANGED
|
@@ -2,13 +2,15 @@
|
|
| 2 |
Database requests module for security functionality.
|
| 3 |
"""
|
| 4 |
|
|
|
|
| 5 |
from datetime import datetime
|
| 6 |
|
| 7 |
from fastapi import HTTPException
|
| 8 |
-
from passlib.context import CryptContext
|
| 9 |
from pydantic import EmailStr
|
| 10 |
|
|
|
|
| 11 |
from cbh.api.account.models import AccountModel, AccountShorten
|
|
|
|
| 12 |
from cbh.api.security.schemas import (
|
| 13 |
LoginAccountRequest,
|
| 14 |
RegisterAccountRequest,
|
|
@@ -38,17 +40,35 @@ async def save_account(data: RegisterAccountRequest) -> AccountModel:
|
|
| 38 |
Create a new user account in the database.
|
| 39 |
"""
|
| 40 |
await check_unique_fields_existence("email", data.email)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
account = AccountModel(
|
| 42 |
name=f"{data.firstName} {data.lastName}",
|
| 43 |
email=data.email,
|
| 44 |
password=data.password,
|
| 45 |
datetimeUpdated=datetime.now(),
|
| 46 |
accountType=data.accountType,
|
|
|
|
| 47 |
)
|
| 48 |
await settings.DB_CLIENT.accounts.insert_one(account.to_mongo())
|
|
|
|
|
|
|
|
|
|
| 49 |
return account
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
async def authenticate_account(data: LoginAccountRequest) -> AccountModel:
|
| 53 |
"""
|
| 54 |
Authenticate a user account using mail and password.
|
|
|
|
| 2 |
Database requests module for security functionality.
|
| 3 |
"""
|
| 4 |
|
| 5 |
+
import asyncio
|
| 6 |
from datetime import datetime
|
| 7 |
|
| 8 |
from fastapi import HTTPException
|
|
|
|
| 9 |
from pydantic import EmailStr
|
| 10 |
|
| 11 |
+
from cbh.api.account.dto import AccountType, CoachOpportunity
|
| 12 |
from cbh.api.account.models import AccountModel, AccountShorten
|
| 13 |
+
from cbh.api.availability.models import AvailabilityModel
|
| 14 |
from cbh.api.security.schemas import (
|
| 15 |
LoginAccountRequest,
|
| 16 |
RegisterAccountRequest,
|
|
|
|
| 40 |
Create a new user account in the database.
|
| 41 |
"""
|
| 42 |
await check_unique_fields_existence("email", data.email)
|
| 43 |
+
|
| 44 |
+
opportunity = None
|
| 45 |
+
if data.accountType == AccountType.COACH:
|
| 46 |
+
opportunity = CoachOpportunity.GENERAL
|
| 47 |
+
|
| 48 |
account = AccountModel(
|
| 49 |
name=f"{data.firstName} {data.lastName}",
|
| 50 |
email=data.email,
|
| 51 |
password=data.password,
|
| 52 |
datetimeUpdated=datetime.now(),
|
| 53 |
accountType=data.accountType,
|
| 54 |
+
opportunity=opportunity,
|
| 55 |
)
|
| 56 |
await settings.DB_CLIENT.accounts.insert_one(account.to_mongo())
|
| 57 |
+
|
| 58 |
+
if account.accountType == AccountType.COACH:
|
| 59 |
+
asyncio.create_task(create_availability_obj(account))
|
| 60 |
return account
|
| 61 |
|
| 62 |
|
| 63 |
+
async def create_availability_obj(account: AccountModel) -> AvailabilityModel:
|
| 64 |
+
"""
|
| 65 |
+
Create a new availability object.
|
| 66 |
+
"""
|
| 67 |
+
availability = AvailabilityModel(coach=AccountShorten(**account.to_mongo()))
|
| 68 |
+
await settings.DB_CLIENT.availabilities.insert_one(availability.to_mongo())
|
| 69 |
+
return availability
|
| 70 |
+
|
| 71 |
+
|
| 72 |
async def authenticate_account(data: LoginAccountRequest) -> AccountModel:
|
| 73 |
"""
|
| 74 |
Authenticate a user account using mail and password.
|
cbh/core/config.py
CHANGED
|
@@ -30,6 +30,7 @@ class BaseConfig:
|
|
| 30 |
).spark
|
| 31 |
|
| 32 |
STRIPE_CLIENT = StripeClient(api_key=os.getenv("STRIPE_API_KEY"))
|
|
|
|
| 33 |
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
|
| 34 |
|
| 35 |
@staticmethod
|
|
@@ -78,9 +79,8 @@ class DevelopmentConfig(BaseConfig):
|
|
| 78 |
Development environment configuration settings.
|
| 79 |
"""
|
| 80 |
|
| 81 |
-
Issuer = "
|
| 82 |
-
Audience = "
|
| 83 |
-
Domain = "trainwitharena.com"
|
| 84 |
|
| 85 |
|
| 86 |
class ProductionConfig(BaseConfig):
|
|
@@ -88,9 +88,8 @@ class ProductionConfig(BaseConfig):
|
|
| 88 |
Production environment configuration settings.
|
| 89 |
"""
|
| 90 |
|
| 91 |
-
Issuer = "https://
|
| 92 |
-
Audience = "
|
| 93 |
-
Domain = "trainwitharena.com"
|
| 94 |
|
| 95 |
|
| 96 |
@lru_cache()
|
|
|
|
| 30 |
).spark
|
| 31 |
|
| 32 |
STRIPE_CLIENT = StripeClient(api_key=os.getenv("STRIPE_API_KEY"))
|
| 33 |
+
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
|
| 34 |
STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET")
|
| 35 |
|
| 36 |
@staticmethod
|
|
|
|
| 79 |
Development environment configuration settings.
|
| 80 |
"""
|
| 81 |
|
| 82 |
+
Issuer = "http://localhost:8000"
|
| 83 |
+
Audience = "http://localhost:3000"
|
|
|
|
| 84 |
|
| 85 |
|
| 86 |
class ProductionConfig(BaseConfig):
|
|
|
|
| 88 |
Production environment configuration settings.
|
| 89 |
"""
|
| 90 |
|
| 91 |
+
Issuer = "https://brestok-spark.hf.space"
|
| 92 |
+
Audience = "http://localhost:3000"
|
|
|
|
| 93 |
|
| 94 |
|
| 95 |
@lru_cache()
|
cbh/core/database.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
Database utilities for ClipboardHealthAI application.
|
| 3 |
"""
|
| 4 |
|
| 5 |
-
from datetime import datetime
|
| 6 |
from enum import Enum
|
| 7 |
import re
|
| 8 |
from typing import Any, Dict, Type, TypeVar
|
|
@@ -122,7 +122,7 @@ class MongoBaseModel(BaseModel):
|
|
| 122 |
doc[key] = [model_to_dict(item) for item in value] # type: ignore
|
| 123 |
elif value and isinstance(value, Enum):
|
| 124 |
doc[key] = value.value
|
| 125 |
-
elif isinstance(value, datetime):
|
| 126 |
doc[key] = value.isoformat() # type: ignore
|
| 127 |
elif value and isinstance(value, AnyUrl):
|
| 128 |
doc[key] = str(value) # type: ignore
|
|
|
|
| 2 |
Database utilities for ClipboardHealthAI application.
|
| 3 |
"""
|
| 4 |
|
| 5 |
+
from datetime import datetime, time, date
|
| 6 |
from enum import Enum
|
| 7 |
import re
|
| 8 |
from typing import Any, Dict, Type, TypeVar
|
|
|
|
| 122 |
doc[key] = [model_to_dict(item) for item in value] # type: ignore
|
| 123 |
elif value and isinstance(value, Enum):
|
| 124 |
doc[key] = value.value
|
| 125 |
+
elif isinstance(value, (datetime, time, date)):
|
| 126 |
doc[key] = value.isoformat() # type: ignore
|
| 127 |
elif value and isinstance(value, AnyUrl):
|
| 128 |
doc[key] = str(value) # type: ignore
|
requirements.txt
CHANGED
|
@@ -1,77 +1,135 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
| 3 |
annotated-types==0.7.0
|
| 4 |
-
anyio==4.
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
distro==1.9.0
|
| 10 |
-
dnspython==2.
|
| 11 |
-
ecdsa==0.19.
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
httpx==0.28.1
|
| 18 |
-
idna==3.
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
jsonpatch==1.33
|
| 21 |
jsonpointer==3.0.0
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
langgraph-checkpoint==3.0.1
|
| 30 |
-
langgraph-prebuilt==1.0.5
|
| 31 |
-
langgraph-sdk==0.3.2
|
| 32 |
-
langsmith==0.6.2
|
| 33 |
-
lark==1.3.1
|
| 34 |
motor==3.7.1
|
|
|
|
|
|
|
| 35 |
mypy_extensions==1.1.0
|
| 36 |
-
numpy==2.
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
passlib==1.7.4
|
| 42 |
-
pathspec==
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
pydash==8.0.5
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
requests-toolbelt==1.0.0
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
six==1.17.0
|
|
|
|
| 61 |
sniffio==1.3.1
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
| 65 |
tenacity==9.1.2
|
| 66 |
-
tiktoken==0.
|
|
|
|
| 67 |
tqdm==4.67.1
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiohappyeyeballs==2.6.1
|
| 2 |
+
aiohttp==3.11.16
|
| 3 |
+
aiosignal==1.3.2
|
| 4 |
+
aiosmtplib==3.0.2
|
| 5 |
annotated-types==0.7.0
|
| 6 |
+
anyio==4.8.0
|
| 7 |
+
APScheduler==3.11.0
|
| 8 |
+
asn1crypto==1.5.1
|
| 9 |
+
assemblyai==0.40.2
|
| 10 |
+
astroid==3.3.10
|
| 11 |
+
attrs==25.3.0
|
| 12 |
+
bandit==1.8.3
|
| 13 |
+
bcrypt==4.2.1
|
| 14 |
+
beautifulsoup4==4.13.4
|
| 15 |
+
black==24.3.0
|
| 16 |
+
boto3==1.35.97
|
| 17 |
+
botocore==1.35.99
|
| 18 |
+
cachetools==6.2.0
|
| 19 |
+
certifi==2025.1.31
|
| 20 |
+
cffi==1.17.1
|
| 21 |
+
chardet==5.2.0
|
| 22 |
+
charset-normalizer==3.4.1
|
| 23 |
+
click==8.1.8
|
| 24 |
+
cryptography==44.0.1
|
| 25 |
+
cssselect==1.3.0
|
| 26 |
+
cssutils==2.11.1
|
| 27 |
+
dateutils==0.6.12
|
| 28 |
+
dill==0.4.0
|
| 29 |
distro==1.9.0
|
| 30 |
+
dnspython==2.7.0
|
| 31 |
+
ecdsa==0.19.0
|
| 32 |
+
email_validator==2.2.0
|
| 33 |
+
emails==0.6
|
| 34 |
+
fastapi==0.115.8
|
| 35 |
+
filelock==3.17.0
|
| 36 |
+
flake8==7.0.0
|
| 37 |
+
frozenlist==1.5.0
|
| 38 |
+
git-filter-repo==2.47.0
|
| 39 |
+
h11==0.14.0
|
| 40 |
+
html2text==2025.4.15
|
| 41 |
+
httpcore==1.0.7
|
| 42 |
+
httptools==0.6.4
|
| 43 |
httpx==0.28.1
|
| 44 |
+
idna==3.10
|
| 45 |
+
isodate==0.7.2
|
| 46 |
+
isort==6.0.1
|
| 47 |
+
Jinja2==3.1.6
|
| 48 |
+
jiter==0.8.2
|
| 49 |
+
jmespath==1.0.1
|
| 50 |
jsonpatch==1.33
|
| 51 |
jsonpointer==3.0.0
|
| 52 |
+
lark==1.2.2
|
| 53 |
+
lxml==6.0.0
|
| 54 |
+
markdown-it-py==3.0.0
|
| 55 |
+
MarkupSafe==3.0.2
|
| 56 |
+
mccabe==0.7.0
|
| 57 |
+
mdurl==0.1.2
|
| 58 |
+
more-itertools==10.7.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
motor==3.7.1
|
| 60 |
+
multidict==6.4.3
|
| 61 |
+
mypy==1.15.0
|
| 62 |
mypy_extensions==1.1.0
|
| 63 |
+
numpy==2.2.3
|
| 64 |
+
orjson==3.10.16
|
| 65 |
+
packaging==24.2
|
| 66 |
+
pandas==2.2.3
|
| 67 |
+
pandas-stubs==2.2.3.250308
|
| 68 |
passlib==1.7.4
|
| 69 |
+
pathspec==0.12.1
|
| 70 |
+
pbr==6.1.1
|
| 71 |
+
pillow==11.3.0
|
| 72 |
+
platformdirs==4.3.6
|
| 73 |
+
premailer==3.10.0
|
| 74 |
+
propcache==0.3.1
|
| 75 |
+
pyasn1==0.4.8
|
| 76 |
+
pycodestyle==2.11.1
|
| 77 |
+
pycparser==2.22
|
| 78 |
+
pydantic==2.11.7
|
| 79 |
+
pydantic-mongo==3.1.0
|
| 80 |
+
pydantic_core==2.33.2
|
| 81 |
pydash==8.0.5
|
| 82 |
+
pydub==0.25.1
|
| 83 |
+
pyflakes==3.2.0
|
| 84 |
+
Pygments==2.19.1
|
| 85 |
+
PyJWT==2.10.1
|
| 86 |
+
pylint==3.3.7
|
| 87 |
+
pymongo==4.15.2
|
| 88 |
+
pyOpenSSL==24.3.0
|
| 89 |
+
python-dateutil==2.9.0.post0
|
| 90 |
+
python-dotenv==1.0.1
|
| 91 |
+
python-jose==3.4.0
|
| 92 |
+
python-multipart==0.0.20
|
| 93 |
+
pytz==2025.1
|
| 94 |
+
PyYAML==6.0.2
|
| 95 |
+
regex==2024.11.6
|
| 96 |
+
requests==2.32.3
|
| 97 |
+
requests-file==2.1.0
|
| 98 |
requests-toolbelt==1.0.0
|
| 99 |
+
rich==14.0.0
|
| 100 |
+
rsa==4.9
|
| 101 |
+
ruff==0.11.9
|
| 102 |
+
s3transfer==0.10.4
|
| 103 |
+
setuptools==80.8.0
|
| 104 |
+
simple-salesforce==1.12.6
|
| 105 |
six==1.17.0
|
| 106 |
+
slack_sdk==3.35.0
|
| 107 |
sniffio==1.3.1
|
| 108 |
+
snowflake-connector-python==3.13.2
|
| 109 |
+
sortedcontainers==2.4.0
|
| 110 |
+
soupsieve==2.7
|
| 111 |
+
SQLAlchemy==2.0.40
|
| 112 |
+
starlette==0.45.3
|
| 113 |
+
stevedore==5.4.1
|
| 114 |
tenacity==9.1.2
|
| 115 |
+
tiktoken==0.9.0
|
| 116 |
+
tomlkit==0.13.2
|
| 117 |
tqdm==4.67.1
|
| 118 |
+
types-passlib==1.7.7.20250408
|
| 119 |
+
types-pyasn1==0.6.0.20250208
|
| 120 |
+
types-python-dateutil==2.9.0.20241206
|
| 121 |
+
types-python-jose==3.4.0.20250224
|
| 122 |
+
types-pytz==2025.2.0.20250326
|
| 123 |
+
types-requests==2.32.0.20250328
|
| 124 |
+
typing-inspection==0.4.0
|
| 125 |
+
typing_extensions==4.12.2
|
| 126 |
+
tzdata==2025.1
|
| 127 |
+
tzlocal==5.3.1
|
| 128 |
+
urllib3==2.3.0
|
| 129 |
+
uvicorn==0.34.0
|
| 130 |
+
uvloop==0.21.0
|
| 131 |
+
watchfiles==1.0.4
|
| 132 |
+
websockets==14.2
|
| 133 |
+
yarl==1.19.0
|
| 134 |
+
zeep==4.3.1
|
| 135 |
+
zstandard==0.23.0
|