From 67b8e4125c88b168dc67a75de72d86220b3084fb Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 14 Jul 2026 14:45:00 +0930 Subject: [PATCH 1/4] feat(events): skip notification on attendee only changes (PPT-2514) --- spec/controllers/events_spec.cr | 60 +++++++++++++++++++++++++++++++++ src/controllers/events.cr | 12 +++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/spec/controllers/events_spec.cr b/spec/controllers/events_spec.cr index 0d8d8353..5ab1ca9b 100644 --- a/spec/controllers/events_spec.cr +++ b/spec/controllers/events_spec.cr @@ -524,6 +524,66 @@ describe Events, tags: ["event"] do ]).size.should eq(0) end + it "#add_attendee only emails new attendees by default and notifies everyone when notify_existing_attendees=true" do + WebMock.stub(:post, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/events") + .to_return(body: File.read("./spec/fixtures/events/o365/create.json")) + + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/room1%40example.com/calendar/events/AAMkADE3YmQxMGQ2LTRmZDgtNDljYy1hNDg1LWM0NzFmMGI0ZTQ3YgBGAAAAAADFYQb3DJ_xSJHh14kbXHWhBwB08dwEuoS_QYSBDzuv558sAAAAAAENAAB08dwEuoS_QYSBDzuv558sAACGVOwUAAA%3D") + .to_return(body: File.read("./spec/fixtures/events/o365/create.json")) + + captured_patch_bodies = [] of String + WebMock.stub(:patch, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/events/AAMkADE3YmQxMGQ2LTRmZDgtNDljYy1hNDg1LWM0NzFmMGI0ZTQ3YgBGAAAAAADFYQb3DJ_xSJHh14kbXHWhBwB08dwEuoS_QYSBDzuv558sAAAAAAENAAB08dwEuoS_QYSBDzuv558sAACGVOwUAAA%3D") + .to_return do |request| + captured_patch_bodies << (request.body.try(&.gets_to_end) || "") + HTTP::Client::Response.new(200, body: File.read("./spec/fixtures/events/o365/update.json")) + end + + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.com/calendars") + .to_return(body: File.read("./spec/fixtures/calendars/o365/show.json")) + + # Stub getting the host event + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/calendarView?startDateTime=2020-08-26T14%3A00%3A00-00%3A00&endDateTime=2020-08-27T13%3A59%3A59-00%3A00&%24filter=iCalUId+eq+%27040000008200E00074C5B7101A82E008000000006DE2E3761F8AD6010000000000000000100000009CCCDBB1F09DE74D8B157797D97F6A10%27&%24top=10000") + .to_return(body: File.read("./spec/fixtures/events/o365/events_query.json")) + + req_body = EventsHelper.create_event_input(permission: PlaceOS::Model::EventMetadata::Permission::PUBLIC) + + event = JSON.parse(client.post(EVENTS_BASE, headers: headers, body: req_body).body).as_h + event_id = event["id"].to_s + + WebMock.stub(:get, "https://graph.microsoft.com/v1.0/users/room1%40example.com/calendar/calendarView?startDateTime=2020-08-26T14:00:00-00:00&endDateTime=2020-08-27T13:59:59-00:00&%24filter=iCalUId+eq+%27040000008200E00074C5B7101A82E008000000006DE2E3761F8AD6010000000000000000100000009CCCDBB1F09DE74D8B157797D97F6A10%27&$top=10000") + .to_return(EventsHelper.event_query_response(event_id)) + + system_id = "sys-rJQQlR4Cn7" + EventsHelper.stub_permissions_check(system_id) + + # Default: existing attendees are not notified, so only the attendees + # property is sent upstream. + response = client.post(%(#{EVENTS_BASE}/#{event_id}/attendee?system_id=#{system_id}), headers: Mock::Headers.office365_no_auth, body: { + name: "User Two", + email: "user-two@example.com", + checked_in: true, + visit_expected: true, + }.to_json) + response.status_code.should eq(200) + + captured_patch_bodies.size.should eq(1) + JSON.parse(captured_patch_bodies[0]).as_h.keys.should eq(["attendees"]) + + # Opt-in: notify existing attendees -> full event body sent upstream. + response = client.post(%(#{EVENTS_BASE}/#{event_id}/attendee?system_id=#{system_id}¬ify_existing_attendees=true), headers: Mock::Headers.office365_no_auth, body: { + name: "User Three", + email: "user-three@example.com", + checked_in: true, + visit_expected: true, + }.to_json) + response.status_code.should eq(200) + + captured_patch_bodies.size.should eq(2) + full_body = JSON.parse(captured_patch_bodies[1]).as_h + full_body.has_key?("attendees").should be_true + full_body.keys.size.should be > 1 + end + it "#delete_attendee should ONLY allow removing self from PRIVATE events" do WebMock.stub(:post, "https://graph.microsoft.com/v1.0/users/dev%40acaprojects.onmicrosoft.com/calendar/events") .to_return(body: File.read("./spec/fixtures/events/o365/create.json")) diff --git a/src/controllers/events.cr b/src/controllers/events.cr index c0dee3fe..7e9e5e58 100644 --- a/src/controllers/events.cr +++ b/src/controllers/events.cr @@ -564,6 +564,8 @@ class Events < Application associated_system : String? = nil, @[AC::Param::Info(name: "calendar", description: "the calendar associated with this event id", example: "user@org.com")] user_cal : String? = nil, + @[AC::Param::Info(description: "(office365 only) set false when the only change is adding attendees to avoid emailing existing attendees; note that when false only the attendee changes are sent upstream", example: "false")] + notify_existing_attendees : Bool = true, ) : PlaceCalendar::Event changes.id = event_id = original_id system_id = (associated_system || changes.system_id).presence @@ -690,7 +692,7 @@ class Events < Application end end - updated_event = client.update_event(user_id: host, event: changes, calendar_id: host) + updated_event = client.update_event(user_id: host, event: changes, calendar_id: host, notify_existing_attendees: notify_existing_attendees) raise Error::BadUpstreamResponse.new("failed to update event #{event_id} as #{host}") unless updated_event if system @@ -876,6 +878,8 @@ class Events < Application system_id : String? = nil, @[AC::Param::Info(name: "calendar", description: "the calendar associated with this event id", example: "user@org.com")] user_cal : String? = nil, + @[AC::Param::Info(description: "(office365 only) when true, existing attendees are also emailed about the change, otherwise only the new attendee is notified", example: "false")] + notify_existing_attendees : Bool = false, ) : Attendee | PlaceCalendar::Event::Attendee placeos_client = get_placeos_client event_id = original_id @@ -929,8 +933,10 @@ class Events < Application # Add the new attendee to the event event.attendees = (event.attendees || [] of PlaceCalendar::Event::Attendee) << attendee - # Update the event with the new attendee - updated_event = client.update_event(user_id: host, event: event, calendar_id: host) + # Update the event with the new attendee. When notify_existing_attendees is + # false (the default), office365 only emails the newly added attendee rather + # than spamming everyone already on the booking. + updated_event = client.update_event(user_id: host, event: event, calendar_id: host, notify_existing_attendees: notify_existing_attendees) raise Error::BadUpstreamResponse.new("failed to update event #{event_id} as #{host}") unless updated_event if system_id From 07a7a37712e7988b0a630fa50ef26c081df4e03a Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 14 Jul 2026 14:46:20 +0930 Subject: [PATCH 2/4] chore(shard.lock): shards update --- shard.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shard.lock b/shard.lock index af22476d..538cbb16 100644 --- a/shard.lock +++ b/shard.lock @@ -139,7 +139,7 @@ shards: place_calendar: git: https://github.com/placeos/calendar.git - version: 4.29.4 + version: 4.30.0 placeos: git: https://github.com/placeos/crystal-client.git @@ -167,7 +167,7 @@ shards: tasker: git: https://github.com/spider-gazelle/tasker.git - version: 3.0.1 + version: 3.1.0 timecop: git: https://github.com/crystal-community/timecop.cr.git From 695cba27983b3b8a36dbb4a611cf8d265023cf38 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 14 Jul 2026 14:48:04 +0930 Subject: [PATCH 3/4] docs(OPENAPI_DOC.yml): generate openapi docs --- OPENAPI_DOC.yml | 169 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/OPENAPI_DOC.yml b/OPENAPI_DOC.yml index a2db8cf4..62444416 100644 --- a/OPENAPI_DOC.yml +++ b/OPENAPI_DOC.yml @@ -4747,6 +4747,14 @@ paths: schema: type: string nullable: true + - name: notify_existing_attendees + in: query + description: (office365 only) set false when the only change is adding attendees + to avoid emailing existing attendees; note that when false only the attendee + changes are sent upstream + example: "false" + schema: + type: boolean responses: 200: description: OK @@ -4983,6 +4991,14 @@ paths: schema: type: string nullable: true + - name: notify_existing_attendees + in: query + description: (office365 only) set false when the only change is adding attendees + to avoid emailing existing attendees; note that when false only the attendee + changes are sent upstream + example: "false" + schema: + type: boolean responses: 200: description: OK @@ -5086,6 +5102,13 @@ paths: schema: type: string nullable: true + - name: notify_existing_attendees + in: query + description: (office365 only) when true, existing attendees are also emailed + about the change, otherwise only the new attendee is notified + example: "false" + schema: + type: boolean responses: 200: description: OK @@ -12041,6 +12064,7 @@ components: type: integer format: Int64 nullable: true + required: [] String: type: string _Array_NamedTuple_asset_id__String__booking_start__Int64__booking_end__Int64_____Array_String__: @@ -12102,6 +12126,7 @@ components: type: integer format: Int64 nullable: true + required: [] PlaceCalendar__Event__Attendee: type: object properties: @@ -12531,6 +12556,7 @@ components: type: integer format: Int64 nullable: true + required: [] PlaceCalendar__Calendar: type: object properties: @@ -13018,6 +13044,7 @@ components: id: type: string nullable: true + required: [] _PlaceCalendar__Event__Attendee___PlaceOS__Model__Attendee_: anyOf: - type: object @@ -13447,6 +13474,7 @@ components: type: integer format: Int64 nullable: true + required: [] JSON__Any: type: object _PlaceCalendar__Event___Nil_: @@ -13852,6 +13880,7 @@ components: type: integer format: Int64 nullable: true + required: [] _Array_NamedTuple_system_id__String__event_start__Int64__event_end__Int64_____Array_String__: type: array PlaceCalendar__Group: @@ -14058,6 +14087,7 @@ components: postalCode: type: string nullable: true + required: [] nullable: true displayName: type: string @@ -14084,6 +14114,7 @@ components: type: number format: Float64 nullable: true + required: [] nullable: true id: type: string @@ -14177,6 +14208,7 @@ components: additionalProperties: type: object nullable: true + required: [] PlaceOS__Model__Survey: type: object properties: @@ -14239,6 +14271,7 @@ components: type: integer format: Int64 nullable: true + required: [] PlaceOS__Model__Survey__Answer: type: object properties: @@ -14268,6 +14301,7 @@ components: type: integer format: Int64 nullable: true + required: [] Array_PlaceOS__Model__Survey__Answer_: type: array items: @@ -14299,6 +14333,7 @@ components: type: integer format: Int64 nullable: true + required: [] PlaceOS__Model__Survey__Invitation: type: object properties: @@ -14327,6 +14362,7 @@ components: type: integer format: Int64 nullable: true + required: [] PlaceOS__Model__Survey__Question: type: object properties: @@ -14373,6 +14409,7 @@ components: type: integer format: Int64 nullable: true + required: [] Office365__ChatMessageList: type: object properties: @@ -14426,6 +14463,7 @@ components: contentType: type: string nullable: true + required: [] chatId: type: string nullable: true @@ -14491,6 +14529,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -14512,6 +14551,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -14533,6 +14573,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -14554,7 +14595,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -14596,6 +14639,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -14617,6 +14661,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -14638,6 +14683,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -14659,7 +14705,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -14681,6 +14729,7 @@ components: required: - id nullable: true + required: [] nullable: true id: type: string @@ -14748,6 +14797,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -14769,6 +14819,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -14790,6 +14841,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -14811,7 +14863,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -14867,6 +14921,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -14888,6 +14943,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -14909,6 +14965,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -14930,7 +14987,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -14972,6 +15031,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -14993,6 +15053,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15014,6 +15075,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15035,15 +15097,19 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id nullable: true + required: [] nullable: true mentionText: type: string nullable: true + required: [] nullable: true messageHistory: type: array @@ -15116,6 +15182,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15137,6 +15204,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15158,6 +15226,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15179,7 +15248,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -15221,6 +15292,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15242,6 +15314,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15263,6 +15336,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15284,7 +15358,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -15326,6 +15402,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15347,6 +15424,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15368,6 +15446,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15389,12 +15468,16 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id nullable: true + required: [] nullable: true + required: [] nullable: true required: - actions @@ -15436,6 +15519,7 @@ components: items: type: string nullable: true + required: [] nullable: true userAction: type: string @@ -15453,6 +15537,7 @@ components: - block_access - block_access_external nullable: true + required: [] nullable: true reactions: type: array @@ -15511,6 +15596,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15532,6 +15618,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15553,6 +15640,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15574,7 +15662,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -15616,6 +15706,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15637,6 +15728,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15658,6 +15750,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15679,7 +15772,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -15721,6 +15816,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15742,6 +15838,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15763,6 +15860,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15784,12 +15882,16 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id nullable: true + required: [] nullable: true + required: [] nullable: true replyToId: type: string @@ -15849,6 +15951,7 @@ components: contentType: type: string nullable: true + required: [] chatId: type: string nullable: true @@ -15914,6 +16017,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -15935,6 +16039,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -15956,6 +16061,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -15977,7 +16083,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -16019,6 +16127,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16040,6 +16149,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16061,6 +16171,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16082,7 +16193,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -16104,6 +16217,7 @@ components: required: - id nullable: true + required: [] nullable: true id: type: string @@ -16171,6 +16285,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16192,6 +16307,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16213,6 +16329,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16234,7 +16351,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -16290,6 +16409,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16311,6 +16431,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16332,6 +16453,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16353,7 +16475,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -16395,6 +16519,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16416,6 +16541,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16437,6 +16563,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16458,15 +16585,19 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id nullable: true + required: [] nullable: true mentionText: type: string nullable: true + required: [] nullable: true messageHistory: type: array @@ -16539,6 +16670,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16560,6 +16692,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16581,6 +16714,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16602,7 +16736,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -16644,6 +16780,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16665,6 +16802,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16686,6 +16824,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16707,7 +16846,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -16749,6 +16890,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16770,6 +16912,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16791,6 +16934,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16812,12 +16956,16 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id nullable: true + required: [] nullable: true + required: [] nullable: true required: - actions @@ -16859,6 +17007,7 @@ components: items: type: string nullable: true + required: [] nullable: true userAction: type: string @@ -16876,6 +17025,7 @@ components: - block_access - block_access_external nullable: true + required: [] nullable: true reactions: type: array @@ -16934,6 +17084,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -16955,6 +17106,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -16976,6 +17128,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -16997,7 +17150,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -17039,6 +17194,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -17060,6 +17216,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -17081,6 +17238,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -17102,7 +17260,9 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id @@ -17144,6 +17304,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true medium: type: object @@ -17165,6 +17326,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true small: type: object @@ -17186,6 +17348,7 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true source: type: object @@ -17207,12 +17370,16 @@ components: sourceItemId: type: string nullable: true + required: [] nullable: true + required: [] nullable: true required: - id nullable: true + required: [] nullable: true + required: [] nullable: true replyToId: type: string @@ -17286,6 +17453,7 @@ components: type: integer format: Int64 nullable: true + required: [] Hash_String__Int32_: type: object additionalProperties: @@ -17648,6 +17816,7 @@ components: type: integer format: Int64 nullable: true + required: [] nullable: true required: - error From be4ec9c5b3b90c4891f1fc6acfe938479241d7e9 Mon Sep 17 00:00:00 2001 From: Mia Bennett Date: Tue, 14 Jul 2026 15:23:56 +0930 Subject: [PATCH 4/4] doc(events): description wording (PPT-2514) --- OPENAPI_DOC.yml | 14 ++++++++------ src/controllers/events.cr | 6 ++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/OPENAPI_DOC.yml b/OPENAPI_DOC.yml index 62444416..df2bf053 100644 --- a/OPENAPI_DOC.yml +++ b/OPENAPI_DOC.yml @@ -4749,9 +4749,10 @@ paths: nullable: true - name: notify_existing_attendees in: query - description: (office365 only) set false when the only change is adding attendees - to avoid emailing existing attendees; note that when false only the attendee - changes are sent upstream + description: '(office365 only) set to false when the only change is adding + attendees, so that existing attendees aren''t notified by email. Warning: + while false, only the attendee changes are propagated to the provider - + any other edits in the request body are ignored' example: "false" schema: type: boolean @@ -4993,9 +4994,10 @@ paths: nullable: true - name: notify_existing_attendees in: query - description: (office365 only) set false when the only change is adding attendees - to avoid emailing existing attendees; note that when false only the attendee - changes are sent upstream + description: '(office365 only) set to false when the only change is adding + attendees, so that existing attendees aren''t notified by email. Warning: + while false, only the attendee changes are propagated to the provider - + any other edits in the request body are ignored' example: "false" schema: type: boolean diff --git a/src/controllers/events.cr b/src/controllers/events.cr index 7e9e5e58..e8467d8d 100644 --- a/src/controllers/events.cr +++ b/src/controllers/events.cr @@ -564,7 +564,7 @@ class Events < Application associated_system : String? = nil, @[AC::Param::Info(name: "calendar", description: "the calendar associated with this event id", example: "user@org.com")] user_cal : String? = nil, - @[AC::Param::Info(description: "(office365 only) set false when the only change is adding attendees to avoid emailing existing attendees; note that when false only the attendee changes are sent upstream", example: "false")] + @[AC::Param::Info(description: "(office365 only) set to false when the only change is adding attendees, so that existing attendees aren't notified by email. Warning: while false, only the attendee changes are propagated to the provider - any other edits in the request body are ignored", example: "false")] notify_existing_attendees : Bool = true, ) : PlaceCalendar::Event changes.id = event_id = original_id @@ -933,9 +933,7 @@ class Events < Application # Add the new attendee to the event event.attendees = (event.attendees || [] of PlaceCalendar::Event::Attendee) << attendee - # Update the event with the new attendee. When notify_existing_attendees is - # false (the default), office365 only emails the newly added attendee rather - # than spamming everyone already on the booking. + # Update the event with the new attendee. updated_event = client.update_event(user_id: host, event: event, calendar_id: host, notify_existing_attendees: notify_existing_attendees) raise Error::BadUpstreamResponse.new("failed to update event #{event_id} as #{host}") unless updated_event