Skip to content

Commit

Permalink
Major clean up + Use attendee names for zoom meeting
Browse files Browse the repository at this point in the history
  • Loading branch information
MelissaAutumn committed May 23, 2024
1 parent 501e543 commit 0c79983
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 247 deletions.
4 changes: 3 additions & 1 deletion backend/src/appointment/database/repo/slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ def get_by_subscriber(db: Session, subscriber_id: int):

def add_for_appointment(db: Session, slots: list[schemas.SlotBase], appointment_id: int):
"""create new slots for appointment of given id"""
return_slots = []
for slot in slots:
db_slot = models.Slot(**slot.dict())
db_slot.appointment_id = appointment_id
db.add(db_slot)
return_slots.append(db_slot)
db.commit()
return slots
return return_slots


def add_for_schedule(db: Session, slot: schemas.SlotBase, schedule_id: int):
Expand Down
115 changes: 0 additions & 115 deletions backend/src/appointment/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,121 +437,6 @@ def read_public_appointment(slug: str, db: Session = Depends(get_db)):
)


@router.put("/apmt/public/{slug}", response_model=schemas.SlotAttendee, deprecated=True)
def update_public_appointment_slot(
slug: str,
s_a: schemas.SlotAttendee,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
google_client: GoogleClient = Depends(get_google_client),
):
"""endpoint to update a time slot for an appointment via public link and create an event in remote calendar"""
db_appointment = repo.appointment.get_public(db, slug=slug)
if db_appointment is None:
raise validation.AppointmentNotFoundException()
db_calendar = repo.calendar.get(db, calendar_id=db_appointment.calendar_id)
if db_calendar is None:
raise validation.CalendarNotFoundException()
if not repo.appointment.has_slot(db, appointment_id=db_appointment.id, slot_id=s_a.slot_id):
raise validation.SlotNotFoundException()
if not repo.slot.is_available(db, slot_id=s_a.slot_id):
raise validation.SlotAlreadyTakenException()
if not validators.email(s_a.attendee.email):
raise HTTPException(status_code=400, detail=l10n('slot-invalid-email'))

slot = repo.slot.get(db=db, slot_id=s_a.slot_id)

# grab the subscriber
organizer = repo.subscriber.get_by_appointment(db=db, appointment_id=db_appointment.id)

location_url = db_appointment.location_url

if db_appointment.meeting_link_provider == MeetingLinkProviderType.zoom:
try:
zoom_client = get_zoom_client(organizer)
response = zoom_client.create_meeting(db_appointment.title, slot.start.isoformat(), slot.duration,
organizer.timezone)
if 'id' in response:
slot.meeting_link_url = zoom_client.get_meeting(response['id'])['join_url']
slot.meeting_link_id = response['id']

location_url = slot.meeting_link_url

# TODO: If we move to a model-based db functions replace this with a .save()
# Save the updated slot information
db.add(slot)
db.commit()
except HTTPError as err: # Not fatal, just a bummer
logging.error("Zoom meeting creation error: ", err)

# Ensure sentry captures the error too!
if os.getenv('SENTRY_DSN') != '':
capture_exception(err)

# Notify the organizer that the meeting link could not be created!
background_tasks.add_task(send_zoom_meeting_failed_email, to=organizer.email,
appointment=db_appointment.title)

except SQLAlchemyError as err: # Not fatal, but could make things tricky
logging.error("Failed to save the zoom meeting link to the appointment: ", err)
if os.getenv('SENTRY_DSN') != '':
capture_exception(err)

event = schemas.Event(
title=db_appointment.title,
start=slot.start.replace(tzinfo=timezone.utc),
end=slot.start.replace(tzinfo=timezone.utc) + timedelta(minutes=slot.duration),
description=db_appointment.details,
location=schemas.EventLocation(
type=db_appointment.location_type,
suggestions=db_appointment.location_suggestions,
selected=db_appointment.location_selected,
name=db_appointment.location_name,
url=location_url,
phone=db_appointment.location_phone,
),
)

organizer_email = organizer.email

# create remote event
if db_calendar.provider == CalendarProvider.google:
external_connection = utils.list_first(repo.external_connection.get_by_type(db, organizer.id, schemas.ExternalConnectionType.google))

if external_connection is None or external_connection.token is None:
raise RemoteCalendarConnectionError()

organizer_email = external_connection.name

con = GoogleConnector(
db=db,
redis_instance=None,
google_client=google_client,
remote_calendar_id=db_calendar.user,
calendar_id=db_calendar.id,
subscriber_id=organizer.id,
google_tkn=external_connection.token,
)
else:
con = CalDavConnector(
redis_instance=None,
url=db_calendar.url,
user=db_calendar.user,
password=db_calendar.password,
subscriber_id=organizer.id,
calendar_id=db_calendar.id,
)
con.create_event(event=event, attendee=s_a.attendee, organizer=organizer, organizer_email=organizer_email)

# update appointment slot data
repo.slot.update(db=db, slot_id=s_a.slot_id, attendee=s_a.attendee)

# send mail with .ics attachment to attendee
Tools().send_vevent(background_tasks, db_appointment, slot, organizer, s_a.attendee)

return schemas.SlotAttendee(slot_id=s_a.slot_id, attendee=s_a.attendee)


@router.get("/apmt/serve/ics/{slug}/{slot_id}", response_model=schemas.FileDownload)
def public_appointment_serve_ics(slug: str, slot_id: int, db: Session = Depends(get_db)):
"""endpoint to serve ICS file for time slot to download"""
Expand Down
181 changes: 96 additions & 85 deletions backend/src/appointment/routes/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ def decide_on_schedule_availability_slot(
):
"""endpoint to react to owners decision to a request of a time slot of his public link
if confirmed: create an event in remote calendar and send invitation mail
TODO: if denied: send information mail to bookee
"""
subscriber = repo.subscriber.verify_link(db, data.owner_url)
if not subscriber:
Expand Down Expand Up @@ -322,6 +321,7 @@ def decide_on_schedule_availability_slot(
date = f"{date}, {slot.duration} minutes"
# send rejection information to bookee
background_tasks.add_task(send_rejection_email, owner_name=subscriber.name, date=date, to=slot.attendee.email)
repo.slot.delete(db, slot.id)

if slot.appointment_id:
# delete the appointment, this will also delete the slot.
Expand All @@ -330,100 +330,111 @@ def decide_on_schedule_availability_slot(
# delete the scheduled slot to make the time available again
repo.slot.delete(db, slot.id)

# Early return
return schemas.AvailabilitySlotAttendee(
slot=schemas.SlotBase(start=slot.start, duration=slot.duration),
attendee=schemas.AttendeeBase(
email=slot.attendee.email,
name=slot.attendee.name,
timezone=slot.attendee.timezone
)
)

# otherwise, confirm slot and create event
location_url = schedule.location_url

attendee_name = slot.attendee.name if slot.attendee.name is not None else slot.attendee.email
subscriber_name = subscriber.name if subscriber.name is not None else subscriber.email

attendees = f"{subscriber_name} and {attendee_name}"

if not slot.appointment:
title = f"Appointment - {attendees}"
else:
location_url = schedule.location_url

# FIXME: This is just duplicated from the appointment code. We should find a nice way to merge the two.
if schedule.meeting_link_provider == MeetingLinkProviderType.zoom:
try:
zoom_client = get_zoom_client(subscriber)
response = zoom_client.create_meeting(schedule.name, slot.start.isoformat(), slot.duration,
subscriber.timezone)
if 'id' in response:
location_url = zoom_client.get_meeting(response['id'])['join_url']
slot.meeting_link_id = response['id']
slot.meeting_link_url = location_url

db.add(slot)
db.commit()
except HTTPError as err: # Not fatal, just a bummer
logging.error("Zoom meeting creation error: ", err)

# Ensure sentry captures the error too!
if os.getenv('SENTRY_DSN') != '':
capture_exception(err)

# Notify the organizer that the meeting link could not be created!
background_tasks.add_task(send_zoom_meeting_failed_email, to=subscriber.email, appointment_title=schedule.name)
except SQLAlchemyError as err: # Not fatal, but could make things tricky
logging.error("Failed to save the zoom meeting link to the appointment: ", err)
if os.getenv('SENTRY_DSN') != '':
capture_exception(err)

if not slot.appointment:
attendee_name = slot.attendee.name if slot.attendee.name is not None else slot.attendee.email
subscriber_name = subscriber.name if subscriber.name is not None else subscriber.email

title = f"Appointment - {subscriber_name} and {attendee_name}"
else:
title = slot.appointment.title
# Update the appointment to closed
repo.appointment.update_status(db, slot.appointment_id, models.AppointmentStatus.closed)

event = schemas.Event(
title=title,
start=slot.start.replace(tzinfo=timezone.utc),
end=slot.start.replace(tzinfo=timezone.utc) + timedelta(minutes=slot.duration),
description=schedule.details,
location=schemas.EventLocation(
type=schedule.location_type,
url=location_url,
name=None,
),
uuid=slot.appointment.uuid if slot.appointment else None
)
title = slot.appointment.title
# Update the appointment to closed
repo.appointment.update_status(db, slot.appointment_id, models.AppointmentStatus.closed)

organizer_email = subscriber.email
# If needed: Create a zoom meeting link for this booking
if schedule.meeting_link_provider == MeetingLinkProviderType.zoom:
try:
zoom_client = get_zoom_client(subscriber)
response = zoom_client.create_meeting(attendees, slot.start.isoformat(), slot.duration,
subscriber.timezone)
if 'id' in response:
location_url = zoom_client.get_meeting(response['id'])['join_url']
slot.meeting_link_id = response['id']
slot.meeting_link_url = location_url

db.add(slot)
db.commit()
except HTTPError as err: # Not fatal, just a bummer
logging.error("Zoom meeting creation error: ", err)

# Ensure sentry captures the error too!
if os.getenv('SENTRY_DSN') != '':
capture_exception(err)

# Notify the organizer that the meeting link could not be created!
background_tasks.add_task(send_zoom_meeting_failed_email, to=subscriber.email, appointment_title=schedule.name)
except SQLAlchemyError as err: # Not fatal, but could make things tricky
logging.error("Failed to save the zoom meeting link to the appointment: ", err)
if os.getenv('SENTRY_DSN') != '':
capture_exception(err)

event = schemas.Event(
title=title,
start=slot.start.replace(tzinfo=timezone.utc),
end=slot.start.replace(tzinfo=timezone.utc) + timedelta(minutes=slot.duration),
description=schedule.details,
location=schemas.EventLocation(
type=schedule.location_type,
url=location_url,
name=None,
),
uuid=slot.appointment.uuid if slot.appointment else None
)

# create remote event
if calendar.provider == CalendarProvider.google:
external_connection: ExternalConnection|None = utils.list_first(repo.external_connection.get_by_type(db, subscriber.id, schemas.ExternalConnectionType.google))
organizer_email = subscriber.email

if external_connection is None or external_connection.token is None:
raise RemoteCalendarConnectionError()
# create remote event
if calendar.provider == CalendarProvider.google:
external_connection: ExternalConnection|None = utils.list_first(repo.external_connection.get_by_type(db, subscriber.id, schemas.ExternalConnectionType.google))

# Email is stored in the name
organizer_email = external_connection.name
if external_connection is None or external_connection.token is None:
raise RemoteCalendarConnectionError()

con = GoogleConnector(
db=db,
redis_instance=redis,
google_client=google_client,
remote_calendar_id=calendar.user,
subscriber_id=subscriber.id,
calendar_id=calendar.id,
google_tkn=external_connection.token,
)
else:
con = CalDavConnector(
redis_instance=redis,
subscriber_id=subscriber.id,
calendar_id=calendar.id,
url=calendar.url,
user=calendar.user,
password=calendar.password
)
# Email is stored in the name
organizer_email = external_connection.name

try:
con.create_event(event=event, attendee=slot.attendee, organizer=subscriber, organizer_email=organizer_email)
except EventNotCreatedException:
raise EventCouldNotBeAccepted
con = GoogleConnector(
db=db,
redis_instance=redis,
google_client=google_client,
remote_calendar_id=calendar.user,
subscriber_id=subscriber.id,
calendar_id=calendar.id,
google_tkn=external_connection.token,
)
else:
con = CalDavConnector(
redis_instance=redis,
subscriber_id=subscriber.id,
calendar_id=calendar.id,
url=calendar.url,
user=calendar.user,
password=calendar.password
)

try:
con.create_event(event=event, attendee=slot.attendee, organizer=subscriber, organizer_email=organizer_email)
except EventNotCreatedException:
raise EventCouldNotBeAccepted

# Book the slot at the end
slot = repo.slot.book(db, slot.id)
# Book the slot at the end
slot = repo.slot.book(db, slot.id)

Tools().send_vevent(background_tasks, slot.appointment, slot, subscriber, slot.attendee)
Tools().send_vevent(background_tasks, slot.appointment, slot, subscriber, slot.attendee)

return schemas.AvailabilitySlotAttendee(
slot=schemas.SlotBase(start=slot.start, duration=slot.duration),
Expand Down
2 changes: 1 addition & 1 deletion backend/test/factory/slot_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def _make_appointment_slot(appointment_id=None,
booking_expires_at=booking_expires_at,
booking_status=booking_status,
meeting_link_id=meeting_link_id,
meeting_link_url=meeting_link_url
meeting_link_url=meeting_link_url,
)], appointment_id)

return _make_appointment_slot
Loading

0 comments on commit 0c79983

Please sign in to comment.