Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions app/backend/src/couchers/email/calendar_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ def create_host_request_ics(
host_request: HostRequest, other_name: str, hosting: bool, loc_context: LocalizationContext
) -> str:
event = create_host_request_event(host_request, other_name, hosting, loc_context)
return event_to_ics(event, loc_context)

# METHOD:PUBLISH means this is part of a stream of calendar event information.
# It allows for later cancellation, and doesn't expose accept/decline functionality.
return event_to_ics(event, "PUBLISH", loc_context)


def create_host_request_event(
Expand All @@ -36,6 +39,9 @@ def create_host_request_event(
event = Event()
event.uid = get_host_request_event_uid(host_request.host_request_id)

# Explicitly allow later sequencing of a cancellation with SEQUENCE:1
event.extra.append(ContentLine(name="SEQUENCE", value="0"))

if hosting:
event.name = get_emails_i18next().localize(
"calendar_events.host_requests.title_host", loc_context.locale, {"name": other_name}
Expand Down Expand Up @@ -76,17 +82,20 @@ def create_host_request_cancellation_ics(
)
event.status = "CANCELLED"

# Cancellation is sequenced after creation (which defaults to sequence number 0)
# Sequence cancellation is after creation (which uses SEQUENCE:0)
event.extra.append(ContentLine(name="SEQUENCE", value="1"))

return event_to_ics(event, loc_context)
# METHOD:PUBLISH means this is part of a stream of calendar event information.
# Gmail™ will immediately remove the event from the user's calendar.
# METHOD:CANCEL might leave the event in cancelled state or not work.
return event_to_ics(event, "PUBLISH", loc_context)


def event_to_ics(event: Event, loc_context: LocalizationContext) -> str:
def event_to_ics(event: Event, method: str | None, loc_context: LocalizationContext) -> str:
# PRODID is mandatory and generally follows "-//[Organization]//[Product Name]//[Language]"
calendar = Calendar(creator=f"-//Couchers.org//Couchers//{loc_context.locale.upper()}")
if event.status == "CANCELLED":
calendar.method = "CANCEL"
if method:
calendar.method = method
calendar.events.add(event)
return cast(str, calendar.serialize())

Expand Down
2 changes: 2 additions & 0 deletions app/backend/src/tests/test_calendar_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def test_ics_content():
VERSION:2.0
PRODID:-//Couchers.org//Couchers//EN
BEGIN:VEVENT
SEQUENCE:0
DTSTART;VALUE=DATE:20000101
DTEND;VALUE=DATE:20000103
DESCRIPTION:<stripped>/42
Expand All @@ -53,6 +54,7 @@ def test_ics_content():
UID:host_request.42@<stripped>
URL:<stripped>/42
END:VEVENT
METHOD:PUBLISH
END:VCALENDAR
""".strip()
)
Expand Down
Loading