From 95599862d47caf4b389360d6908749028fcfe824 Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Sat, 28 Feb 2026 12:36:28 +0900 Subject: [PATCH 1/2] good Signed-off-by: Yujong Lee --- apps/api/openapi.gen.json | 4158 ++++++++++++++--- apps/api/src/main.rs | 7 +- crates/api-calendar/Cargo.toml | 10 +- crates/api-calendar/src/google/mod.rs | 9 + crates/api-calendar/src/google/routes.rs | 129 + crates/api-calendar/src/lib.rs | 15 +- crates/api-calendar/src/openapi.rs | 29 +- crates/api-calendar/src/outlook/mod.rs | 9 + crates/api-calendar/src/outlook/routes.rs | 115 + crates/api-calendar/src/provider.rs | 100 - crates/api-calendar/src/providers/google.rs | 169 - crates/api-calendar/src/providers/mod.rs | 2 - crates/api-calendar/src/providers/outlook.rs | 168 - crates/api-calendar/src/routes/calendar.rs | 133 - crates/api-calendar/src/routes/mod.rs | 17 - crates/api-client/Cargo.toml | 3 + crates/api-client/build.rs | 21 +- crates/api-client/openapi.gen.json | 2062 +++++++- crates/api-subscription/src/routes/billing.rs | 8 +- crates/google-calendar/Cargo.toml | 2 + crates/google-calendar/src/lib.rs | 3 + crates/google-calendar/src/openapi.rs | 59 + crates/google-calendar/src/types.rs | 58 + crates/outlook-calendar/Cargo.toml | 5 + crates/outlook-calendar/src/lib.rs | 3 + crates/outlook-calendar/src/openapi.rs | 42 + crates/outlook-calendar/src/types.rs | 41 + packages/api-client/src/generated/index.ts | 4 +- packages/api-client/src/generated/sdk.gen.ts | 20 +- .../api-client/src/generated/types.gen.ts | 630 ++- plugins/google-calendar/src/convert.rs | 107 + plugins/google-calendar/src/ext.rs | 10 +- plugins/google-calendar/src/fetch.rs | 96 +- plugins/google-calendar/src/fixture.rs | 207 - plugins/google-calendar/src/lib.rs | 14 +- 35 files changed, 6643 insertions(+), 1822 deletions(-) create mode 100644 crates/api-calendar/src/google/mod.rs create mode 100644 crates/api-calendar/src/google/routes.rs create mode 100644 crates/api-calendar/src/outlook/mod.rs create mode 100644 crates/api-calendar/src/outlook/routes.rs delete mode 100644 crates/api-calendar/src/provider.rs delete mode 100644 crates/api-calendar/src/providers/google.rs delete mode 100644 crates/api-calendar/src/providers/mod.rs delete mode 100644 crates/api-calendar/src/providers/outlook.rs delete mode 100644 crates/api-calendar/src/routes/calendar.rs delete mode 100644 crates/api-calendar/src/routes/mod.rs create mode 100644 crates/google-calendar/src/openapi.rs create mode 100644 crates/outlook-calendar/src/openapi.rs delete mode 100644 plugins/google-calendar/src/fixture.rs diff --git a/apps/api/openapi.gen.json b/apps/api/openapi.gen.json index e786efcc7f..c6f350669a 100644 --- a/apps/api/openapi.gen.json +++ b/apps/api/openapi.gen.json @@ -9,19 +9,19 @@ "version": "1.0.0" }, "paths": { - "/calendar/calendars": { + "/calendar/google/list-calendars": { "post": { "tags": [ "calendar" ], - "operationId": "list_calendars", + "operationId": "google_list_calendars", "responses": { "200": { - "description": "Calendars fetched", + "description": "Google calendars fetched", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListCalendarsResponse" + "$ref": "#/components/schemas/google.ListCalendarsResponse" } } } @@ -40,17 +40,17 @@ ] } }, - "/calendar/events": { + "/calendar/google/list-events": { "post": { "tags": [ "calendar" ], - "operationId": "list_events", + "operationId": "google_list_events", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListEventsRequest" + "$ref": "#/components/schemas/GoogleListEventsRequest" } } }, @@ -58,11 +58,11 @@ }, "responses": { "200": { - "description": "Events fetched", + "description": "Google events fetched", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListEventsResponse" + "$ref": "#/components/schemas/google.ListEventsResponse" } } } @@ -81,17 +81,48 @@ ] } }, - "/calendar/events/create": { + "/calendar/outlook/list-calendars": { "post": { "tags": [ "calendar" ], - "operationId": "create_event", + "operationId": "outlook_list_calendars", + "responses": { + "200": { + "description": "Outlook calendars fetched", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/outlook.ListCalendarsResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/calendar/outlook/list-events": { + "post": { + "tags": [ + "calendar" + ], + "operationId": "outlook_list_events", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateEventRequest" + "$ref": "#/components/schemas/OutlookListEventsRequest" } } }, @@ -99,11 +130,11 @@ }, "responses": { "200": { - "description": "Event created", + "description": "Outlook events fetched", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateEventResponse" + "$ref": "#/components/schemas/outlook.ListEventsResponse" } } } @@ -872,6 +903,44 @@ }, "components": { "schemas": { + "AccessRole": { + "type": "string", + "enum": [ + "freeBusyReader", + "reader", + "writer", + "owner", + "unknown" + ] + }, + "AttendeeResponseStatus": { + "type": "string", + "enum": [ + "needsAction", + "declined", + "tentative", + "accepted", + "unknown" + ] + }, + "AttendeeType": { + "type": "string", + "enum": [ + "required", + "optional", + "resource", + "unknown" + ] + }, + "AutoDeclineMode": { + "type": "string", + "enum": [ + "declineNone", + "declineAllConflictingInvitations", + "declineOnlyNewConflictingInvitations", + "unknown" + ] + }, "BatchAlternatives": { "type": "object", "required": [ @@ -976,499 +1045,312 @@ } } }, - "CanStartTrialReason": { - "type": "string", - "enum": [ - "eligible", - "not_eligible", - "error" - ] - }, - "CanStartTrialResponse": { + "BirthdayProperties": { "type": "object", - "required": [ - "canStartTrial" - ], "properties": { - "canStartTrial": { - "type": "boolean", - "example": true + "contact": { + "type": [ + "string", + "null" + ] }, - "reason": { + "customTypeName": { + "type": [ + "string", + "null" + ] + }, + "type": { "oneOf": [ { "type": "null" }, { - "$ref": "#/components/schemas/CanStartTrialReason" + "$ref": "#/components/schemas/BirthdayPropertyType" } ] } } }, - "CharTask": { + "BirthdayPropertyType": { "type": "string", "enum": [ - "chat", - "enhance", - "title" + "birthday", + "anniversary", + "self", + "other", + "custom", + "unknown" ] }, - "ConnectSessionResponse": { - "type": "object", - "required": [ - "token", - "expires_at" - ], - "properties": { - "expires_at": { - "type": "string" - }, - "token": { - "type": "string" - } - } + "BodyType": { + "type": "string", + "enum": [ + "text", + "html", + "unknown" + ] }, - "ConnectionItem": { + "Calendar": { "type": "object", "required": [ - "integration_id", - "connection_id" + "id" ], "properties": { - "connection_id": { - "type": "string" - }, - "integration_id": { - "type": "string" + "allowedOnlineMeetingProviders": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/OnlineMeetingProviderType" + } }, - "updated_at": { + "canEdit": { "type": [ - "string", + "boolean", "null" ] - } - } - }, - "ConversationSummary": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" }, - "inboxId": { + "canShare": { "type": [ - "string", + "boolean", "null" ] - } - } - }, - "CreateConnectSessionRequest": { - "type": "object", - "properties": { - "allowed_integrations": { + }, + "canViewPrivateItems": { "type": [ - "array", + "boolean", "null" - ], - "items": { - "type": "string" - } - } - } - }, - "CreateContactRequest": { - "type": "object", - "required": [ - "identifier" - ], - "properties": { - "customAttributes": {}, - "email": { + ] + }, + "changeKey": { "type": [ "string", "null" ] }, - "identifier": { - "type": "string" + "color": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/CalendarColor" + } + ] }, - "name": { + "defaultOnlineMeetingProvider": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/OnlineMeetingProviderType" + } + ] + }, + "hexColor": { "type": [ "string", "null" ] - } - } - }, - "CreateContactResponse": { - "type": "object", - "required": [ - "sourceId", - "pubsubToken" - ], - "properties": { - "pubsubToken": { - "type": "string" }, - "sourceId": { - "type": "string" - } - } - }, - "CreateConversationRequest": { - "type": "object", - "required": [ - "sourceId" - ], - "properties": { - "customAttributes": {}, - "sourceId": { + "id": { "type": "string" - } - } - }, - "CreateConversationResponse": { - "type": "object", - "required": [ - "conversationId" - ], - "properties": { - "conversationId": { - "type": "integer", - "format": "int64" - } - } - }, - "CreateEventRequest": { - "type": "object", - "required": [ - "calendar_id", - "summary", - "start", - "end" - ], - "properties": { - "attendees": { + }, + "isDefaultCalendar": { "type": [ - "array", + "boolean", "null" - ], - "items": { - "$ref": "#/components/schemas/EventAttendee" - } - }, - "calendar_id": { - "type": "string" + ] }, - "description": { + "isRemovable": { "type": [ - "string", + "boolean", "null" ] }, - "end": { - "$ref": "#/components/schemas/EventDateTime" + "isTallyingResponses": { + "type": [ + "boolean", + "null" + ] }, - "location": { + "name": { "type": [ "string", "null" ] }, - "start": { - "$ref": "#/components/schemas/EventDateTime" - }, - "summary": { - "type": "string" + "owner": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EmailAddress" + } + ] } } }, - "CreateEventResponse": { + "CalendarColor": { + "type": "string", + "enum": [ + "auto", + "lightBlue", + "lightGreen", + "lightOrange", + "lightGray", + "lightYellow", + "lightTeal", + "lightPink", + "lightBrown", + "lightRed", + "maxColor", + "unknown" + ] + }, + "CalendarListEntry": { "type": "object", "required": [ - "event" - ], - "properties": { - "event": {} - } - }, - "CreateReconnectSessionRequest": { - "type": "object", - "required": [ - "connection_id", - "integration_id" - ], - "properties": { - "connection_id": { - "type": "string" - }, - "integration_id": { - "type": "string" - } - } - }, - "DeleteAccountResponse": { - "type": "object", - "required": [ - "deleted" + "id" ], "properties": { - "deleted": { - "type": "boolean" + "accessRole": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AccessRole" + } + ] }, - "error": { + "autoAcceptInvitations": { "type": [ - "string", + "boolean", "null" ] - } - } - }, - "DeviceInfo": { - "type": "object", - "required": [ - "platform", - "arch", - "osVersion", - "appVersion" - ], - "properties": { - "appVersion": { - "type": "string" - }, - "arch": { - "type": "string" }, - "buildHash": { + "backgroundColor": { "type": [ "string", "null" ] }, - "locale": { + "colorId": { "type": [ "string", "null" ] }, - "osVersion": { - "type": "string" + "conferenceProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceProperties" + } + ] }, - "platform": { - "type": "string" - } - } - }, - "EventAttendee": { - "type": "object", - "required": [ - "email" - ], - "properties": { - "displayName": { + "dataOwner": { "type": [ "string", "null" ] }, - "email": { - "type": "string" - }, - "optional": { - "type": [ - "boolean", - "null" - ] - } - } - }, - "EventDateTime": { - "type": "object", - "properties": { - "date": { + "defaultReminders": { "type": [ - "string", + "array", "null" - ] + ], + "items": { + "$ref": "#/components/schemas/Reminder" + } }, - "dateTime": { + "deleted": { "type": [ - "string", + "boolean", "null" ] }, - "timeZone": { + "description": { "type": [ "string", "null" ] - } - } - }, - "FeedbackRequest": { - "type": "object", - "required": [ - "description", - "deviceInfo" - ], - "properties": { - "description": { - "type": "string" - }, - "deviceInfo": { - "$ref": "#/components/schemas/DeviceInfo" }, - "logs": { + "etag": { "type": [ "string", "null" ] }, - "type": { - "$ref": "#/components/schemas/FeedbackType" - } - } - }, - "FeedbackResponse": { - "type": "object", - "required": [ - "success" - ], - "properties": { - "error": { + "foregroundColor": { "type": [ "string", "null" ] }, - "issueUrl": { + "hidden": { "type": [ - "string", + "boolean", "null" ] }, - "success": { - "type": "boolean" - } - } - }, - "FeedbackType": { - "type": "string", - "enum": [ - "bug", - "feature" - ] - }, - "Interval": { - "type": "string", - "enum": [ - "monthly", - "yearly" - ] - }, - "ListCalendarsResponse": { - "type": "object", - "required": [ - "calendars" - ], - "properties": { - "calendars": { - "type": "array", - "items": {} - } - } - }, - "ListConnectionsResponse": { - "type": "object", - "required": [ - "connections" - ], - "properties": { - "connections": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ConnectionItem" - } - } - } - }, - "ListConversationsQuery": { - "type": "object", - "required": [ - "sourceId" - ], - "properties": { - "sourceId": { - "type": "string" - } - } - }, - "ListEventsRequest": { - "type": "object", - "required": [ - "calendar_id" - ], - "properties": { - "calendar_id": { + "id": { "type": "string" }, - "max_results": { + "kind": { "type": [ - "integer", + "string", "null" - ], - "format": "int32", - "minimum": 0 + ] }, - "order_by": { + "location": { "type": [ "string", "null" ] }, - "page_token": { + "notificationSettings": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/NotificationSettings" + } + ] + }, + "primary": { "type": [ - "string", + "boolean", "null" ] }, - "single_events": { + "selected": { "type": [ "boolean", "null" ] }, - "time_max": { + "summary": { "type": [ "string", "null" ] }, - "time_min": { + "summaryOverride": { "type": [ "string", "null" ] - } - } - }, - "ListEventsResponse": { - "type": "object", - "required": [ - "events" - ], - "properties": { - "events": { - "type": "array", - "items": {} }, - "next_page_token": { + "timeZone": { "type": [ "string", "null" @@ -1476,78 +1358,163 @@ } } }, - "ListenCallbackRequest": { + "CalendarNotification": { "type": "object", "required": [ - "url" + "method", + "type" ], "properties": { - "url": { - "type": "string" + "method": { + "$ref": "#/components/schemas/NotificationMethod" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" } } }, - "ListenCallbackResponse": { - "type": "object", - "required": [ - "request_id" - ], - "properties": { - "request_id": { - "type": "string" - } - } + "CanStartTrialReason": { + "type": "string", + "enum": [ + "eligible", + "not_eligible", + "error" + ] }, - "MessageResponse": { + "CanStartTrialResponse": { "type": "object", "required": [ - "id" + "canStartTrial" ], "properties": { - "content": { - "type": [ - "string", - "null" + "canStartTrial": { + "type": "boolean", + "example": true + }, + "reason": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/CanStartTrialReason" + } + ] + } + } + }, + "CharTask": { + "type": "string", + "enum": [ + "chat", + "enhance", + "title" + ] + }, + "ChatStatus": { + "type": "string", + "enum": [ + "available", + "doNotDisturb", + "unknown" + ] + }, + "ConferenceCreateRequest": { + "type": "object", + "properties": { + "conferenceSolutionKey": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceSolutionKey" + } ] }, - "createdAt": { + "requestId": { "type": [ "string", "null" ] }, - "id": { - "type": "string" - }, - "messageType": { - "type": [ - "string", - "null" + "status": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceCreateRequestStatus" + } ] } } }, - "PipelineStatus": { + "ConferenceCreateRequestStatus": { + "type": "object", + "required": [ + "statusCode" + ], + "properties": { + "statusCode": { + "$ref": "#/components/schemas/ConferenceCreateStatusCode" + } + } + }, + "ConferenceCreateStatusCode": { "type": "string", "enum": [ - "processing", - "done", - "error" + "pending", + "success", + "failure", + "unknown" ] }, - "SendMessageRequest": { + "ConferenceData": { "type": "object", - "required": [ - "content" - ], "properties": { - "content": { - "type": "string" + "conferenceId": { + "type": [ + "string", + "null" + ] }, - "messageType": { - "type": "string" + "conferenceSolution": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceSolution" + } + ] }, - "sourceId": { + "createRequest": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceCreateRequest" + } + ] + }, + "entryPoints": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/EntryPoint" + } + }, + "notes": { + "type": [ + "string", + "null" + ] + }, + "signature": { "type": [ "string", "null" @@ -1555,378 +1522,3259 @@ } } }, - "StartTrialReason": { - "type": "string", - "enum": [ - "started", - "not_eligible" - ] + "ConferenceProperties": { + "type": "object", + "properties": { + "allowedConferenceSolutionTypes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/ConferenceSolutionType" + } + } + } }, - "StartTrialResponse": { + "ConferenceSolution": { "type": "object", - "required": [ - "started" - ], "properties": { - "reason": { + "iconUri": { + "type": [ + "string", + "null" + ] + }, + "key": { "oneOf": [ { "type": "null" }, { - "$ref": "#/components/schemas/StartTrialReason" + "$ref": "#/components/schemas/ConferenceSolutionKey" } ] }, - "started": { - "type": "boolean", - "example": true + "name": { + "type": [ + "string", + "null" + ] } } }, - "StreamAlternatives": { + "ConferenceSolutionKey": { "type": "object", "required": [ - "transcript", - "words", - "confidence" + "type" ], "properties": { - "confidence": { - "type": "number", - "format": "double" + "type": { + "$ref": "#/components/schemas/ConferenceSolutionType" + } + } + }, + "ConferenceSolutionType": { + "type": "string", + "enum": [ + "addOn", + "hangoutsMeet", + "eventNamedHangout", + "eventHangout", + "unknown" + ] + }, + "ConnectSessionResponse": { + "type": "object", + "required": [ + "token", + "expires_at" + ], + "properties": { + "expires_at": { + "type": "string" }, - "languages": { - "type": "array", - "items": { - "type": "string" - } + "token": { + "type": "string" + } + } + }, + "ConnectionItem": { + "type": "object", + "required": [ + "integration_id", + "connection_id" + ], + "properties": { + "connection_id": { + "type": "string" }, - "transcript": { + "integration_id": { "type": "string" }, - "words": { - "type": "array", - "items": { - "$ref": "#/components/schemas/StreamWord" - } + "updated_at": { + "type": [ + "string", + "null" + ] } } }, - "StreamChannel": { + "ConversationSummary": { "type": "object", "required": [ - "alternatives" + "id" ], "properties": { - "alternatives": { - "type": "array", + "id": { + "type": "integer", + "format": "int64" + }, + "inboxId": { + "type": [ + "string", + "null" + ] + } + } + }, + "CreateConnectSessionRequest": { + "type": "object", + "properties": { + "allowed_integrations": { + "type": [ + "array", + "null" + ], "items": { - "$ref": "#/components/schemas/StreamAlternatives" + "type": "string" } } } }, - "StreamMetadata": { + "CreateContactRequest": { "type": "object", "required": [ - "request_id", - "model_info", - "model_uuid" + "identifier" ], "properties": { - "extra": { + "customAttributes": {}, + "email": { "type": [ - "object", + "string", "null" ] }, - "model_info": { - "$ref": "#/components/schemas/StreamModelInfo" - }, - "model_uuid": { + "identifier": { "type": "string" }, - "request_id": { - "type": "string" + "name": { + "type": [ + "string", + "null" + ] } } }, - "StreamModelInfo": { + "CreateContactResponse": { "type": "object", "required": [ - "name", - "version", - "arch" + "sourceId", + "pubsubToken" ], "properties": { - "arch": { + "pubsubToken": { "type": "string" }, - "name": { + "sourceId": { "type": "string" - }, - "version": { + } + } + }, + "CreateConversationRequest": { + "type": "object", + "required": [ + "sourceId" + ], + "properties": { + "customAttributes": {}, + "sourceId": { "type": "string" } } }, - "StreamResponse": { - "oneOf": [ - { - "type": "object", - "required": [ - "start", - "duration", - "is_final", - "speech_final", - "from_finalize", - "channel", - "metadata", - "channel_index", - "type" + "CreateConversationResponse": { + "type": "object", + "required": [ + "conversationId" + ], + "properties": { + "conversationId": { + "type": "integer", + "format": "int64" + } + } + }, + "CreateEventRequest": { + "type": "object", + "required": [ + "calendar_id", + "event" + ], + "properties": { + "calendar_id": { + "type": "string" + }, + "event": { + "$ref": "#/components/schemas/google.CreateEventBody" + } + } + }, + "CreateReconnectSessionRequest": { + "type": "object", + "required": [ + "connection_id", + "integration_id" + ], + "properties": { + "connection_id": { + "type": "string" + }, + "integration_id": { + "type": "string" + } + } + }, + "CustomLocation": { + "type": "object", + "properties": { + "label": { + "type": [ + "string", + "null" + ] + } + } + }, + "DateTimeTimeZone": { + "type": "object", + "required": [ + "dateTime" + ], + "properties": { + "dateTime": { + "type": "string" + }, + "timeZone": { + "type": [ + "string", + "null" + ] + } + } + }, + "DayOfWeek": { + "type": "string", + "enum": [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "unknown" + ] + }, + "DeleteAccountResponse": { + "type": "object", + "required": [ + "deleted" + ], + "properties": { + "deleted": { + "type": "boolean" + }, + "error": { + "type": [ + "string", + "null" + ] + } + } + }, + "DeviceInfo": { + "type": "object", + "required": [ + "platform", + "arch", + "osVersion", + "appVersion" + ], + "properties": { + "appVersion": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "buildHash": { + "type": [ + "string", + "null" + ] + }, + "locale": { + "type": [ + "string", + "null" + ] + }, + "osVersion": { + "type": "string" + }, + "platform": { + "type": "string" + } + } + }, + "EmailAddress": { + "type": "object", + "properties": { + "address": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + } + } + }, + "EntryPoint": { + "type": "object", + "required": [ + "entryPointType", + "uri" + ], + "properties": { + "accessCode": { + "type": [ + "string", + "null" + ] + }, + "entryPointType": { + "$ref": "#/components/schemas/EntryPointType" + }, + "label": { + "type": [ + "string", + "null" + ] + }, + "meetingCode": { + "type": [ + "string", + "null" + ] + }, + "passcode": { + "type": [ + "string", + "null" + ] + }, + "password": { + "type": [ + "string", + "null" + ] + }, + "pin": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + } + }, + "EntryPointType": { + "type": "string", + "enum": [ + "video", + "phone", + "sip", + "more", + "unknown" + ] + }, + "EventAttachment": { + "type": "object", + "properties": { + "fileId": { + "type": [ + "string", + "null" + ] + }, + "fileUrl": { + "type": [ + "string", + "null" + ] + }, + "iconLink": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + } + } + }, + "EventDateTime": { + "type": "object", + "properties": { + "date": { + "type": [ + "string", + "null" + ], + "format": "date" + }, + "dateTime": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "timeZone": { + "type": [ + "string", + "null" + ] + } + } + }, + "EventOrderBy": { + "type": "string", + "enum": [ + "startTime", + "updated" + ] + }, + "EventPerson": { + "type": "object", + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "self": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "EventShowAs": { + "type": "string", + "enum": [ + "free", + "tentative", + "busy", + "oof", + "workingElsewhere", + "unknown", + "other" + ] + }, + "EventSource": { + "type": "object", + "required": [ + "url", + "title" + ], + "properties": { + "title": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "EventStatus": { + "type": "string", + "enum": [ + "confirmed", + "tentative", + "cancelled", + "unknown" + ] + }, + "ExtendedProperties": { + "type": "object", + "properties": { + "private": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "shared": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + } + } + }, + "FeedbackRequest": { + "type": "object", + "required": [ + "description", + "deviceInfo" + ], + "properties": { + "description": { + "type": "string" + }, + "deviceInfo": { + "$ref": "#/components/schemas/DeviceInfo" + }, + "logs": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/components/schemas/FeedbackType" + } + } + }, + "FeedbackResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "issueUrl": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + } + } + }, + "FeedbackType": { + "type": "string", + "enum": [ + "bug", + "feature" + ] + }, + "FocusTimeProperties": { + "type": "object", + "properties": { + "autoDeclineMode": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AutoDeclineMode" + } + ] + }, + "chatStatus": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ChatStatus" + } + ] + }, + "declineMessage": { + "type": [ + "string", + "null" + ] + } + } + }, + "Gadget": { + "type": "object", + "properties": { + "display": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/GadgetDisplay" + } + ] + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "iconLink": { + "type": [ + "string", + "null" + ] + }, + "link": { + "type": [ + "string", + "null" + ] + }, + "preferences": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + } + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": [ + "string", + "null" + ] + }, + "width": { + "type": [ + "integer", + "null" + ], + "format": "int32" + } + } + }, + "GadgetDisplay": { + "type": "string", + "enum": [ + "chip", + "icon", + "unknown" + ] + }, + "GoogleListEventsRequest": { + "type": "object", + "required": [ + "calendar_id" + ], + "properties": { + "calendar_id": { + "type": "string" + }, + "max_results": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "order_by": { + "type": [ + "string", + "null" + ] + }, + "page_token": { + "type": [ + "string", + "null" + ] + }, + "single_events": { + "type": [ + "boolean", + "null" + ] + }, + "time_max": { + "type": [ + "string", + "null" + ] + }, + "time_min": { + "type": [ + "string", + "null" + ] + } + } + }, + "Importance": { + "type": "string", + "enum": [ + "low", + "normal", + "high", + "unknown" + ] + }, + "Interval": { + "type": "string", + "enum": [ + "monthly", + "yearly" + ] + }, + "ItemBody": { + "type": "object", + "properties": { + "content": { + "type": [ + "string", + "null" + ] + }, + "contentType": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/BodyType" + } + ] + } + } + }, + "ListConnectionsResponse": { + "type": "object", + "required": [ + "connections" + ], + "properties": { + "connections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectionItem" + } + } + } + }, + "ListConversationsQuery": { + "type": "object", + "required": [ + "sourceId" + ], + "properties": { + "sourceId": { + "type": "string" + } + } + }, + "ListEventsRequest": { + "type": "object", + "required": [ + "calendar_id" + ], + "properties": { + "calendar_id": { + "type": "string" + }, + "event_types": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/google.EventType" + } + }, + "i_cal_uid": { + "type": [ + "string", + "null" + ] + }, + "max_results": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "order_by": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventOrderBy" + } + ] + }, + "page_token": { + "type": [ + "string", + "null" + ] + }, + "q": { + "type": [ + "string", + "null" + ] + }, + "show_deleted": { + "type": [ + "boolean", + "null" + ] + }, + "show_hidden_invitations": { + "type": [ + "boolean", + "null" + ] + }, + "single_events": { + "type": [ + "boolean", + "null" + ] + }, + "sync_token": { + "type": [ + "string", + "null" + ] + }, + "time_max": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "time_min": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "time_zone": { + "type": [ + "string", + "null" + ] + }, + "updated_min": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + }, + "ListenCallbackRequest": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + } + }, + "ListenCallbackResponse": { + "type": "object", + "required": [ + "request_id" + ], + "properties": { + "request_id": { + "type": "string" + } + } + }, + "Location": { + "type": "object", + "properties": { + "address": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/PhysicalAddress" + } + ] + }, + "coordinates": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/OutlookGeoCoordinates" + } + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "locationType": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/LocationType" + } + ] + }, + "uniqueId": { + "type": [ + "string", + "null" + ] + }, + "uniqueIdType": { + "type": [ + "string", + "null" + ] + } + } + }, + "LocationType": { + "type": "string", + "enum": [ + "default", + "conferenceRoom", + "homeAddress", + "businessAddress", + "geoCoordinates", + "streetAddress", + "hotel", + "restaurant", + "localBusiness", + "postalAddress", + "unknown" + ] + }, + "MessageResponse": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "content": { + "type": [ + "string", + "null" + ] + }, + "createdAt": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "messageType": { + "type": [ + "string", + "null" + ] + } + } + }, + "NotificationMethod": { + "type": "string", + "enum": [ + "email", + "unknown" + ] + }, + "NotificationSettings": { + "type": "object", + "properties": { + "notifications": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/CalendarNotification" + } + } + } + }, + "NotificationType": { + "type": "string", + "enum": [ + "eventCreation", + "eventChange", + "eventCancellation", + "eventResponse", + "agenda", + "unknown" + ] + }, + "OfficeLocation": { + "type": "object", + "properties": { + "buildingId": { + "type": [ + "string", + "null" + ] + }, + "deskId": { + "type": [ + "string", + "null" + ] + }, + "floorId": { + "type": [ + "string", + "null" + ] + }, + "floorSectionId": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": [ + "string", + "null" + ] + } + } + }, + "OnlineMeetingInfo": { + "type": "object", + "properties": { + "conferenceId": { + "type": [ + "string", + "null" + ] + }, + "joinUrl": { + "type": [ + "string", + "null" + ] + }, + "quickDial": { + "type": [ + "string", + "null" + ] + }, + "tollFreeNumbers": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "tollNumber": { + "type": [ + "string", + "null" + ] + } + } + }, + "OnlineMeetingProviderType": { + "type": "string", + "enum": [ + "teamsForBusiness", + "skypeForBusiness", + "skypeForConsumer", + "unknown", + "other" + ] + }, + "OutOfOfficeProperties": { + "type": "object", + "properties": { + "autoDeclineMode": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AutoDeclineMode" + } + ] + }, + "declineMessage": { + "type": [ + "string", + "null" + ] + } + } + }, + "OutlookGeoCoordinates": { + "type": "object", + "properties": { + "accuracy": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "altitude": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "altitudeAccuracy": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "latitude": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "longitude": { + "type": [ + "number", + "null" + ], + "format": "double" + } + } + }, + "OutlookListEventsRequest": { + "type": "object", + "required": [ + "calendar_id" + ], + "properties": { + "calendar_id": { + "type": "string" + }, + "max_results": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "order_by": { + "type": [ + "string", + "null" + ] + }, + "time_max": { + "type": [ + "string", + "null" + ] + }, + "time_min": { + "type": [ + "string", + "null" + ] + } + } + }, + "PatternedRecurrence": { + "type": "object", + "properties": { + "pattern": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/RecurrencePattern" + } + ] + }, + "range": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/RecurrenceRange" + } + ] + } + } + }, + "PhysicalAddress": { + "type": "object", + "properties": { + "city": { + "type": [ + "string", + "null" + ] + }, + "countryOrRegion": { + "type": [ + "string", + "null" + ] + }, + "postalCode": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": [ + "string", + "null" + ] + }, + "street": { + "type": [ + "string", + "null" + ] + } + } + }, + "PipelineStatus": { + "type": "string", + "enum": [ + "processing", + "done", + "error" + ] + }, + "Recipient": { + "type": "object", + "properties": { + "emailAddress": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EmailAddress" + } + ] + } + } + }, + "RecurrencePattern": { + "type": "object", + "properties": { + "dayOfMonth": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "daysOfWeek": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/DayOfWeek" + } + }, + "firstDayOfWeek": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/DayOfWeek" + } + ] + }, + "index": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/WeekIndex" + } + ] + }, + "interval": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "month": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "type": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/RecurrencePatternType" + } + ] + } + } + }, + "RecurrencePatternType": { + "type": "string", + "enum": [ + "daily", + "weekly", + "absoluteMonthly", + "relativeMonthly", + "absoluteYearly", + "relativeYearly", + "unknown" + ] + }, + "RecurrenceRange": { + "type": "object", + "properties": { + "endDate": { + "type": [ + "string", + "null" + ] + }, + "numberOfOccurrences": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "recurrenceTimeZone": { + "type": [ + "string", + "null" + ] + }, + "startDate": { + "type": [ + "string", + "null" + ] + }, + "type": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/RecurrenceRangeType" + } + ] + } + } + }, + "RecurrenceRangeType": { + "type": "string", + "enum": [ + "endDate", + "noEnd", + "numbered", + "unknown" + ] + }, + "Reminder": { + "type": "object", + "required": [ + "method", + "minutes" + ], + "properties": { + "method": { + "$ref": "#/components/schemas/ReminderMethod" + }, + "minutes": { + "type": "integer", + "format": "int32" + } + } + }, + "ReminderMethod": { + "type": "string", + "enum": [ + "email", + "popup", + "unknown" + ] + }, + "Reminders": { + "type": "object", + "properties": { + "overrides": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/Reminder" + } + }, + "useDefault": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "ResponseStatus": { + "type": "object", + "properties": { + "response": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ResponseType" + } + ] + }, + "time": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + }, + "ResponseType": { + "type": "string", + "enum": [ + "none", + "organizer", + "tentativelyAccepted", + "accepted", + "declined", + "notResponded", + "unknown" + ] + }, + "SendMessageRequest": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string" + }, + "messageType": { + "type": "string" + }, + "sourceId": { + "type": [ + "string", + "null" + ] + } + } + }, + "Sensitivity": { + "type": "string", + "enum": [ + "normal", + "personal", + "private", + "confidential", + "unknown" + ] + }, + "StartTrialReason": { + "type": "string", + "enum": [ + "started", + "not_eligible" + ] + }, + "StartTrialResponse": { + "type": "object", + "required": [ + "started" + ], + "properties": { + "reason": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/StartTrialReason" + } + ] + }, + "started": { + "type": "boolean", + "example": true + } + } + }, + "StreamAlternatives": { + "type": "object", + "required": [ + "transcript", + "words", + "confidence" + ], + "properties": { + "confidence": { + "type": "number", + "format": "double" + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "transcript": { + "type": "string" + }, + "words": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StreamWord" + } + } + } + }, + "StreamChannel": { + "type": "object", + "required": [ + "alternatives" + ], + "properties": { + "alternatives": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StreamAlternatives" + } + } + } + }, + "StreamMetadata": { + "type": "object", + "required": [ + "request_id", + "model_info", + "model_uuid" + ], + "properties": { + "extra": { + "type": [ + "object", + "null" + ] + }, + "model_info": { + "$ref": "#/components/schemas/StreamModelInfo" + }, + "model_uuid": { + "type": "string" + }, + "request_id": { + "type": "string" + } + } + }, + "StreamModelInfo": { + "type": "object", + "required": [ + "name", + "version", + "arch" + ], + "properties": { + "arch": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "StreamResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "start", + "duration", + "is_final", + "speech_final", + "from_finalize", + "channel", + "metadata", + "channel_index", + "type" + ], + "properties": { + "channel": { + "$ref": "#/components/schemas/StreamChannel" + }, + "channel_index": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "duration": { + "type": "number", + "format": "double" + }, + "from_finalize": { + "type": "boolean" + }, + "is_final": { + "type": "boolean" + }, + "metadata": { + "$ref": "#/components/schemas/StreamMetadata" + }, + "speech_final": { + "type": "boolean" + }, + "start": { + "type": "number", + "format": "double" + }, + "type": { + "type": "string", + "enum": [ + "Results" + ] + } + } + }, + { + "type": "object", + "required": [ + "request_id", + "created", + "duration", + "channels", + "type" + ], + "properties": { + "channels": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "created": { + "type": "string" + }, + "duration": { + "type": "number", + "format": "double" + }, + "request_id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "Metadata" + ] + } + } + }, + { + "type": "object", + "required": [ + "channel", + "timestamp", + "type" + ], + "properties": { + "channel": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + "timestamp": { + "type": "number", + "format": "double" + }, + "type": { + "type": "string", + "enum": [ + "SpeechStarted" + ] + } + } + }, + { + "type": "object", + "required": [ + "channel", + "last_word_end", + "type" + ], + "properties": { + "channel": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + "last_word_end": { + "type": "number", + "format": "double" + }, + "type": { + "type": "string", + "enum": [ + "UtteranceEnd" + ] + } + } + }, + { + "type": "object", + "required": [ + "error_message", + "provider", + "type" + ], + "properties": { + "error_code": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "error_message": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "Error" + ] + } + } + } + ] + }, + "StreamWord": { + "type": "object", + "required": [ + "word", + "start", + "end", + "confidence" + ], + "properties": { + "confidence": { + "type": "number", + "format": "double" + }, + "end": { + "type": "number", + "format": "double" + }, + "language": { + "type": [ + "string", + "null" + ] + }, + "punctuated_word": { + "type": [ + "string", + "null" + ] + }, + "speaker": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "start": { + "type": "number", + "format": "double" + }, + "word": { + "type": "string" + } + } + }, + "SttStatusResponse": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "provider": { + "type": [ + "string", + "null" + ] + }, + "rawResult": { + "type": [ + "object", + "null" + ] + }, + "status": { + "$ref": "#/components/schemas/PipelineStatus" + } + } + }, + "Transparency": { + "type": "string", + "enum": [ + "opaque", + "transparent", + "unknown" + ] + }, + "Visibility": { + "type": "string", + "enum": [ + "default", + "public", + "private", + "confidential", + "unknown" + ] + }, + "WebhookResponse": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + }, + "WeekIndex": { + "type": "string", + "enum": [ + "first", + "second", + "third", + "fourth", + "last", + "unknown" + ] + }, + "WorkingLocationProperties": { + "type": "object", + "properties": { + "customLocation": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/CustomLocation" + } + ] + }, + "homeOffice": {}, + "officeLocation": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/OfficeLocation" + } + ] + }, + "type": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/WorkingLocationType" + } + ] + } + } + }, + "WorkingLocationType": { + "type": "string", + "enum": [ + "homeOffice", + "officeLocation", + "customLocation", + "unknown" + ] + }, + "google.Attendee": { + "type": "object", + "properties": { + "additionalGuests": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "comment": { + "type": [ + "string", + "null" + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "email": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "optional": { + "type": [ + "boolean", + "null" + ] + }, + "organizer": { + "type": [ + "boolean", + "null" + ] + }, + "resource": { + "type": [ + "boolean", + "null" + ] + }, + "responseStatus": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AttendeeResponseStatus" + } + ] + }, + "self": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "google.CreateEventBody": { + "type": "object", + "properties": { + "attendees": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/google.Attendee" + } + }, + "colorId": { + "type": [ + "string", + "null" + ] + }, + "conferenceData": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceData" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "end": { + "$ref": "#/components/schemas/EventDateTime" + }, + "eventType": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/google.EventType" + } + ] + }, + "extendedProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ExtendedProperties" + } + ] + }, + "guestsCanInviteOthers": { + "type": [ + "boolean", + "null" + ] + }, + "guestsCanModify": { + "type": [ + "boolean", + "null" + ] + }, + "guestsCanSeeOtherGuests": { + "type": [ + "boolean", + "null" + ] + }, + "location": { + "type": [ + "string", + "null" + ] + }, + "recurrence": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "reminders": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Reminders" + } + ] + }, + "source": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventSource" + } + ] + }, + "start": { + "$ref": "#/components/schemas/EventDateTime" + }, + "summary": { + "type": "string" + }, + "transparency": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Transparency" + } + ] + }, + "visibility": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Visibility" + } + ] + } + } + }, + "google.Event": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "anyoneCanAddSelf": { + "type": [ + "boolean", + "null" + ] + }, + "attachments": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/EventAttachment" + } + }, + "attendees": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/google.Attendee" + } + }, + "attendeesOmitted": { + "type": [ + "boolean", + "null" + ] + }, + "birthdayProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/BirthdayProperties" + } + ] + }, + "colorId": { + "type": [ + "string", + "null" + ] + }, + "conferenceData": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ConferenceData" + } + ] + }, + "created": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "creator": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventPerson" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "end": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventDateTime" + } + ] + }, + "endTimeUnspecified": { + "type": [ + "boolean", + "null" + ] + }, + "etag": { + "type": [ + "string", + "null" + ] + }, + "eventType": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/google.EventType" + } + ] + }, + "extendedProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ExtendedProperties" + } + ] + }, + "focusTimeProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/FocusTimeProperties" + } + ] + }, + "gadget": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Gadget" + } + ] + }, + "guestsCanInviteOthers": { + "type": [ + "boolean", + "null" + ] + }, + "guestsCanModify": { + "type": [ + "boolean", + "null" + ] + }, + "guestsCanSeeOtherGuests": { + "type": [ + "boolean", + "null" + ] + }, + "hangoutLink": { + "type": [ + "string", + "null" + ] + }, + "htmlLink": { + "type": [ + "string", + "null" + ] + }, + "iCalUID": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "kind": { + "type": [ + "string", + "null" + ] + }, + "location": { + "type": [ + "string", + "null" + ] + }, + "locked": { + "type": [ + "boolean", + "null" + ] + }, + "organizer": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventPerson" + } + ] + }, + "originalStartTime": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventDateTime" + } + ] + }, + "outOfOfficeProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/OutOfOfficeProperties" + } + ] + }, + "privateCopy": { + "type": [ + "boolean", + "null" + ] + }, + "recurrence": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "recurringEventId": { + "type": [ + "string", + "null" + ] + }, + "reminders": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Reminders" + } + ] + }, + "sequence": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "source": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventSource" + } + ] + }, + "start": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventDateTime" + } + ] + }, + "status": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventStatus" + } + ] + }, + "summary": { + "type": [ + "string", + "null" + ] + }, + "transparency": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Transparency" + } + ] + }, + "updated": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "visibility": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Visibility" + } + ] + }, + "workingLocationProperties": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/WorkingLocationProperties" + } + ] + } + } + }, + "google.EventType": { + "type": "string", + "enum": [ + "default", + "birthday", + "focusTime", + "fromGmail", + "outOfOffice", + "workingLocation", + "unknown" + ] + }, + "google.ListCalendarsResponse": { + "type": "object", + "properties": { + "etag": { + "type": [ + "string", + "null" + ] + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CalendarListEntry" + } + }, + "kind": { + "type": [ + "string", + "null" + ] + }, + "nextPageToken": { + "type": [ + "string", + "null" + ] + }, + "nextSyncToken": { + "type": [ + "string", + "null" + ] + } + } + }, + "google.ListEventsResponse": { + "type": "object", + "properties": { + "accessRole": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AccessRole" + } + ] + }, + "defaultReminders": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/Reminder" + } + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "etag": { + "type": [ + "string", + "null" + ] + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/google.Event" + } + }, + "kind": { + "type": [ + "string", + "null" + ] + }, + "nextPageToken": { + "type": [ + "string", + "null" + ] + }, + "nextSyncToken": { + "type": [ + "string", + "null" + ] + }, + "summary": { + "type": [ + "string", + "null" + ] + }, + "timeZone": { + "type": [ + "string", + "null" + ] + }, + "updated": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + }, + "outlook.Attendee": { + "type": "object", + "properties": { + "emailAddress": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EmailAddress" + } + ] + }, + "status": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ResponseStatus" + } + ] + }, + "type": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AttendeeType" + } + ] + } + } + }, + "outlook.CreateEventBody": { + "type": "object", + "properties": { + "allowNewTimeProposals": { + "type": [ + "boolean", + "null" + ] + }, + "attendees": { + "type": [ + "array", + "null" ], - "properties": { - "channel": { - "$ref": "#/components/schemas/StreamChannel" + "items": { + "$ref": "#/components/schemas/outlook.Attendee" + } + }, + "body": { + "oneOf": [ + { + "type": "null" }, - "channel_index": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - } + { + "$ref": "#/components/schemas/ItemBody" + } + ] + }, + "categories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "end": { + "$ref": "#/components/schemas/DateTimeTimeZone" + }, + "hideAttendees": { + "type": [ + "boolean", + "null" + ] + }, + "importance": { + "oneOf": [ + { + "type": "null" }, - "duration": { - "type": "number", - "format": "double" + { + "$ref": "#/components/schemas/Importance" + } + ] + }, + "isAllDay": { + "type": [ + "boolean", + "null" + ] + }, + "isOnlineMeeting": { + "type": [ + "boolean", + "null" + ] + }, + "isReminderOn": { + "type": [ + "boolean", + "null" + ] + }, + "location": { + "oneOf": [ + { + "type": "null" }, - "from_finalize": { - "type": "boolean" + { + "$ref": "#/components/schemas/Location" + } + ] + }, + "onlineMeetingProvider": { + "oneOf": [ + { + "type": "null" }, - "is_final": { - "type": "boolean" + { + "$ref": "#/components/schemas/OnlineMeetingProviderType" + } + ] + }, + "recurrence": { + "oneOf": [ + { + "type": "null" }, - "metadata": { - "$ref": "#/components/schemas/StreamMetadata" + { + "$ref": "#/components/schemas/PatternedRecurrence" + } + ] + }, + "reminderMinutesBeforeStart": { + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "responseRequested": { + "type": [ + "boolean", + "null" + ] + }, + "sensitivity": { + "oneOf": [ + { + "type": "null" }, - "speech_final": { - "type": "boolean" + { + "$ref": "#/components/schemas/Sensitivity" + } + ] + }, + "showAs": { + "oneOf": [ + { + "type": "null" }, - "start": { - "type": "number", - "format": "double" + { + "$ref": "#/components/schemas/EventShowAs" + } + ] + }, + "start": { + "$ref": "#/components/schemas/DateTimeTimeZone" + }, + "subject": { + "type": "string" + } + } + }, + "outlook.Event": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "allowNewTimeProposals": { + "type": [ + "boolean", + "null" + ] + }, + "attendees": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/outlook.Attendee" + } + }, + "body": { + "oneOf": [ + { + "type": "null" }, - "type": { - "type": "string", - "enum": [ - "Results" - ] + { + "$ref": "#/components/schemas/ItemBody" } + ] + }, + "bodyPreview": { + "type": [ + "string", + "null" + ] + }, + "categories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" } }, - { - "type": "object", - "required": [ - "request_id", - "created", - "duration", - "channels", - "type" + "changeKey": { + "type": [ + "string", + "null" + ] + }, + "createdDateTime": { + "type": [ + "string", + "null" ], - "properties": { - "channels": { - "type": "integer", - "format": "int32", - "minimum": 0 - }, - "created": { - "type": "string" - }, - "duration": { - "type": "number", - "format": "double" + "format": "date-time" + }, + "end": { + "oneOf": [ + { + "type": "null" }, - "request_id": { - "type": "string" + { + "$ref": "#/components/schemas/DateTimeTimeZone" + } + ] + }, + "hasAttachments": { + "type": [ + "boolean", + "null" + ] + }, + "hideAttendees": { + "type": [ + "boolean", + "null" + ] + }, + "iCalUId": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "importance": { + "oneOf": [ + { + "type": "null" }, - "type": { - "type": "string", - "enum": [ - "Metadata" - ] + { + "$ref": "#/components/schemas/Importance" } - } + ] + }, + "isAllDay": { + "type": [ + "boolean", + "null" + ] + }, + "isCancelled": { + "type": [ + "boolean", + "null" + ] + }, + "isDraft": { + "type": [ + "boolean", + "null" + ] + }, + "isOnlineMeeting": { + "type": [ + "boolean", + "null" + ] + }, + "isOrganizer": { + "type": [ + "boolean", + "null" + ] }, - { - "type": "object", - "required": [ - "channel", - "timestamp", - "type" + "isReminderOn": { + "type": [ + "boolean", + "null" + ] + }, + "lastModifiedDateTime": { + "type": [ + "string", + "null" ], - "properties": { - "channel": { - "type": "array", - "items": { - "type": "integer", - "format": "int32", - "minimum": 0 - } - }, - "timestamp": { - "type": "number", - "format": "double" + "format": "date-time" + }, + "location": { + "oneOf": [ + { + "type": "null" }, - "type": { - "type": "string", - "enum": [ - "SpeechStarted" - ] + { + "$ref": "#/components/schemas/Location" } - } + ] }, - { - "type": "object", - "required": [ - "channel", - "last_word_end", - "type" + "locations": { + "type": [ + "array", + "null" ], - "properties": { - "channel": { - "type": "array", - "items": { - "type": "integer", - "format": "int32", - "minimum": 0 - } - }, - "last_word_end": { - "type": "number", - "format": "double" - }, - "type": { - "type": "string", - "enum": [ - "UtteranceEnd" - ] - } + "items": { + "$ref": "#/components/schemas/Location" } }, - { - "type": "object", - "required": [ - "error_message", - "provider", - "type" - ], - "properties": { - "error_code": { - "type": [ - "integer", - "null" - ], - "format": "int32" - }, - "error_message": { - "type": "string" + "onlineMeeting": { + "oneOf": [ + { + "type": "null" }, - "provider": { - "type": "string" + { + "$ref": "#/components/schemas/OnlineMeetingInfo" + } + ] + }, + "onlineMeetingProvider": { + "oneOf": [ + { + "type": "null" }, - "type": { - "type": "string", - "enum": [ - "Error" - ] + { + "$ref": "#/components/schemas/OnlineMeetingProviderType" } - } - } - ] - }, - "StreamWord": { - "type": "object", - "required": [ - "word", - "start", - "end", - "confidence" - ], - "properties": { - "confidence": { - "type": "number", - "format": "double" + ] }, - "end": { - "type": "number", - "format": "double" + "onlineMeetingUrl": { + "type": [ + "string", + "null" + ] }, - "language": { + "organizer": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Recipient" + } + ] + }, + "originalEndTimeZone": { "type": [ "string", "null" ] }, - "punctuated_word": { + "originalStartTimeZone": { "type": [ "string", "null" ] }, - "speaker": { + "recurrence": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/PatternedRecurrence" + } + ] + }, + "reminderMinutesBeforeStart": { "type": [ "integer", "null" ], "format": "int32" }, + "responseRequested": { + "type": [ + "boolean", + "null" + ] + }, + "responseStatus": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ResponseStatus" + } + ] + }, + "sensitivity": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Sensitivity" + } + ] + }, + "seriesMasterId": { + "type": [ + "string", + "null" + ] + }, + "showAs": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EventShowAs" + } + ] + }, "start": { - "type": "number", - "format": "double" + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/DateTimeTimeZone" + } + ] }, - "word": { - "type": "string" + "subject": { + "type": [ + "string", + "null" + ] + }, + "transactionId": { + "type": [ + "string", + "null" + ] + }, + "type": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/outlook.EventType" + } + ] + }, + "webLink": { + "type": [ + "string", + "null" + ] } } }, - "SttStatusResponse": { + "outlook.EventType": { + "type": "string", + "enum": [ + "singleInstance", + "occurrence", + "exception", + "seriesMaster", + "unknown" + ] + }, + "outlook.ListCalendarsResponse": { "type": "object", - "required": [ - "status" - ], "properties": { - "error": { + "@odata.context": { "type": [ "string", "null" ] }, - "provider": { + "@odata.nextLink": { "type": [ "string", "null" ] }, - "rawResult": { - "type": [ - "object", - "null" - ] - }, - "status": { - "$ref": "#/components/schemas/PipelineStatus" + "value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Calendar" + } } } }, - "WebhookResponse": { + "outlook.ListEventsResponse": { "type": "object", - "required": [ - "status" - ], "properties": { - "status": { - "type": "string" + "@odata.context": { + "type": [ + "string", + "null" + ] + }, + "@odata.nextLink": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/outlook.Event" + } } } } diff --git a/apps/api/src/main.rs b/apps/api/src/main.rs index 3552a8d023..0f56a99d24 100644 --- a/apps/api/src/main.rs +++ b/apps/api/src/main.rs @@ -110,13 +110,8 @@ async fn app() -> Router { auth::require_auth, )); - let calendar_config = hypr_api_calendar::CalendarConfig { - google: true, - ..Default::default() - }; - let integration_routes = Router::new() - .nest("/calendar", hypr_api_calendar::router(calendar_config)) + .nest("/calendar", hypr_api_calendar::router()) .nest("/nango", hypr_api_nango::router(nango_config.clone())) .layer(axum::Extension(nango_connection_state)) .route_layer(middleware::from_fn(auth::sentry_and_analytics)) diff --git a/crates/api-calendar/Cargo.toml b/crates/api-calendar/Cargo.toml index 9d9dbe334a..20ff1841fe 100644 --- a/crates/api-calendar/Cargo.toml +++ b/crates/api-calendar/Cargo.toml @@ -6,18 +6,14 @@ edition = "2024" [dependencies] hypr-api-error = { workspace = true } hypr-api-nango = { workspace = true } -hypr-google-calendar = { workspace = true } +hypr-google-calendar = { workspace = true, features = ["utoipa"] } hypr-nango = { workspace = true } -hypr-outlook-calendar = { workspace = true } +hypr-outlook-calendar = { workspace = true, features = ["utoipa"] } chrono = { workspace = true, features = ["serde"] } -utoipa = { workspace = true } - axum = { workspace = true } -sentry = { workspace = true } -tracing = { workspace = true } +utoipa = { workspace = true } serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/api-calendar/src/google/mod.rs b/crates/api-calendar/src/google/mod.rs new file mode 100644 index 0000000000..06292ea930 --- /dev/null +++ b/crates/api-calendar/src/google/mod.rs @@ -0,0 +1,9 @@ +pub(crate) mod routes; + +use axum::{Router, routing::post}; + +pub fn router() -> Router { + Router::new() + .route("/list-calendars", post(routes::list_calendars)) + .route("/list-events", post(routes::list_events)) +} diff --git a/crates/api-calendar/src/google/routes.rs b/crates/api-calendar/src/google/routes.rs new file mode 100644 index 0000000000..146f001089 --- /dev/null +++ b/crates/api-calendar/src/google/routes.rs @@ -0,0 +1,129 @@ +use axum::Json; +use axum::extract::FromRequestParts; +use axum::http::request::Parts; +use hypr_api_nango::{GoogleCalendar, NangoConnection}; +use hypr_google_calendar::{ + EventOrderBy, GoogleCalendarClient, ListCalendarsResponse, ListEventsResponse, +}; +use hypr_nango::OwnedNangoHttpClient; +use serde::Deserialize; +use utoipa::ToSchema; + +use crate::error::{CalendarError, Result}; + +pub(crate) struct GoogleClient(GoogleCalendarClient); + +impl FromRequestParts for GoogleClient { + type Rejection = CalendarError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let conn = NangoConnection::::from_request_parts(parts, state).await?; + Ok(GoogleClient(GoogleCalendarClient::new(conn.into_http()))) + } +} + +#[derive(Debug, Deserialize, ToSchema)] +pub struct GoogleListEventsRequest { + pub calendar_id: String, + #[serde(default)] + pub time_min: Option, + #[serde(default)] + pub time_max: Option, + #[serde(default)] + pub max_results: Option, + #[serde(default)] + pub page_token: Option, + #[serde(default)] + pub single_events: Option, + #[serde(default)] + pub order_by: Option, +} + +#[utoipa::path( + post, + path = "/google/list-calendars", + operation_id = "google_list_calendars", + responses( + (status = 200, description = "Google calendars fetched", body = ListCalendarsResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error"), + ), + tag = "calendar", +)] +pub async fn list_calendars(client: GoogleClient) -> Result> { + let response = client + .0 + .list_calendars() + .await + .map_err(|e| CalendarError::Internal(e.to_string()))?; + + Ok(Json(response)) +} + +#[utoipa::path( + post, + path = "/google/list-events", + operation_id = "google_list_events", + request_body = GoogleListEventsRequest, + responses( + (status = 200, description = "Google events fetched", body = ListEventsResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error"), + ), + tag = "calendar", +)] +pub async fn list_events( + client: GoogleClient, + Json(req): Json, +) -> Result> { + let time_min = req + .time_min + .as_deref() + .map(|s| { + chrono::DateTime::parse_from_rfc3339(s) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .map_err(|e| CalendarError::BadRequest(format!("Invalid time_min: {e}"))) + }) + .transpose()?; + + let time_max = req + .time_max + .as_deref() + .map(|s| { + chrono::DateTime::parse_from_rfc3339(s) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .map_err(|e| CalendarError::BadRequest(format!("Invalid time_max: {e}"))) + }) + .transpose()?; + + let order_by = req + .order_by + .as_deref() + .map(|s| match s { + "startTime" => Ok(EventOrderBy::StartTime), + "updated" => Ok(EventOrderBy::Updated), + other => Err(CalendarError::BadRequest(format!( + "Invalid order_by: {other}" + ))), + }) + .transpose()?; + + let google_req = hypr_google_calendar::ListEventsRequest { + calendar_id: req.calendar_id, + time_min, + time_max, + max_results: req.max_results, + page_token: req.page_token, + single_events: req.single_events, + order_by, + ..Default::default() + }; + + let response = client + .0 + .list_events(google_req) + .await + .map_err(|e| CalendarError::Internal(e.to_string()))?; + + Ok(Json(response)) +} diff --git a/crates/api-calendar/src/lib.rs b/crates/api-calendar/src/lib.rs index d783643fd5..c668cf7196 100644 --- a/crates/api-calendar/src/lib.rs +++ b/crates/api-calendar/src/lib.rs @@ -1,9 +1,14 @@ mod error; +mod google; mod openapi; -mod provider; -mod providers; -mod routes; +mod outlook; + +use axum::Router; pub use openapi::openapi; -pub use provider::CalendarConfig; -pub use routes::router; + +pub fn router() -> Router { + Router::new() + .nest("/google", google::router()) + .nest("/outlook", outlook::router()) +} diff --git a/crates/api-calendar/src/openapi.rs b/crates/api-calendar/src/openapi.rs index ef9cb8ce40..2a4ab3e637 100644 --- a/crates/api-calendar/src/openapi.rs +++ b/crates/api-calendar/src/openapi.rs @@ -1,25 +1,17 @@ use utoipa::OpenApi; -use crate::routes::ListEventsResponse; - #[derive(OpenApi)] #[openapi( paths( - crate::routes::calendar::list_calendars, - crate::routes::calendar::list_events, - crate::routes::calendar::create_event, - ), - components( - schemas( - crate::routes::calendar::ListCalendarsResponse, - crate::routes::calendar::ListEventsRequest, - ListEventsResponse, - crate::routes::calendar::CreateEventRequest, - crate::routes::calendar::CreateEventResponse, - crate::routes::calendar::EventDateTime, - crate::routes::calendar::EventAttendee, - ) + crate::google::routes::list_calendars, + crate::google::routes::list_events, + crate::outlook::routes::list_calendars, + crate::outlook::routes::list_events, ), + components(schemas( + crate::google::routes::GoogleListEventsRequest, + crate::outlook::routes::OutlookListEventsRequest, + )), tags( (name = "calendar", description = "Calendar management") ) @@ -27,5 +19,8 @@ use crate::routes::ListEventsResponse; struct ApiDoc; pub fn openapi() -> utoipa::openapi::OpenApi { - ApiDoc::openapi() + let mut doc = ApiDoc::openapi(); + doc.merge(hypr_google_calendar::openapi::openapi()); + doc.merge(hypr_outlook_calendar::openapi::openapi()); + doc } diff --git a/crates/api-calendar/src/outlook/mod.rs b/crates/api-calendar/src/outlook/mod.rs new file mode 100644 index 0000000000..06292ea930 --- /dev/null +++ b/crates/api-calendar/src/outlook/mod.rs @@ -0,0 +1,9 @@ +pub(crate) mod routes; + +use axum::{Router, routing::post}; + +pub fn router() -> Router { + Router::new() + .route("/list-calendars", post(routes::list_calendars)) + .route("/list-events", post(routes::list_events)) +} diff --git a/crates/api-calendar/src/outlook/routes.rs b/crates/api-calendar/src/outlook/routes.rs new file mode 100644 index 0000000000..ed2d7f7f79 --- /dev/null +++ b/crates/api-calendar/src/outlook/routes.rs @@ -0,0 +1,115 @@ +use axum::Json; +use axum::extract::FromRequestParts; +use axum::http::request::Parts; +use hypr_api_nango::{NangoConnection, OutlookCalendar}; +use hypr_nango::OwnedNangoHttpClient; +use hypr_outlook_calendar::{ListCalendarsResponse, ListEventsResponse, OutlookCalendarClient}; +use serde::Deserialize; +use utoipa::ToSchema; + +use crate::error::{CalendarError, Result}; + +pub(crate) struct OutlookClient(OutlookCalendarClient); + +impl FromRequestParts for OutlookClient { + type Rejection = CalendarError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let conn = NangoConnection::::from_request_parts(parts, state).await?; + Ok(OutlookClient(OutlookCalendarClient::new(conn.into_http()))) + } +} + +#[derive(Debug, Deserialize, ToSchema)] +pub struct OutlookListEventsRequest { + pub calendar_id: String, + #[serde(default)] + pub time_min: Option, + #[serde(default)] + pub time_max: Option, + #[serde(default)] + pub max_results: Option, + #[serde(default)] + pub order_by: Option, +} + +#[utoipa::path( + post, + path = "/outlook/list-calendars", + operation_id = "outlook_list_calendars", + responses( + (status = 200, description = "Outlook calendars fetched", body = ListCalendarsResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error"), + ), + tag = "calendar", +)] +pub async fn list_calendars(client: OutlookClient) -> Result> { + let response = client + .0 + .list_calendars() + .await + .map_err(|e| CalendarError::Internal(e.to_string()))?; + + Ok(Json(response)) +} + +#[utoipa::path( + post, + path = "/outlook/list-events", + operation_id = "outlook_list_events", + request_body = OutlookListEventsRequest, + responses( + (status = 200, description = "Outlook events fetched", body = ListEventsResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error"), + ), + tag = "calendar", +)] +pub async fn list_events( + client: OutlookClient, + Json(req): Json, +) -> Result> { + let start_date_time = req + .time_min + .as_deref() + .map(|s| { + chrono::DateTime::parse_from_rfc3339(s) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .map_err(|e| CalendarError::BadRequest(format!("Invalid time_min: {e}"))) + }) + .transpose()?; + + let end_date_time = req + .time_max + .as_deref() + .map(|s| { + chrono::DateTime::parse_from_rfc3339(s) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .map_err(|e| CalendarError::BadRequest(format!("Invalid time_max: {e}"))) + }) + .transpose()?; + + let order_by = req.order_by.as_deref().map(|s| match s { + "startTime" => "start/dateTime".to_string(), + "updated" => "lastModifiedDateTime".to_string(), + other => other.to_string(), + }); + + let outlook_req = hypr_outlook_calendar::ListEventsRequest { + calendar_id: req.calendar_id, + start_date_time, + end_date_time, + top: req.max_results, + order_by, + ..Default::default() + }; + + let response = client + .0 + .list_events(outlook_req) + .await + .map_err(|e| CalendarError::Internal(e.to_string()))?; + + Ok(Json(response)) +} diff --git a/crates/api-calendar/src/provider.rs b/crates/api-calendar/src/provider.rs deleted file mode 100644 index 6139706f96..0000000000 --- a/crates/api-calendar/src/provider.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::sync::Arc; - -use axum::extract::FromRequestParts; -use axum::http::request::Parts; -use hypr_api_nango::{GoogleCalendar, NangoConnection, NangoConnectionError, OutlookCalendar}; - -use crate::error::CalendarError; -use crate::providers::google::GoogleAdapter; -use crate::providers::outlook::OutlookAdapter; - -pub struct ListCalendarsResult { - pub calendars: Vec, -} - -pub struct ListEventsResult { - pub events: Vec, - pub next_page_token: Option, -} - -pub struct CreateEventResult { - pub event: serde_json::Value, -} - -#[derive(Clone, Default)] -pub struct CalendarConfig { - pub google: bool, - pub outlook: bool, -} - -pub enum CalendarClient { - Google(GoogleAdapter), - Outlook(OutlookAdapter), -} - -impl CalendarClient { - pub async fn list_calendars(&self) -> Result { - match self { - Self::Google(a) => a.list_calendars().await, - Self::Outlook(a) => a.list_calendars().await, - } - } - - pub async fn list_events( - &self, - req: crate::routes::calendar::ListEventsRequest, - ) -> Result { - match self { - Self::Google(a) => a.list_events(req).await, - Self::Outlook(a) => a.list_events(req).await, - } - } - - pub async fn create_event( - &self, - req: crate::routes::calendar::CreateEventRequest, - ) -> Result { - match self { - Self::Google(a) => a.create_event(req).await, - Self::Outlook(a) => a.create_event(req).await, - } - } -} - -impl FromRequestParts for CalendarClient { - type Rejection = CalendarError; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let config = parts - .extensions - .get::>() - .ok_or(CalendarError::Internal("missing CalendarConfig".into()))? - .clone(); - - if config.google { - match NangoConnection::::from_request_parts(parts, state).await { - Ok(conn) => { - return Ok(CalendarClient::Google(GoogleAdapter::new(conn.into_http()))); - } - Err(NangoConnectionError::NotConnected(_)) => {} - Err(e) => return Err(CalendarError::NangoConnection(e)), - } - } - - if config.outlook { - match NangoConnection::::from_request_parts(parts, state).await { - Ok(conn) => { - return Ok(CalendarClient::Outlook(OutlookAdapter::new( - conn.into_http(), - ))); - } - Err(NangoConnectionError::NotConnected(_)) => {} - Err(e) => return Err(CalendarError::NangoConnection(e)), - } - } - - Err(CalendarError::BadRequest( - "No calendar provider connected".into(), - )) - } -} diff --git a/crates/api-calendar/src/providers/google.rs b/crates/api-calendar/src/providers/google.rs deleted file mode 100644 index 9be3278b4b..0000000000 --- a/crates/api-calendar/src/providers/google.rs +++ /dev/null @@ -1,169 +0,0 @@ -use hypr_google_calendar::GoogleCalendarClient; -use hypr_nango::OwnedNangoHttpClient; - -use crate::error::CalendarError; -use crate::provider::{CreateEventResult, ListCalendarsResult, ListEventsResult}; -use crate::routes::calendar::{CreateEventRequest, EventDateTime, ListEventsRequest}; - -pub struct GoogleAdapter { - client: GoogleCalendarClient, -} - -impl GoogleAdapter { - pub fn new(http: OwnedNangoHttpClient) -> Self { - Self { - client: GoogleCalendarClient::new(http), - } - } - - pub async fn list_calendars(&self) -> Result { - let response = self - .client - .list_calendars() - .await - .map_err(|e| CalendarError::Internal(e.to_string()))?; - - let calendars = response - .items - .iter() - .map(|c| serde_json::to_value(c).unwrap_or_default()) - .collect(); - - Ok(ListCalendarsResult { calendars }) - } - - pub async fn list_events( - &self, - req: ListEventsRequest, - ) -> Result { - let time_min = req - .time_min - .as_deref() - .map(|s| { - chrono::DateTime::parse_from_rfc3339(s) - .map(|dt| dt.with_timezone(&chrono::Utc)) - .map_err(|e| CalendarError::BadRequest(format!("Invalid time_min: {e}"))) - }) - .transpose()?; - - let time_max = req - .time_max - .as_deref() - .map(|s| { - chrono::DateTime::parse_from_rfc3339(s) - .map(|dt| dt.with_timezone(&chrono::Utc)) - .map_err(|e| CalendarError::BadRequest(format!("Invalid time_max: {e}"))) - }) - .transpose()?; - - let order_by = req - .order_by - .as_deref() - .map(|s| match s { - "startTime" => Ok(hypr_google_calendar::EventOrderBy::StartTime), - "updated" => Ok(hypr_google_calendar::EventOrderBy::Updated), - other => Err(CalendarError::BadRequest(format!( - "Invalid order_by: {other}" - ))), - }) - .transpose()?; - - let google_req = hypr_google_calendar::ListEventsRequest { - calendar_id: req.calendar_id, - time_min, - time_max, - max_results: req.max_results, - page_token: req.page_token, - single_events: req.single_events, - order_by, - ..Default::default() - }; - - let response = self - .client - .list_events(google_req) - .await - .map_err(|e| CalendarError::Internal(e.to_string()))?; - - let events = response - .items - .iter() - .map(|e| serde_json::to_value(e).unwrap_or_default()) - .collect(); - - Ok(ListEventsResult { - events, - next_page_token: response.next_page_token, - }) - } - - pub async fn create_event( - &self, - req: CreateEventRequest, - ) -> Result { - let start = convert_event_datetime(req.start, "start")?; - let end = convert_event_datetime(req.end, "end")?; - - let google_req = hypr_google_calendar::CreateEventRequest { - calendar_id: req.calendar_id, - event: hypr_google_calendar::CreateEventBody { - summary: req.summary, - start, - end, - description: req.description, - location: req.location, - attendees: req.attendees.map(|attendees| { - attendees - .into_iter() - .map(|a| hypr_google_calendar::Attendee { - email: Some(a.email), - display_name: a.display_name, - optional: a.optional, - ..Default::default() - }) - .collect() - }), - ..Default::default() - }, - }; - - let event = self - .client - .create_event(google_req) - .await - .map_err(|e| CalendarError::Internal(e.to_string()))?; - - let event = serde_json::to_value(event).unwrap_or_default(); - Ok(CreateEventResult { event }) - } -} - -fn parse_date(s: &str, field: &str) -> Result { - chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") - .map_err(|e| CalendarError::BadRequest(format!("Invalid {field}: {e}"))) -} - -fn parse_datetime( - s: &str, - field: &str, -) -> Result, CalendarError> { - chrono::DateTime::parse_from_rfc3339(s) - .map_err(|e| CalendarError::BadRequest(format!("Invalid {field}: {e}"))) -} - -fn convert_event_datetime( - dt: EventDateTime, - prefix: &str, -) -> Result { - Ok(hypr_google_calendar::EventDateTime { - date: dt - .date - .map(|s| parse_date(&s, &format!("{prefix}.date"))) - .transpose()?, - date_time: dt - .date_time - .map(|s| parse_datetime(&s, &format!("{prefix}.dateTime"))) - .transpose()?, - time_zone: dt.time_zone, - }) -} diff --git a/crates/api-calendar/src/providers/mod.rs b/crates/api-calendar/src/providers/mod.rs deleted file mode 100644 index 31c48e3da4..0000000000 --- a/crates/api-calendar/src/providers/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod google; -pub mod outlook; diff --git a/crates/api-calendar/src/providers/outlook.rs b/crates/api-calendar/src/providers/outlook.rs deleted file mode 100644 index e650080790..0000000000 --- a/crates/api-calendar/src/providers/outlook.rs +++ /dev/null @@ -1,168 +0,0 @@ -use hypr_nango::OwnedNangoHttpClient; -use hypr_outlook_calendar::OutlookCalendarClient; - -use crate::error::CalendarError; -use crate::provider::{CreateEventResult, ListCalendarsResult, ListEventsResult}; -use crate::routes::calendar::{CreateEventRequest, ListEventsRequest}; - -pub struct OutlookAdapter { - client: OutlookCalendarClient, -} - -impl OutlookAdapter { - pub fn new(http: OwnedNangoHttpClient) -> Self { - Self { - client: OutlookCalendarClient::new(http), - } - } - - pub async fn list_calendars(&self) -> Result { - let response = self - .client - .list_calendars() - .await - .map_err(|e| CalendarError::Internal(e.to_string()))?; - - let calendars = response - .value - .iter() - .map(|c| serde_json::to_value(c).unwrap_or_default()) - .collect(); - - Ok(ListCalendarsResult { calendars }) - } - - pub async fn list_events( - &self, - req: ListEventsRequest, - ) -> Result { - let start_date_time = req - .time_min - .as_deref() - .map(|s| { - chrono::DateTime::parse_from_rfc3339(s) - .map(|dt| dt.with_timezone(&chrono::Utc)) - .map_err(|e| CalendarError::BadRequest(format!("Invalid time_min: {e}"))) - }) - .transpose()?; - - let end_date_time = req - .time_max - .as_deref() - .map(|s| { - chrono::DateTime::parse_from_rfc3339(s) - .map(|dt| dt.with_timezone(&chrono::Utc)) - .map_err(|e| CalendarError::BadRequest(format!("Invalid time_max: {e}"))) - }) - .transpose()?; - - let order_by = req.order_by.as_deref().map(|s| match s { - "startTime" => "start/dateTime".to_string(), - "updated" => "lastModifiedDateTime".to_string(), - other => other.to_string(), - }); - - let outlook_req = hypr_outlook_calendar::ListEventsRequest { - calendar_id: req.calendar_id, - start_date_time, - end_date_time, - top: req.max_results, - order_by, - ..Default::default() - }; - - let response = self - .client - .list_events(outlook_req) - .await - .map_err(|e| CalendarError::Internal(e.to_string()))?; - - let events = response - .value - .iter() - .map(|e| serde_json::to_value(e).unwrap_or_default()) - .collect(); - - Ok(ListEventsResult { - events, - next_page_token: response.odata_next_link, - }) - } - - pub async fn create_event( - &self, - req: CreateEventRequest, - ) -> Result { - let start = convert_to_outlook_datetime(&req.start)?; - let end = convert_to_outlook_datetime(&req.end)?; - - let outlook_req = hypr_outlook_calendar::CreateEventRequest { - calendar_id: req.calendar_id, - event: hypr_outlook_calendar::CreateEventBody { - subject: req.summary, - start, - end, - body: req.description.map(|d| hypr_outlook_calendar::ItemBody { - content_type: Some(hypr_outlook_calendar::BodyType::Text), - content: Some(d), - }), - location: req.location.map(|l| hypr_outlook_calendar::Location { - display_name: Some(l), - ..Default::default() - }), - attendees: req.attendees.map(|attendees| { - attendees - .into_iter() - .map(|a| hypr_outlook_calendar::Attendee { - email_address: Some(hypr_outlook_calendar::EmailAddress { - name: a.display_name, - address: Some(a.email), - }), - ..Default::default() - }) - .collect() - }), - ..Default::default() - }, - }; - - let event = self - .client - .create_event(outlook_req) - .await - .map_err(|e| CalendarError::Internal(e.to_string()))?; - - let event = serde_json::to_value(event).unwrap_or_default(); - Ok(CreateEventResult { event }) - } -} - -fn convert_to_outlook_datetime( - dt: &crate::routes::calendar::EventDateTime, -) -> Result { - if let Some(ref date_time_str) = dt.date_time { - let parsed = chrono::DateTime::parse_from_rfc3339(date_time_str) - .map_err(|e| CalendarError::BadRequest(format!("Invalid dateTime: {e}")))?; - - let time_zone = dt - .time_zone - .clone() - .unwrap_or_else(|| parsed.timezone().to_string()); - - let local = parsed.naive_local().format("%Y-%m-%dT%H:%M:%S").to_string(); - - Ok(hypr_outlook_calendar::DateTimeTimeZone { - date_time: local, - time_zone: Some(time_zone), - }) - } else if let Some(ref date_str) = dt.date { - Ok(hypr_outlook_calendar::DateTimeTimeZone { - date_time: format!("{date_str}T00:00:00"), - time_zone: dt.time_zone.clone(), - }) - } else { - Err(CalendarError::BadRequest( - "Either date or dateTime must be provided".into(), - )) - } -} diff --git a/crates/api-calendar/src/routes/calendar.rs b/crates/api-calendar/src/routes/calendar.rs deleted file mode 100644 index c6ea92c195..0000000000 --- a/crates/api-calendar/src/routes/calendar.rs +++ /dev/null @@ -1,133 +0,0 @@ -use axum::Json; -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; - -use crate::error::Result; -use crate::provider::CalendarClient; - -#[derive(Debug, Serialize, ToSchema)] -pub struct ListCalendarsResponse { - pub calendars: Vec, -} - -#[derive(Debug, Deserialize, ToSchema)] -pub struct ListEventsRequest { - pub calendar_id: String, - #[serde(default)] - pub time_min: Option, - #[serde(default)] - pub time_max: Option, - #[serde(default)] - pub max_results: Option, - #[serde(default)] - pub page_token: Option, - #[serde(default)] - pub single_events: Option, - #[serde(default)] - pub order_by: Option, -} - -#[derive(Debug, Serialize, ToSchema)] -pub struct ListEventsResponse { - pub events: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub next_page_token: Option, -} - -#[derive(Debug, Deserialize, ToSchema)] -pub struct CreateEventRequest { - pub calendar_id: String, - pub summary: String, - pub start: EventDateTime, - pub end: EventDateTime, - #[serde(default)] - pub description: Option, - #[serde(default)] - pub location: Option, - #[serde(default)] - pub attendees: Option>, -} - -#[derive(Debug, Deserialize, Serialize, ToSchema)] -pub struct EventDateTime { - #[serde(default)] - pub date: Option, - #[serde(default, rename = "dateTime")] - pub date_time: Option, - #[serde(default, rename = "timeZone")] - pub time_zone: Option, -} - -#[derive(Debug, Deserialize, Serialize, ToSchema)] -pub struct EventAttendee { - pub email: String, - #[serde(default, rename = "displayName")] - pub display_name: Option, - #[serde(default)] - pub optional: Option, -} - -#[derive(Debug, Serialize, ToSchema)] -pub struct CreateEventResponse { - pub event: serde_json::Value, -} - -#[utoipa::path( - post, - path = "/calendars", - responses( - (status = 200, description = "Calendars fetched", body = ListCalendarsResponse), - (status = 401, description = "Unauthorized"), - (status = 500, description = "Internal server error"), - ), - tag = "calendar", -)] -pub async fn list_calendars(client: CalendarClient) -> Result> { - let result = client.list_calendars().await?; - Ok(Json(ListCalendarsResponse { - calendars: result.calendars, - })) -} - -#[utoipa::path( - post, - path = "/events", - request_body = ListEventsRequest, - responses( - (status = 200, description = "Events fetched", body = ListEventsResponse), - (status = 401, description = "Unauthorized"), - (status = 500, description = "Internal server error"), - ), - tag = "calendar", -)] -pub async fn list_events( - client: CalendarClient, - Json(payload): Json, -) -> Result> { - let result = client.list_events(payload).await?; - Ok(Json(ListEventsResponse { - events: result.events, - next_page_token: result.next_page_token, - })) -} - -#[utoipa::path( - post, - path = "/events/create", - request_body = CreateEventRequest, - responses( - (status = 200, description = "Event created", body = CreateEventResponse), - (status = 401, description = "Unauthorized"), - (status = 500, description = "Internal server error"), - ), - tag = "calendar", -)] -pub async fn create_event( - client: CalendarClient, - Json(payload): Json, -) -> Result> { - let result = client.create_event(payload).await?; - Ok(Json(CreateEventResponse { - event: result.event, - })) -} diff --git a/crates/api-calendar/src/routes/mod.rs b/crates/api-calendar/src/routes/mod.rs deleted file mode 100644 index 5cd6817717..0000000000 --- a/crates/api-calendar/src/routes/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub(crate) mod calendar; - -use std::sync::Arc; - -use axum::{Router, routing::post}; - -pub use calendar::ListEventsResponse; - -use crate::provider::CalendarConfig; - -pub fn router(config: CalendarConfig) -> Router { - Router::new() - .route("/calendars", post(calendar::list_calendars)) - .route("/events", post(calendar::list_events)) - .route("/events/create", post(calendar::create_event)) - .layer(axum::Extension(Arc::new(config))) -} diff --git a/crates/api-client/Cargo.toml b/crates/api-client/Cargo.toml index e90ff0ae87..f2f07ddf7f 100644 --- a/crates/api-client/Cargo.toml +++ b/crates/api-client/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] +hypr-google-calendar = { workspace = true } +hypr-outlook-calendar = { workspace = true } + chrono = { workspace = true, features = ["serde"] } progenitor-client = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } diff --git a/crates/api-client/build.rs b/crates/api-client/build.rs index 64a92a712c..0a0a28ca7c 100644 --- a/crates/api-client/build.rs +++ b/crates/api-client/build.rs @@ -8,6 +8,25 @@ const ALLOWED_PATH_PREFIXES: &[&str] = &[ "/support", ]; +const TYPE_REPLACEMENTS: &[(&str, &str)] = &[ + ( + "GoogleListCalendarsResponse", + "hypr_google_calendar::ListCalendarsResponse", + ), + ( + "GoogleListEventsResponse", + "hypr_google_calendar::ListEventsResponse", + ), + ( + "OutlookListCalendarsResponse", + "hypr_outlook_calendar::ListCalendarsResponse", + ), + ( + "OutlookListEventsResponse", + "hypr_outlook_calendar::ListEventsResponse", + ), +]; + fn main() { let src = concat!( env!("CARGO_MANIFEST_DIR"), @@ -22,5 +41,5 @@ fn main() { .convert_31_to_30() .remove_unreferenced_schemas() .write_filtered(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("openapi.gen.json")) - .generate("codegen.rs"); + .generate_with_replacements("codegen.rs", TYPE_REPLACEMENTS); } diff --git a/crates/api-client/openapi.gen.json b/crates/api-client/openapi.gen.json index 2afafc9bcd..1b89334419 100644 --- a/crates/api-client/openapi.gen.json +++ b/crates/api-client/openapi.gen.json @@ -1,6 +1,272 @@ { "components": { "schemas": { + "AccessRole": { + "enum": [ + "freeBusyReader", + "reader", + "writer", + "owner", + "unknown" + ], + "type": "string" + }, + "AttendeeResponseStatus": { + "enum": [ + "needsAction", + "declined", + "tentative", + "accepted", + "unknown" + ], + "type": "string" + }, + "AttendeeType": { + "enum": [ + "required", + "optional", + "resource", + "unknown" + ], + "type": "string" + }, + "AutoDeclineMode": { + "enum": [ + "declineNone", + "declineAllConflictingInvitations", + "declineOnlyNewConflictingInvitations", + "unknown" + ], + "type": "string" + }, + "BirthdayProperties": { + "properties": { + "contact": { + "nullable": true, + "type": "string" + }, + "customTypeName": { + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/BirthdayPropertyType", + "nullable": true + } + }, + "type": "object" + }, + "BirthdayPropertyType": { + "enum": [ + "birthday", + "anniversary", + "self", + "other", + "custom", + "unknown" + ], + "type": "string" + }, + "BodyType": { + "enum": [ + "text", + "html", + "unknown" + ], + "type": "string" + }, + "Calendar": { + "properties": { + "allowedOnlineMeetingProviders": { + "items": { + "$ref": "#/components/schemas/OnlineMeetingProviderType" + }, + "nullable": true, + "type": "array" + }, + "canEdit": { + "nullable": true, + "type": "boolean" + }, + "canShare": { + "nullable": true, + "type": "boolean" + }, + "canViewPrivateItems": { + "nullable": true, + "type": "boolean" + }, + "changeKey": { + "nullable": true, + "type": "string" + }, + "color": { + "$ref": "#/components/schemas/CalendarColor", + "nullable": true + }, + "defaultOnlineMeetingProvider": { + "$ref": "#/components/schemas/OnlineMeetingProviderType", + "nullable": true + }, + "hexColor": { + "nullable": true, + "type": "string" + }, + "id": { + "type": "string" + }, + "isDefaultCalendar": { + "nullable": true, + "type": "boolean" + }, + "isRemovable": { + "nullable": true, + "type": "boolean" + }, + "isTallyingResponses": { + "nullable": true, + "type": "boolean" + }, + "name": { + "nullable": true, + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/EmailAddress", + "nullable": true + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "CalendarColor": { + "enum": [ + "auto", + "lightBlue", + "lightGreen", + "lightOrange", + "lightGray", + "lightYellow", + "lightTeal", + "lightPink", + "lightBrown", + "lightRed", + "maxColor", + "unknown" + ], + "type": "string" + }, + "CalendarListEntry": { + "properties": { + "accessRole": { + "$ref": "#/components/schemas/AccessRole", + "nullable": true + }, + "autoAcceptInvitations": { + "nullable": true, + "type": "boolean" + }, + "backgroundColor": { + "nullable": true, + "type": "string" + }, + "colorId": { + "nullable": true, + "type": "string" + }, + "conferenceProperties": { + "$ref": "#/components/schemas/ConferenceProperties", + "nullable": true + }, + "dataOwner": { + "nullable": true, + "type": "string" + }, + "defaultReminders": { + "items": { + "$ref": "#/components/schemas/Reminder" + }, + "nullable": true, + "type": "array" + }, + "deleted": { + "nullable": true, + "type": "boolean" + }, + "description": { + "nullable": true, + "type": "string" + }, + "etag": { + "nullable": true, + "type": "string" + }, + "foregroundColor": { + "nullable": true, + "type": "string" + }, + "hidden": { + "nullable": true, + "type": "boolean" + }, + "id": { + "type": "string" + }, + "kind": { + "nullable": true, + "type": "string" + }, + "location": { + "nullable": true, + "type": "string" + }, + "notificationSettings": { + "$ref": "#/components/schemas/NotificationSettings", + "nullable": true + }, + "primary": { + "nullable": true, + "type": "boolean" + }, + "selected": { + "nullable": true, + "type": "boolean" + }, + "summary": { + "nullable": true, + "type": "string" + }, + "summaryOverride": { + "nullable": true, + "type": "string" + }, + "timeZone": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "CalendarNotification": { + "properties": { + "method": { + "$ref": "#/components/schemas/NotificationMethod" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" + } + }, + "required": [ + "method", + "type" + ], + "type": "object" + }, "CanStartTrialReason": { "enum": [ "eligible", @@ -25,6 +291,133 @@ ], "type": "object" }, + "ChatStatus": { + "enum": [ + "available", + "doNotDisturb", + "unknown" + ], + "type": "string" + }, + "ConferenceCreateRequest": { + "properties": { + "conferenceSolutionKey": { + "$ref": "#/components/schemas/ConferenceSolutionKey", + "nullable": true + }, + "requestId": { + "nullable": true, + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/ConferenceCreateRequestStatus", + "nullable": true + } + }, + "type": "object" + }, + "ConferenceCreateRequestStatus": { + "properties": { + "statusCode": { + "$ref": "#/components/schemas/ConferenceCreateStatusCode" + } + }, + "required": [ + "statusCode" + ], + "type": "object" + }, + "ConferenceCreateStatusCode": { + "enum": [ + "pending", + "success", + "failure", + "unknown" + ], + "type": "string" + }, + "ConferenceData": { + "properties": { + "conferenceId": { + "nullable": true, + "type": "string" + }, + "conferenceSolution": { + "$ref": "#/components/schemas/ConferenceSolution", + "nullable": true + }, + "createRequest": { + "$ref": "#/components/schemas/ConferenceCreateRequest", + "nullable": true + }, + "entryPoints": { + "items": { + "$ref": "#/components/schemas/EntryPoint" + }, + "nullable": true, + "type": "array" + }, + "notes": { + "nullable": true, + "type": "string" + }, + "signature": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "ConferenceProperties": { + "properties": { + "allowedConferenceSolutionTypes": { + "items": { + "$ref": "#/components/schemas/ConferenceSolutionType" + }, + "nullable": true, + "type": "array" + } + }, + "type": "object" + }, + "ConferenceSolution": { + "properties": { + "iconUri": { + "nullable": true, + "type": "string" + }, + "key": { + "$ref": "#/components/schemas/ConferenceSolutionKey", + "nullable": true + }, + "name": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "ConferenceSolutionKey": { + "properties": { + "type": { + "$ref": "#/components/schemas/ConferenceSolutionType" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "ConferenceSolutionType": { + "enum": [ + "addOn", + "hangoutsMeet", + "eventNamedHangout", + "eventHangout", + "unknown" + ], + "type": "string" + }, "ConnectSessionResponse": { "properties": { "expires_at": { @@ -146,68 +539,58 @@ ], "type": "object" }, - "CreateEventRequest": { + "CreateReconnectSessionRequest": { "properties": { - "attendees": { - "items": { - "$ref": "#/components/schemas/EventAttendee" - }, - "nullable": true, - "type": "array" - }, - "calendar_id": { - "type": "string" - }, - "description": { - "nullable": true, - "type": "string" - }, - "end": { - "$ref": "#/components/schemas/EventDateTime" - }, - "location": { - "nullable": true, + "connection_id": { "type": "string" }, - "start": { - "$ref": "#/components/schemas/EventDateTime" - }, - "summary": { + "integration_id": { "type": "string" } }, "required": [ - "calendar_id", - "summary", - "start", - "end" + "connection_id", + "integration_id" ], "type": "object" }, - "CreateEventResponse": { + "CustomLocation": { "properties": { - "event": {} + "label": { + "nullable": true, + "type": "string" + } }, - "required": [ - "event" - ], "type": "object" }, - "CreateReconnectSessionRequest": { + "DateTimeTimeZone": { "properties": { - "connection_id": { + "dateTime": { "type": "string" }, - "integration_id": { + "timeZone": { + "nullable": true, "type": "string" } }, "required": [ - "connection_id", - "integration_id" + "dateTime" ], "type": "object" }, + "DayOfWeek": { + "enum": [ + "sunday", + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "unknown" + ], + "type": "string" + }, "DeleteAccountResponse": { "properties": { "deleted": { @@ -254,249 +637,1515 @@ ], "type": "object" }, - "EventAttendee": { + "EmailAddress": { + "properties": { + "address": { + "nullable": true, + "type": "string" + }, + "name": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "EntryPoint": { + "properties": { + "accessCode": { + "nullable": true, + "type": "string" + }, + "entryPointType": { + "$ref": "#/components/schemas/EntryPointType" + }, + "label": { + "nullable": true, + "type": "string" + }, + "meetingCode": { + "nullable": true, + "type": "string" + }, + "passcode": { + "nullable": true, + "type": "string" + }, + "password": { + "nullable": true, + "type": "string" + }, + "pin": { + "nullable": true, + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "entryPointType", + "uri" + ], + "type": "object" + }, + "EntryPointType": { + "enum": [ + "video", + "phone", + "sip", + "more", + "unknown" + ], + "type": "string" + }, + "EventAttachment": { + "properties": { + "fileId": { + "nullable": true, + "type": "string" + }, + "fileUrl": { + "nullable": true, + "type": "string" + }, + "iconLink": { + "nullable": true, + "type": "string" + }, + "mimeType": { + "nullable": true, + "type": "string" + }, + "title": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "EventDateTime": { + "properties": { + "date": { + "format": "date", + "nullable": true, + "type": "string" + }, + "dateTime": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "timeZone": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "EventPerson": { "properties": { "displayName": { "nullable": true, "type": "string" }, "email": { + "nullable": true, "type": "string" }, - "optional": { + "id": { + "nullable": true, + "type": "string" + }, + "self": { + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "EventShowAs": { + "enum": [ + "free", + "tentative", + "busy", + "oof", + "workingElsewhere", + "unknown", + "other" + ], + "type": "string" + }, + "EventSource": { + "properties": { + "title": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "url", + "title" + ], + "type": "object" + }, + "EventStatus": { + "enum": [ + "confirmed", + "tentative", + "cancelled", + "unknown" + ], + "type": "string" + }, + "ExtendedProperties": { + "properties": { + "private": { + "additionalProperties": { + "type": "string" + }, + "nullable": true, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "shared": { + "additionalProperties": { + "type": "string" + }, + "nullable": true, + "propertyNames": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "FeedbackRequest": { + "properties": { + "description": { + "type": "string" + }, + "deviceInfo": { + "$ref": "#/components/schemas/DeviceInfo" + }, + "logs": { + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/FeedbackType" + } + }, + "required": [ + "description", + "deviceInfo" + ], + "type": "object" + }, + "FeedbackResponse": { + "properties": { + "error": { + "nullable": true, + "type": "string" + }, + "issueUrl": { "nullable": true, + "type": "string" + }, + "success": { "type": "boolean" } }, "required": [ - "email" + "success" ], "type": "object" }, - "EventDateTime": { + "FeedbackType": { + "enum": [ + "bug", + "feature" + ], + "type": "string" + }, + "FocusTimeProperties": { "properties": { - "date": { + "autoDeclineMode": { + "$ref": "#/components/schemas/AutoDeclineMode", + "nullable": true + }, + "chatStatus": { + "$ref": "#/components/schemas/ChatStatus", + "nullable": true + }, + "declineMessage": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "Gadget": { + "properties": { + "display": { + "$ref": "#/components/schemas/GadgetDisplay", + "nullable": true + }, + "height": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "iconLink": { + "nullable": true, + "type": "string" + }, + "link": { + "nullable": true, + "type": "string" + }, + "preferences": { + "additionalProperties": { + "type": "string" + }, + "nullable": true, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "title": { + "nullable": true, + "type": "string" + }, + "type": { + "nullable": true, + "type": "string" + }, + "width": { + "format": "int32", + "nullable": true, + "type": "integer" + } + }, + "type": "object" + }, + "GadgetDisplay": { + "enum": [ + "chip", + "icon", + "unknown" + ], + "type": "string" + }, + "GoogleListEventsRequest": { + "properties": { + "calendar_id": { + "type": "string" + }, + "max_results": { + "format": "int32", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "order_by": { + "nullable": true, + "type": "string" + }, + "page_token": { + "nullable": true, + "type": "string" + }, + "single_events": { + "nullable": true, + "type": "boolean" + }, + "time_max": { + "nullable": true, + "type": "string" + }, + "time_min": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "calendar_id" + ], + "type": "object" + }, + "Importance": { + "enum": [ + "low", + "normal", + "high", + "unknown" + ], + "type": "string" + }, + "Interval": { + "enum": [ + "monthly", + "yearly" + ], + "type": "string" + }, + "ItemBody": { + "properties": { + "content": { + "nullable": true, + "type": "string" + }, + "contentType": { + "$ref": "#/components/schemas/BodyType", + "nullable": true + } + }, + "type": "object" + }, + "ListConnectionsResponse": { + "properties": { + "connections": { + "items": { + "$ref": "#/components/schemas/ConnectionItem" + }, + "type": "array" + } + }, + "required": [ + "connections" + ], + "type": "object" + }, + "Location": { + "properties": { + "address": { + "$ref": "#/components/schemas/PhysicalAddress", + "nullable": true + }, + "coordinates": { + "$ref": "#/components/schemas/OutlookGeoCoordinates", + "nullable": true + }, + "displayName": { + "nullable": true, + "type": "string" + }, + "locationType": { + "$ref": "#/components/schemas/LocationType", + "nullable": true + }, + "uniqueId": { + "nullable": true, + "type": "string" + }, + "uniqueIdType": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "LocationType": { + "enum": [ + "default", + "conferenceRoom", + "homeAddress", + "businessAddress", + "geoCoordinates", + "streetAddress", + "hotel", + "restaurant", + "localBusiness", + "postalAddress", + "unknown" + ], + "type": "string" + }, + "MessageResponse": { + "properties": { + "content": { + "nullable": true, + "type": "string" + }, + "createdAt": { + "nullable": true, + "type": "string" + }, + "id": { + "type": "string" + }, + "messageType": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "NotificationMethod": { + "enum": [ + "email", + "unknown" + ], + "type": "string" + }, + "NotificationSettings": { + "properties": { + "notifications": { + "items": { + "$ref": "#/components/schemas/CalendarNotification" + }, + "nullable": true, + "type": "array" + } + }, + "type": "object" + }, + "NotificationType": { + "enum": [ + "eventCreation", + "eventChange", + "eventCancellation", + "eventResponse", + "agenda", + "unknown" + ], + "type": "string" + }, + "OfficeLocation": { + "properties": { + "buildingId": { + "nullable": true, + "type": "string" + }, + "deskId": { + "nullable": true, + "type": "string" + }, + "floorId": { + "nullable": true, + "type": "string" + }, + "floorSectionId": { + "nullable": true, + "type": "string" + }, + "label": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "OnlineMeetingInfo": { + "properties": { + "conferenceId": { + "nullable": true, + "type": "string" + }, + "joinUrl": { + "nullable": true, + "type": "string" + }, + "quickDial": { + "nullable": true, + "type": "string" + }, + "tollFreeNumbers": { + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, + "tollNumber": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "OnlineMeetingProviderType": { + "enum": [ + "teamsForBusiness", + "skypeForBusiness", + "skypeForConsumer", + "unknown", + "other" + ], + "type": "string" + }, + "OutOfOfficeProperties": { + "properties": { + "autoDeclineMode": { + "$ref": "#/components/schemas/AutoDeclineMode", + "nullable": true + }, + "declineMessage": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "OutlookGeoCoordinates": { + "properties": { + "accuracy": { + "format": "double", + "nullable": true, + "type": "number" + }, + "altitude": { + "format": "double", + "nullable": true, + "type": "number" + }, + "altitudeAccuracy": { + "format": "double", + "nullable": true, + "type": "number" + }, + "latitude": { + "format": "double", + "nullable": true, + "type": "number" + }, + "longitude": { + "format": "double", + "nullable": true, + "type": "number" + } + }, + "type": "object" + }, + "OutlookListEventsRequest": { + "properties": { + "calendar_id": { + "type": "string" + }, + "max_results": { + "format": "int32", + "minimum": 0, + "nullable": true, + "type": "integer" + }, + "order_by": { + "nullable": true, + "type": "string" + }, + "time_max": { + "nullable": true, + "type": "string" + }, + "time_min": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "calendar_id" + ], + "type": "object" + }, + "PatternedRecurrence": { + "properties": { + "pattern": { + "$ref": "#/components/schemas/RecurrencePattern", + "nullable": true + }, + "range": { + "$ref": "#/components/schemas/RecurrenceRange", + "nullable": true + } + }, + "type": "object" + }, + "PhysicalAddress": { + "properties": { + "city": { + "nullable": true, + "type": "string" + }, + "countryOrRegion": { + "nullable": true, + "type": "string" + }, + "postalCode": { + "nullable": true, + "type": "string" + }, + "state": { + "nullable": true, + "type": "string" + }, + "street": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "Recipient": { + "properties": { + "emailAddress": { + "$ref": "#/components/schemas/EmailAddress", + "nullable": true + } + }, + "type": "object" + }, + "RecurrencePattern": { + "properties": { + "dayOfMonth": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "daysOfWeek": { + "items": { + "$ref": "#/components/schemas/DayOfWeek" + }, + "nullable": true, + "type": "array" + }, + "firstDayOfWeek": { + "$ref": "#/components/schemas/DayOfWeek", + "nullable": true + }, + "index": { + "$ref": "#/components/schemas/WeekIndex", + "nullable": true + }, + "interval": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "month": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "type": { + "$ref": "#/components/schemas/RecurrencePatternType", + "nullable": true + } + }, + "type": "object" + }, + "RecurrencePatternType": { + "enum": [ + "daily", + "weekly", + "absoluteMonthly", + "relativeMonthly", + "absoluteYearly", + "relativeYearly", + "unknown" + ], + "type": "string" + }, + "RecurrenceRange": { + "properties": { + "endDate": { + "nullable": true, + "type": "string" + }, + "numberOfOccurrences": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "recurrenceTimeZone": { + "nullable": true, + "type": "string" + }, + "startDate": { + "nullable": true, + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/RecurrenceRangeType", + "nullable": true + } + }, + "type": "object" + }, + "RecurrenceRangeType": { + "enum": [ + "endDate", + "noEnd", + "numbered", + "unknown" + ], + "type": "string" + }, + "Reminder": { + "properties": { + "method": { + "$ref": "#/components/schemas/ReminderMethod" + }, + "minutes": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "method", + "minutes" + ], + "type": "object" + }, + "ReminderMethod": { + "enum": [ + "email", + "popup", + "unknown" + ], + "type": "string" + }, + "Reminders": { + "properties": { + "overrides": { + "items": { + "$ref": "#/components/schemas/Reminder" + }, + "nullable": true, + "type": "array" + }, + "useDefault": { + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "ResponseStatus": { + "properties": { + "response": { + "$ref": "#/components/schemas/ResponseType", + "nullable": true + }, + "time": { + "format": "date-time", + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "ResponseType": { + "enum": [ + "none", + "organizer", + "tentativelyAccepted", + "accepted", + "declined", + "notResponded", + "unknown" + ], + "type": "string" + }, + "SendMessageRequest": { + "properties": { + "content": { + "type": "string" + }, + "messageType": { + "type": "string" + }, + "sourceId": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "Sensitivity": { + "enum": [ + "normal", + "personal", + "private", + "confidential", + "unknown" + ], + "type": "string" + }, + "StartTrialReason": { + "enum": [ + "started", + "not_eligible" + ], + "type": "string" + }, + "StartTrialResponse": { + "properties": { + "reason": { + "$ref": "#/components/schemas/StartTrialReason", + "nullable": true + }, + "started": { + "example": true, + "type": "boolean" + } + }, + "required": [ + "started" + ], + "type": "object" + }, + "Transparency": { + "enum": [ + "opaque", + "transparent", + "unknown" + ], + "type": "string" + }, + "Visibility": { + "enum": [ + "default", + "public", + "private", + "confidential", + "unknown" + ], + "type": "string" + }, + "WebhookResponse": { + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "WeekIndex": { + "enum": [ + "first", + "second", + "third", + "fourth", + "last", + "unknown" + ], + "type": "string" + }, + "WorkingLocationProperties": { + "properties": { + "customLocation": { + "$ref": "#/components/schemas/CustomLocation", + "nullable": true + }, + "homeOffice": {}, + "officeLocation": { + "$ref": "#/components/schemas/OfficeLocation", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/WorkingLocationType", + "nullable": true + } + }, + "type": "object" + }, + "WorkingLocationType": { + "enum": [ + "homeOffice", + "officeLocation", + "customLocation", + "unknown" + ], + "type": "string" + }, + "google.Attendee": { + "properties": { + "additionalGuests": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "comment": { + "nullable": true, + "type": "string" + }, + "displayName": { + "nullable": true, + "type": "string" + }, + "email": { + "nullable": true, + "type": "string" + }, + "id": { + "nullable": true, + "type": "string" + }, + "optional": { + "nullable": true, + "type": "boolean" + }, + "organizer": { + "nullable": true, + "type": "boolean" + }, + "resource": { + "nullable": true, + "type": "boolean" + }, + "responseStatus": { + "$ref": "#/components/schemas/AttendeeResponseStatus", + "nullable": true + }, + "self": { + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "google.Event": { + "properties": { + "anyoneCanAddSelf": { + "nullable": true, + "type": "boolean" + }, + "attachments": { + "items": { + "$ref": "#/components/schemas/EventAttachment" + }, + "nullable": true, + "type": "array" + }, + "attendees": { + "items": { + "$ref": "#/components/schemas/google.Attendee" + }, + "nullable": true, + "type": "array" + }, + "attendeesOmitted": { + "nullable": true, + "type": "boolean" + }, + "birthdayProperties": { + "$ref": "#/components/schemas/BirthdayProperties", + "nullable": true + }, + "colorId": { + "nullable": true, + "type": "string" + }, + "conferenceData": { + "$ref": "#/components/schemas/ConferenceData", + "nullable": true + }, + "created": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "creator": { + "$ref": "#/components/schemas/EventPerson", + "nullable": true + }, + "description": { + "nullable": true, + "type": "string" + }, + "end": { + "$ref": "#/components/schemas/EventDateTime", + "nullable": true + }, + "endTimeUnspecified": { + "nullable": true, + "type": "boolean" + }, + "etag": { + "nullable": true, + "type": "string" + }, + "eventType": { + "$ref": "#/components/schemas/google.EventType", + "nullable": true + }, + "extendedProperties": { + "$ref": "#/components/schemas/ExtendedProperties", + "nullable": true + }, + "focusTimeProperties": { + "$ref": "#/components/schemas/FocusTimeProperties", + "nullable": true + }, + "gadget": { + "$ref": "#/components/schemas/Gadget", + "nullable": true + }, + "guestsCanInviteOthers": { + "nullable": true, + "type": "boolean" + }, + "guestsCanModify": { + "nullable": true, + "type": "boolean" + }, + "guestsCanSeeOtherGuests": { + "nullable": true, + "type": "boolean" + }, + "hangoutLink": { + "nullable": true, + "type": "string" + }, + "htmlLink": { + "nullable": true, + "type": "string" + }, + "iCalUID": { + "nullable": true, + "type": "string" + }, + "id": { + "type": "string" + }, + "kind": { + "nullable": true, + "type": "string" + }, + "location": { + "nullable": true, + "type": "string" + }, + "locked": { + "nullable": true, + "type": "boolean" + }, + "organizer": { + "$ref": "#/components/schemas/EventPerson", + "nullable": true + }, + "originalStartTime": { + "$ref": "#/components/schemas/EventDateTime", + "nullable": true + }, + "outOfOfficeProperties": { + "$ref": "#/components/schemas/OutOfOfficeProperties", + "nullable": true + }, + "privateCopy": { + "nullable": true, + "type": "boolean" + }, + "recurrence": { + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, + "recurringEventId": { + "nullable": true, + "type": "string" + }, + "reminders": { + "$ref": "#/components/schemas/Reminders", + "nullable": true + }, + "sequence": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "source": { + "$ref": "#/components/schemas/EventSource", + "nullable": true + }, + "start": { + "$ref": "#/components/schemas/EventDateTime", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/EventStatus", + "nullable": true + }, + "summary": { + "nullable": true, + "type": "string" + }, + "transparency": { + "$ref": "#/components/schemas/Transparency", + "nullable": true + }, + "updated": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "visibility": { + "$ref": "#/components/schemas/Visibility", + "nullable": true + }, + "workingLocationProperties": { + "$ref": "#/components/schemas/WorkingLocationProperties", + "nullable": true + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "google.EventType": { + "enum": [ + "default", + "birthday", + "focusTime", + "fromGmail", + "outOfOffice", + "workingLocation", + "unknown" + ], + "type": "string" + }, + "google.ListCalendarsResponse": { + "properties": { + "etag": { + "nullable": true, + "type": "string" + }, + "items": { + "items": { + "$ref": "#/components/schemas/CalendarListEntry" + }, + "type": "array" + }, + "kind": { + "nullable": true, + "type": "string" + }, + "nextPageToken": { + "nullable": true, + "type": "string" + }, + "nextSyncToken": { + "nullable": true, + "type": "string" + } + }, + "type": "object" + }, + "google.ListEventsResponse": { + "properties": { + "accessRole": { + "$ref": "#/components/schemas/AccessRole", + "nullable": true + }, + "defaultReminders": { + "items": { + "$ref": "#/components/schemas/Reminder" + }, + "nullable": true, + "type": "array" + }, + "description": { + "nullable": true, + "type": "string" + }, + "etag": { + "nullable": true, + "type": "string" + }, + "items": { + "items": { + "$ref": "#/components/schemas/google.Event" + }, + "type": "array" + }, + "kind": { "nullable": true, "type": "string" }, - "dateTime": { + "nextPageToken": { "nullable": true, "type": "string" }, - "timeZone": { + "nextSyncToken": { "nullable": true, "type": "string" - } - }, - "type": "object" - }, - "FeedbackRequest": { - "properties": { - "description": { - "type": "string" - }, - "deviceInfo": { - "$ref": "#/components/schemas/DeviceInfo" }, - "logs": { + "summary": { "nullable": true, "type": "string" }, - "type": { - "$ref": "#/components/schemas/FeedbackType" - } - }, - "required": [ - "description", - "deviceInfo" - ], - "type": "object" - }, - "FeedbackResponse": { - "properties": { - "error": { + "timeZone": { "nullable": true, "type": "string" }, - "issueUrl": { + "updated": { + "format": "date-time", "nullable": true, "type": "string" - }, - "success": { - "type": "boolean" } }, - "required": [ - "success" - ], "type": "object" }, - "FeedbackType": { - "enum": [ - "bug", - "feature" - ], - "type": "string" - }, - "Interval": { - "enum": [ - "monthly", - "yearly" - ], - "type": "string" - }, - "ListCalendarsResponse": { + "outlook.Attendee": { "properties": { - "calendars": { - "items": {}, - "type": "array" + "emailAddress": { + "$ref": "#/components/schemas/EmailAddress", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/ResponseStatus", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/AttendeeType", + "nullable": true } }, - "required": [ - "calendars" - ], "type": "object" }, - "ListConnectionsResponse": { + "outlook.Event": { "properties": { - "connections": { + "allowNewTimeProposals": { + "nullable": true, + "type": "boolean" + }, + "attendees": { "items": { - "$ref": "#/components/schemas/ConnectionItem" + "$ref": "#/components/schemas/outlook.Attendee" }, + "nullable": true, "type": "array" - } - }, - "required": [ - "connections" - ], - "type": "object" - }, - "ListEventsRequest": { - "properties": { - "calendar_id": { + }, + "body": { + "$ref": "#/components/schemas/ItemBody", + "nullable": true + }, + "bodyPreview": { + "nullable": true, "type": "string" }, - "max_results": { - "format": "int32", - "minimum": 0, + "categories": { + "items": { + "type": "string" + }, "nullable": true, - "type": "integer" + "type": "array" }, - "order_by": { + "changeKey": { "nullable": true, "type": "string" }, - "page_token": { + "createdDateTime": { + "format": "date-time", "nullable": true, "type": "string" }, - "single_events": { + "end": { + "$ref": "#/components/schemas/DateTimeTimeZone", + "nullable": true + }, + "hasAttachments": { "nullable": true, "type": "boolean" }, - "time_max": { + "hideAttendees": { + "nullable": true, + "type": "boolean" + }, + "iCalUId": { "nullable": true, "type": "string" }, - "time_min": { + "id": { + "type": "string" + }, + "importance": { + "$ref": "#/components/schemas/Importance", + "nullable": true + }, + "isAllDay": { + "nullable": true, + "type": "boolean" + }, + "isCancelled": { + "nullable": true, + "type": "boolean" + }, + "isDraft": { + "nullable": true, + "type": "boolean" + }, + "isOnlineMeeting": { + "nullable": true, + "type": "boolean" + }, + "isOrganizer": { + "nullable": true, + "type": "boolean" + }, + "isReminderOn": { + "nullable": true, + "type": "boolean" + }, + "lastModifiedDateTime": { + "format": "date-time", "nullable": true, "type": "string" - } - }, - "required": [ - "calendar_id" - ], - "type": "object" - }, - "ListEventsResponse": { - "properties": { - "events": { - "items": {}, + }, + "location": { + "$ref": "#/components/schemas/Location", + "nullable": true + }, + "locations": { + "items": { + "$ref": "#/components/schemas/Location" + }, + "nullable": true, "type": "array" }, - "next_page_token": { + "onlineMeeting": { + "$ref": "#/components/schemas/OnlineMeetingInfo", + "nullable": true + }, + "onlineMeetingProvider": { + "$ref": "#/components/schemas/OnlineMeetingProviderType", + "nullable": true + }, + "onlineMeetingUrl": { "nullable": true, "type": "string" - } - }, - "required": [ - "events" - ], - "type": "object" - }, - "MessageResponse": { - "properties": { - "content": { + }, + "organizer": { + "$ref": "#/components/schemas/Recipient", + "nullable": true + }, + "originalEndTimeZone": { "nullable": true, "type": "string" }, - "createdAt": { + "originalStartTimeZone": { "nullable": true, "type": "string" }, - "id": { - "type": "string" + "recurrence": { + "$ref": "#/components/schemas/PatternedRecurrence", + "nullable": true }, - "messageType": { + "reminderMinutesBeforeStart": { + "format": "int32", + "nullable": true, + "type": "integer" + }, + "responseRequested": { + "nullable": true, + "type": "boolean" + }, + "responseStatus": { + "$ref": "#/components/schemas/ResponseStatus", + "nullable": true + }, + "sensitivity": { + "$ref": "#/components/schemas/Sensitivity", + "nullable": true + }, + "seriesMasterId": { "nullable": true, "type": "string" - } - }, - "required": [ - "id" - ], - "type": "object" - }, - "SendMessageRequest": { - "properties": { - "content": { + }, + "showAs": { + "$ref": "#/components/schemas/EventShowAs", + "nullable": true + }, + "start": { + "$ref": "#/components/schemas/DateTimeTimeZone", + "nullable": true + }, + "subject": { + "nullable": true, "type": "string" }, - "messageType": { + "transactionId": { + "nullable": true, "type": "string" }, - "sourceId": { + "type": { + "$ref": "#/components/schemas/outlook.EventType", + "nullable": true + }, + "webLink": { "nullable": true, "type": "string" } }, "required": [ - "content" + "id" ], "type": "object" }, - "StartTrialReason": { + "outlook.EventType": { "enum": [ - "started", - "not_eligible" + "singleInstance", + "occurrence", + "exception", + "seriesMaster", + "unknown" ], "type": "string" }, - "StartTrialResponse": { + "outlook.ListCalendarsResponse": { "properties": { - "reason": { - "$ref": "#/components/schemas/StartTrialReason", - "nullable": true + "@odata.context": { + "nullable": true, + "type": "string" }, - "started": { - "example": true, - "type": "boolean" + "@odata.nextLink": { + "nullable": true, + "type": "string" + }, + "value": { + "items": { + "$ref": "#/components/schemas/Calendar" + }, + "type": "array" } }, - "required": [ - "started" - ], "type": "object" }, - "WebhookResponse": { + "outlook.ListEventsResponse": { "properties": { - "status": { + "@odata.context": { + "nullable": true, + "type": "string" + }, + "@odata.nextLink": { + "nullable": true, "type": "string" + }, + "value": { + "items": { + "$ref": "#/components/schemas/outlook.Event" + }, + "type": "array" } }, - "required": [ - "status" - ], "type": "object" } }, @@ -519,19 +2168,19 @@ }, "openapi": "3.0.3", "paths": { - "/calendar/calendars": { + "/calendar/google/list-calendars": { "post": { - "operationId": "list_calendars", + "operationId": "google_list_calendars", "responses": { "200": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListCalendarsResponse" + "$ref": "#/components/schemas/google.ListCalendarsResponse" } } }, - "description": "Calendars fetched" + "description": "Google calendars fetched" }, "401": { "description": "Unauthorized" @@ -550,14 +2199,14 @@ ] } }, - "/calendar/events": { + "/calendar/google/list-events": { "post": { - "operationId": "list_events", + "operationId": "google_list_events", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListEventsRequest" + "$ref": "#/components/schemas/GoogleListEventsRequest" } } }, @@ -568,11 +2217,42 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ListEventsResponse" + "$ref": "#/components/schemas/google.ListEventsResponse" + } + } + }, + "description": "Google events fetched" + }, + "401": { + "description": "Unauthorized" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "tags": [ + "calendar" + ] + } + }, + "/calendar/outlook/list-calendars": { + "post": { + "operationId": "outlook_list_calendars", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/outlook.ListCalendarsResponse" } } }, - "description": "Events fetched" + "description": "Outlook calendars fetched" }, "401": { "description": "Unauthorized" @@ -591,14 +2271,14 @@ ] } }, - "/calendar/events/create": { + "/calendar/outlook/list-events": { "post": { - "operationId": "create_event", + "operationId": "outlook_list_events", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateEventRequest" + "$ref": "#/components/schemas/OutlookListEventsRequest" } } }, @@ -609,11 +2289,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateEventResponse" + "$ref": "#/components/schemas/outlook.ListEventsResponse" } } }, - "description": "Event created" + "description": "Outlook events fetched" }, "401": { "description": "Unauthorized" diff --git a/crates/api-subscription/src/routes/billing.rs b/crates/api-subscription/src/routes/billing.rs index f81bdc3074..e232d72d67 100644 --- a/crates/api-subscription/src/routes/billing.rs +++ b/crates/api-subscription/src/routes/billing.rs @@ -106,9 +106,13 @@ where if let Some(analytics) = analytics { let mut payload = outcome.to_analytics_payload(); payload.props.insert("source".to_string(), source.into()); - let _ = analytics.event(user_id, payload).await; + if let Err(e) = analytics.event(user_id, payload).await { + tracing::warn!("analytics event error: {e}"); + } if let Some(props) = outcome.to_analytics_properties() { - let _ = analytics.set_properties(user_id, props).await; + if let Err(e) = analytics.set_properties(user_id, props).await { + tracing::warn!("analytics set_properties error: {e}"); + } } } outcome.into_response() diff --git a/crates/google-calendar/Cargo.toml b/crates/google-calendar/Cargo.toml index c7c262e998..1f7ee41501 100644 --- a/crates/google-calendar/Cargo.toml +++ b/crates/google-calendar/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [features] default = [] specta = ["dep:specta"] +utoipa = ["dep:utoipa"] [dependencies] hypr-http = { workspace = true } @@ -16,6 +17,7 @@ serde_json = { workspace = true } specta = { workspace = true, optional = true, features = ["chrono"] } thiserror = { workspace = true } urlencoding = { workspace = true } +utoipa = { workspace = true, optional = true, features = ["chrono"] } [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/google-calendar/src/lib.rs b/crates/google-calendar/src/lib.rs index da550a2ce4..3bdbbb8491 100644 --- a/crates/google-calendar/src/lib.rs +++ b/crates/google-calendar/src/lib.rs @@ -2,6 +2,9 @@ mod client; mod error; mod types; +#[cfg(feature = "utoipa")] +pub mod openapi; + pub use client::GoogleCalendarClient; pub use error::Error; pub use types::*; diff --git a/crates/google-calendar/src/openapi.rs b/crates/google-calendar/src/openapi.rs new file mode 100644 index 0000000000..36ae838324 --- /dev/null +++ b/crates/google-calendar/src/openapi.rs @@ -0,0 +1,59 @@ +use utoipa::OpenApi; + +#[derive(OpenApi)] +#[openapi(components(schemas( + crate::AccessRole, + crate::Attendee, + crate::AttendeeResponseStatus, + crate::AutoDeclineMode, + crate::BirthdayProperties, + crate::BirthdayPropertyType, + crate::CalendarListEntry, + crate::CalendarNotification, + crate::ChatStatus, + crate::ConferenceCreateRequest, + crate::ConferenceCreateRequestStatus, + crate::ConferenceCreateStatusCode, + crate::ConferenceData, + crate::ConferenceProperties, + crate::ConferenceSolution, + crate::ConferenceSolutionKey, + crate::ConferenceSolutionType, + crate::CreateEventBody, + crate::CreateEventRequest, + crate::CustomLocation, + crate::EntryPoint, + crate::EntryPointType, + crate::Event, + crate::EventAttachment, + crate::EventDateTime, + crate::EventOrderBy, + crate::EventPerson, + crate::EventSource, + crate::EventStatus, + crate::EventType, + crate::ExtendedProperties, + crate::FocusTimeProperties, + crate::Gadget, + crate::GadgetDisplay, + crate::ListCalendarsResponse, + crate::ListEventsRequest, + crate::ListEventsResponse, + crate::NotificationMethod, + crate::NotificationSettings, + crate::NotificationType, + crate::OfficeLocation, + crate::OutOfOfficeProperties, + crate::Reminder, + crate::ReminderMethod, + crate::Reminders, + crate::Transparency, + crate::Visibility, + crate::WorkingLocationProperties, + crate::WorkingLocationType, +)))] +struct ApiDoc; + +pub fn openapi() -> utoipa::openapi::OpenApi { + ApiDoc::openapi() +} diff --git a/crates/google-calendar/src/types.rs b/crates/google-calendar/src/types.rs index 41ec7c76bc..e6ce160c72 100644 --- a/crates/google-calendar/src/types.rs +++ b/crates/google-calendar/src/types.rs @@ -6,10 +6,14 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "specta")] use specta::Type; +#[cfg(feature = "utoipa")] +use utoipa::ToSchema; + // === Enums (response-side with forward-compatible Unknown fallback) === #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum EventStatus { Confirmed, @@ -21,6 +25,7 @@ pub enum EventStatus { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum AccessRole { FreeBusyReader, @@ -33,6 +38,7 @@ pub enum AccessRole { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum AttendeeResponseStatus { NeedsAction, @@ -45,6 +51,8 @@ pub enum AttendeeResponseStatus { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = google::EventType))] #[serde(rename_all = "camelCase")] pub enum EventType { Default, @@ -59,6 +67,7 @@ pub enum EventType { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum Transparency { Opaque, @@ -69,6 +78,7 @@ pub enum Transparency { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum Visibility { Default, @@ -83,6 +93,7 @@ pub enum Visibility { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum EventOrderBy { StartTime, @@ -93,6 +104,7 @@ pub enum EventOrderBy { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum ConferenceSolutionType { AddOn, @@ -105,6 +117,7 @@ pub enum ConferenceSolutionType { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "lowercase")] pub enum EntryPointType { Video, @@ -117,6 +130,7 @@ pub enum EntryPointType { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "lowercase")] pub enum ReminderMethod { Email, @@ -127,6 +141,7 @@ pub enum ReminderMethod { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "lowercase")] pub enum NotificationMethod { Email, @@ -136,6 +151,7 @@ pub enum NotificationMethod { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum NotificationType { EventCreation, @@ -149,6 +165,7 @@ pub enum NotificationType { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "lowercase")] pub enum ConferenceCreateStatusCode { Pending, @@ -160,6 +177,7 @@ pub enum ConferenceCreateStatusCode { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum WorkingLocationType { HomeOffice, @@ -171,6 +189,7 @@ pub enum WorkingLocationType { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum AutoDeclineMode { DeclineNone, @@ -182,6 +201,7 @@ pub enum AutoDeclineMode { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum ChatStatus { Available, @@ -192,6 +212,7 @@ pub enum ChatStatus { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "lowercase")] pub enum BirthdayPropertyType { Birthday, @@ -206,6 +227,7 @@ pub enum BirthdayPropertyType { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "lowercase")] pub enum GadgetDisplay { Chip, @@ -218,6 +240,7 @@ pub enum GadgetDisplay { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ConferenceData { #[serde(default)] @@ -236,6 +259,7 @@ pub struct ConferenceData { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ConferenceSolution { #[serde(default)] @@ -248,6 +272,7 @@ pub struct ConferenceSolution { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ConferenceSolutionKey { #[serde(rename = "type")] @@ -256,6 +281,7 @@ pub struct ConferenceSolutionKey { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ConferenceCreateRequest { #[serde(default)] @@ -268,6 +294,7 @@ pub struct ConferenceCreateRequest { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ConferenceCreateRequestStatus { pub status_code: ConferenceCreateStatusCode, @@ -275,6 +302,7 @@ pub struct ConferenceCreateRequestStatus { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct EntryPoint { pub entry_point_type: EntryPointType, @@ -295,6 +323,7 @@ pub struct EntryPoint { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ConferenceProperties { #[serde(default)] @@ -305,6 +334,7 @@ pub struct ConferenceProperties { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct Reminders { #[serde(default)] @@ -315,6 +345,7 @@ pub struct Reminders { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct Reminder { pub method: ReminderMethod, @@ -323,6 +354,7 @@ pub struct Reminder { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct NotificationSettings { #[serde(default)] @@ -331,6 +363,7 @@ pub struct NotificationSettings { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct CalendarNotification { pub method: NotificationMethod, @@ -342,6 +375,7 @@ pub struct CalendarNotification { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct Gadget { #[serde(default, rename = "type")] @@ -364,6 +398,7 @@ pub struct Gadget { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct WorkingLocationProperties { #[serde(default, rename = "type")] @@ -379,6 +414,7 @@ pub struct WorkingLocationProperties { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct CustomLocation { #[serde(default)] @@ -387,6 +423,7 @@ pub struct CustomLocation { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct OfficeLocation { #[serde(default)] @@ -403,6 +440,7 @@ pub struct OfficeLocation { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct OutOfOfficeProperties { #[serde(default)] @@ -413,6 +451,7 @@ pub struct OutOfOfficeProperties { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct FocusTimeProperties { #[serde(default)] @@ -425,6 +464,7 @@ pub struct FocusTimeProperties { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct BirthdayProperties { #[serde(default)] @@ -439,6 +479,7 @@ pub struct BirthdayProperties { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct EventSource { pub url: String, @@ -447,6 +488,7 @@ pub struct EventSource { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ExtendedProperties { #[serde(default)] @@ -457,6 +499,7 @@ pub struct ExtendedProperties { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct EventAttachment { #[serde(default)] @@ -475,6 +518,7 @@ pub struct EventAttachment { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct CalendarListEntry { pub id: String, @@ -524,6 +568,8 @@ pub struct CalendarListEntry { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = google::Event))] #[serde(rename_all = "camelCase")] pub struct Event { pub id: String, @@ -617,6 +663,7 @@ pub struct Event { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct EventDateTime { #[serde(default)] @@ -629,6 +676,7 @@ pub struct EventDateTime { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct EventPerson { #[serde(default)] @@ -643,6 +691,8 @@ pub struct EventPerson { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = google::Attendee))] #[serde(rename_all = "camelCase")] pub struct Attendee { #[serde(default)] @@ -671,6 +721,8 @@ pub struct Attendee { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = google::ListCalendarsResponse))] #[serde(rename_all = "camelCase")] pub struct ListCalendarsResponse { #[serde(default)] @@ -687,6 +739,8 @@ pub struct ListCalendarsResponse { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = google::ListEventsResponse))] #[serde(rename_all = "camelCase")] pub struct ListEventsResponse { #[serde(default)] @@ -714,6 +768,7 @@ pub struct ListEventsResponse { } #[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] pub struct ListEventsRequest { pub calendar_id: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -748,6 +803,7 @@ pub struct ListEventsRequest { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] pub struct CreateEventRequest { pub calendar_id: String, pub event: CreateEventBody, @@ -755,6 +811,8 @@ pub struct CreateEventRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[cfg_attr(feature = "specta", derive(Type))] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = google::CreateEventBody))] #[serde(rename_all = "camelCase")] pub struct CreateEventBody { #[serde(default)] diff --git a/crates/outlook-calendar/Cargo.toml b/crates/outlook-calendar/Cargo.toml index 48fd8a4be4..f0661a9070 100644 --- a/crates/outlook-calendar/Cargo.toml +++ b/crates/outlook-calendar/Cargo.toml @@ -3,6 +3,10 @@ name = "outlook-calendar" version = "0.1.0" edition = "2024" +[features] +default = [] +utoipa = ["dep:utoipa"] + [dependencies] hypr-http = { workspace = true } @@ -11,6 +15,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } urlencoding = { workspace = true } +utoipa = { workspace = true, optional = true, features = ["chrono"] } [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/outlook-calendar/src/lib.rs b/crates/outlook-calendar/src/lib.rs index f4c77cbd23..aaddb147f4 100644 --- a/crates/outlook-calendar/src/lib.rs +++ b/crates/outlook-calendar/src/lib.rs @@ -2,6 +2,9 @@ mod client; mod error; mod types; +#[cfg(feature = "utoipa")] +pub mod openapi; + pub use client::OutlookCalendarClient; pub use error::Error; pub use types::*; diff --git a/crates/outlook-calendar/src/openapi.rs b/crates/outlook-calendar/src/openapi.rs new file mode 100644 index 0000000000..2460634d9c --- /dev/null +++ b/crates/outlook-calendar/src/openapi.rs @@ -0,0 +1,42 @@ +use utoipa::OpenApi; + +#[derive(OpenApi)] +#[openapi(components(schemas( + crate::Attendee, + crate::AttendeeType, + crate::BodyType, + crate::Calendar, + crate::CalendarColor, + crate::CreateEventBody, + crate::DateTimeTimeZone, + crate::DayOfWeek, + crate::EmailAddress, + crate::Event, + crate::EventShowAs, + crate::EventType, + crate::Importance, + crate::ItemBody, + crate::ListCalendarsResponse, + crate::ListEventsResponse, + crate::Location, + crate::LocationType, + crate::OnlineMeetingInfo, + crate::OnlineMeetingProviderType, + crate::OutlookGeoCoordinates, + crate::PatternedRecurrence, + crate::PhysicalAddress, + crate::Recipient, + crate::RecurrencePattern, + crate::RecurrencePatternType, + crate::RecurrenceRange, + crate::RecurrenceRangeType, + crate::ResponseStatus, + crate::ResponseType, + crate::Sensitivity, + crate::WeekIndex, +)))] +struct ApiDoc; + +pub fn openapi() -> utoipa::openapi::OpenApi { + ApiDoc::openapi() +} diff --git a/crates/outlook-calendar/src/types.rs b/crates/outlook-calendar/src/types.rs index 3723efe856..9e1d60be7a 100644 --- a/crates/outlook-calendar/src/types.rs +++ b/crates/outlook-calendar/src/types.rs @@ -1,7 +1,11 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "utoipa")] +use utoipa::ToSchema; + #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum EventShowAs { Free, @@ -15,6 +19,7 @@ pub enum EventShowAs { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum Importance { Low, @@ -25,6 +30,7 @@ pub enum Importance { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum Sensitivity { Normal, @@ -36,6 +42,8 @@ pub enum Sensitivity { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = outlook::EventType))] #[serde(rename_all = "camelCase")] pub enum EventType { SingleInstance, @@ -47,6 +55,7 @@ pub enum EventType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum ResponseType { None, @@ -60,6 +69,7 @@ pub enum ResponseType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum AttendeeType { Required, @@ -70,6 +80,7 @@ pub enum AttendeeType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum OnlineMeetingProviderType { TeamsForBusiness, @@ -81,6 +92,7 @@ pub enum OnlineMeetingProviderType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum BodyType { Text, @@ -90,6 +102,7 @@ pub enum BodyType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum CalendarColor { Auto, @@ -108,6 +121,7 @@ pub enum CalendarColor { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum DayOfWeek { Sunday, @@ -122,6 +136,7 @@ pub enum DayOfWeek { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum WeekIndex { First, @@ -134,6 +149,7 @@ pub enum WeekIndex { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum RecurrencePatternType { Daily, @@ -147,6 +163,7 @@ pub enum RecurrencePatternType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum RecurrenceRangeType { EndDate, @@ -157,6 +174,7 @@ pub enum RecurrenceRangeType { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub enum LocationType { Default, @@ -174,6 +192,7 @@ pub enum LocationType { } #[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct DateTimeTimeZone { pub date_time: String, @@ -182,6 +201,7 @@ pub struct DateTimeTimeZone { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct EmailAddress { #[serde(default)] @@ -191,6 +211,7 @@ pub struct EmailAddress { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ResponseStatus { #[serde(default)] @@ -200,6 +221,8 @@ pub struct ResponseStatus { } #[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = outlook::Attendee))] #[serde(rename_all = "camelCase")] pub struct Attendee { #[serde(default, rename = "type")] @@ -211,6 +234,7 @@ pub struct Attendee { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct ItemBody { #[serde(default)] @@ -220,6 +244,7 @@ pub struct ItemBody { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct PhysicalAddress { #[serde(default)] @@ -235,6 +260,7 @@ pub struct PhysicalAddress { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct OutlookGeoCoordinates { #[serde(default)] @@ -250,6 +276,7 @@ pub struct OutlookGeoCoordinates { } #[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct Location { #[serde(default)] @@ -267,6 +294,7 @@ pub struct Location { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct OnlineMeetingInfo { #[serde(default)] @@ -282,6 +310,7 @@ pub struct OnlineMeetingInfo { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct RecurrencePattern { #[serde(default, rename = "type")] @@ -301,6 +330,7 @@ pub struct RecurrencePattern { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct RecurrenceRange { #[serde(default, rename = "type")] @@ -316,6 +346,7 @@ pub struct RecurrenceRange { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct PatternedRecurrence { #[serde(default)] @@ -325,6 +356,7 @@ pub struct PatternedRecurrence { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct Calendar { pub id: String, @@ -357,6 +389,8 @@ pub struct Calendar { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = outlook::Event))] #[serde(rename_all = "camelCase")] pub struct Event { pub id: String, @@ -441,6 +475,7 @@ pub struct Event { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] #[serde(rename_all = "camelCase")] pub struct Recipient { #[serde(default)] @@ -448,6 +483,8 @@ pub struct Recipient { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = outlook::ListCalendarsResponse))] #[serde(rename_all = "camelCase")] pub struct ListCalendarsResponse { #[serde(default, rename = "@odata.context")] @@ -459,6 +496,8 @@ pub struct ListCalendarsResponse { } #[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = outlook::ListEventsResponse))] #[serde(rename_all = "camelCase")] pub struct ListEventsResponse { #[serde(default, rename = "@odata.context")] @@ -487,6 +526,8 @@ pub struct CreateEventRequest { } #[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(ToSchema))] +#[cfg_attr(feature = "utoipa", schema(as = outlook::CreateEventBody))] #[serde(rename_all = "camelCase")] pub struct CreateEventBody { #[serde(default)] diff --git a/packages/api-client/src/generated/index.ts b/packages/api-client/src/generated/index.ts index 2b8308140b..80b73cdf2f 100644 --- a/packages/api-client/src/generated/index.ts +++ b/packages/api-client/src/generated/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { canStartTrial, createConnectSession, createContact, createConversation, createEvent, createReconnectSession, deleteAccount, getMessages, listCalendars, listConnections, listConversations, listEvents, llmChatCompletions, nangoWebhook, type Options, sendMessage, startTrial, sttListenBatch, sttListenStream, sttStatus, submit } from './sdk.gen'; -export type { BatchAlternatives, BatchChannel, BatchResponse, BatchResults, BatchWord, CanStartTrialData, CanStartTrialErrors, CanStartTrialReason, CanStartTrialResponse, CanStartTrialResponse2, CanStartTrialResponses, CharTask, ClientOptions, ConnectionItem, ConnectSessionResponse, ConversationSummary, CreateConnectSessionData, CreateConnectSessionErrors, CreateConnectSessionRequest, CreateConnectSessionResponse, CreateConnectSessionResponses, CreateContactData, CreateContactErrors, CreateContactRequest, CreateContactResponse, CreateContactResponse2, CreateContactResponses, CreateConversationData, CreateConversationErrors, CreateConversationRequest, CreateConversationResponse, CreateConversationResponse2, CreateConversationResponses, CreateEventData, CreateEventErrors, CreateEventRequest, CreateEventResponse, CreateEventResponse2, CreateEventResponses, CreateReconnectSessionData, CreateReconnectSessionErrors, CreateReconnectSessionRequest, CreateReconnectSessionResponse, CreateReconnectSessionResponses, DeleteAccountData, DeleteAccountErrors, DeleteAccountResponse, DeleteAccountResponse2, DeleteAccountResponses, DeviceInfo, EventAttendee, EventDateTime, FeedbackRequest, FeedbackResponse, FeedbackType, GetMessagesData, GetMessagesErrors, GetMessagesResponse, GetMessagesResponses, Interval, ListCalendarsData, ListCalendarsErrors, ListCalendarsResponse, ListCalendarsResponse2, ListCalendarsResponses, ListConnectionsData, ListConnectionsErrors, ListConnectionsResponse, ListConnectionsResponse2, ListConnectionsResponses, ListConversationsData, ListConversationsErrors, ListConversationsQuery, ListConversationsResponse, ListConversationsResponses, ListenCallbackRequest, ListenCallbackResponse, ListEventsData, ListEventsErrors, ListEventsRequest, ListEventsResponse, ListEventsResponse2, ListEventsResponses, LlmChatCompletionsData, LlmChatCompletionsErrors, LlmChatCompletionsResponses, MessageResponse, NangoWebhookData, NangoWebhookErrors, NangoWebhookResponse, NangoWebhookResponses, PipelineStatus, SendMessageData, SendMessageErrors, SendMessageRequest, SendMessageResponse, SendMessageResponses, StartTrialData, StartTrialErrors, StartTrialReason, StartTrialResponse, StartTrialResponse2, StartTrialResponses, StreamAlternatives, StreamChannel, StreamMetadata, StreamModelInfo, StreamResponse, StreamWord, SttListenBatchData, SttListenBatchErrors, SttListenBatchResponse, SttListenBatchResponses, SttListenStreamData, SttListenStreamErrors, SttStatusData, SttStatusErrors, SttStatusResponse, SttStatusResponse2, SttStatusResponses, SubmitData, SubmitError, SubmitErrors, SubmitResponse, SubmitResponses, WebhookResponse } from './types.gen'; +export { canStartTrial, createConnectSession, createContact, createConversation, createReconnectSession, deleteAccount, getMessages, googleListCalendars, googleListEvents, listConnections, listConversations, llmChatCompletions, nangoWebhook, type Options, outlookListCalendars, outlookListEvents, sendMessage, startTrial, sttListenBatch, sttListenStream, sttStatus, submit } from './sdk.gen'; +export type { AccessRole, AttendeeResponseStatus, AttendeeType, AutoDeclineMode, BatchAlternatives, BatchChannel, BatchResponse, BatchResults, BatchWord, BirthdayProperties, BirthdayPropertyType, BodyType, Calendar, CalendarColor, CalendarListEntry, CalendarNotification, CanStartTrialData, CanStartTrialErrors, CanStartTrialReason, CanStartTrialResponse, CanStartTrialResponse2, CanStartTrialResponses, CharTask, ChatStatus, ClientOptions, ConferenceCreateRequest, ConferenceCreateRequestStatus, ConferenceCreateStatusCode, ConferenceData, ConferenceProperties, ConferenceSolution, ConferenceSolutionKey, ConferenceSolutionType, ConnectionItem, ConnectSessionResponse, ConversationSummary, CreateConnectSessionData, CreateConnectSessionErrors, CreateConnectSessionRequest, CreateConnectSessionResponse, CreateConnectSessionResponses, CreateContactData, CreateContactErrors, CreateContactRequest, CreateContactResponse, CreateContactResponse2, CreateContactResponses, CreateConversationData, CreateConversationErrors, CreateConversationRequest, CreateConversationResponse, CreateConversationResponse2, CreateConversationResponses, CreateEventRequest, CreateReconnectSessionData, CreateReconnectSessionErrors, CreateReconnectSessionRequest, CreateReconnectSessionResponse, CreateReconnectSessionResponses, CustomLocation, DateTimeTimeZone, DayOfWeek, DeleteAccountData, DeleteAccountErrors, DeleteAccountResponse, DeleteAccountResponse2, DeleteAccountResponses, DeviceInfo, EmailAddress, EntryPoint, EntryPointType, EventAttachment, EventDateTime, EventOrderBy, EventPerson, EventShowAs, EventSource, EventStatus, ExtendedProperties, FeedbackRequest, FeedbackResponse, FeedbackType, FocusTimeProperties, Gadget, GadgetDisplay, GetMessagesData, GetMessagesErrors, GetMessagesResponse, GetMessagesResponses, GoogleAttendee, GoogleCreateEventBody, GoogleEvent, GoogleEventType, GoogleListCalendarsData, GoogleListCalendarsErrors, GoogleListCalendarsResponse, GoogleListCalendarsResponse2, GoogleListCalendarsResponses, GoogleListEventsData, GoogleListEventsErrors, GoogleListEventsRequest, GoogleListEventsResponse, GoogleListEventsResponse2, GoogleListEventsResponses, Importance, Interval, ItemBody, ListConnectionsData, ListConnectionsErrors, ListConnectionsResponse, ListConnectionsResponse2, ListConnectionsResponses, ListConversationsData, ListConversationsErrors, ListConversationsQuery, ListConversationsResponse, ListConversationsResponses, ListenCallbackRequest, ListenCallbackResponse, ListEventsRequest, LlmChatCompletionsData, LlmChatCompletionsErrors, LlmChatCompletionsResponses, Location, LocationType, MessageResponse, NangoWebhookData, NangoWebhookErrors, NangoWebhookResponse, NangoWebhookResponses, NotificationMethod, NotificationSettings, NotificationType, OfficeLocation, OnlineMeetingInfo, OnlineMeetingProviderType, OutlookAttendee, OutlookCreateEventBody, OutlookEvent, OutlookEventType, OutlookGeoCoordinates, OutlookListCalendarsData, OutlookListCalendarsErrors, OutlookListCalendarsResponse, OutlookListCalendarsResponse2, OutlookListCalendarsResponses, OutlookListEventsData, OutlookListEventsErrors, OutlookListEventsRequest, OutlookListEventsResponse, OutlookListEventsResponse2, OutlookListEventsResponses, OutOfOfficeProperties, PatternedRecurrence, PhysicalAddress, PipelineStatus, Recipient, RecurrencePattern, RecurrencePatternType, RecurrenceRange, RecurrenceRangeType, Reminder, ReminderMethod, Reminders, ResponseStatus, ResponseType, SendMessageData, SendMessageErrors, SendMessageRequest, SendMessageResponse, SendMessageResponses, Sensitivity, StartTrialData, StartTrialErrors, StartTrialReason, StartTrialResponse, StartTrialResponse2, StartTrialResponses, StreamAlternatives, StreamChannel, StreamMetadata, StreamModelInfo, StreamResponse, StreamWord, SttListenBatchData, SttListenBatchErrors, SttListenBatchResponse, SttListenBatchResponses, SttListenStreamData, SttListenStreamErrors, SttStatusData, SttStatusErrors, SttStatusResponse, SttStatusResponse2, SttStatusResponses, SubmitData, SubmitError, SubmitErrors, SubmitResponse, SubmitResponses, Transparency, Visibility, WebhookResponse, WeekIndex, WorkingLocationProperties, WorkingLocationType } from './types.gen'; diff --git a/packages/api-client/src/generated/sdk.gen.ts b/packages/api-client/src/generated/sdk.gen.ts index 92d8b83b90..1052ba56fd 100644 --- a/packages/api-client/src/generated/sdk.gen.ts +++ b/packages/api-client/src/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { CanStartTrialData, CanStartTrialErrors, CanStartTrialResponses, CreateConnectSessionData, CreateConnectSessionErrors, CreateConnectSessionResponses, CreateContactData, CreateContactErrors, CreateContactResponses, CreateConversationData, CreateConversationErrors, CreateConversationResponses, CreateEventData, CreateEventErrors, CreateEventResponses, CreateReconnectSessionData, CreateReconnectSessionErrors, CreateReconnectSessionResponses, DeleteAccountData, DeleteAccountErrors, DeleteAccountResponses, GetMessagesData, GetMessagesErrors, GetMessagesResponses, ListCalendarsData, ListCalendarsErrors, ListCalendarsResponses, ListConnectionsData, ListConnectionsErrors, ListConnectionsResponses, ListConversationsData, ListConversationsErrors, ListConversationsResponses, ListEventsData, ListEventsErrors, ListEventsResponses, LlmChatCompletionsData, LlmChatCompletionsErrors, LlmChatCompletionsResponses, NangoWebhookData, NangoWebhookErrors, NangoWebhookResponses, SendMessageData, SendMessageErrors, SendMessageResponses, StartTrialData, StartTrialErrors, StartTrialResponses, SttListenBatchData, SttListenBatchErrors, SttListenBatchResponses, SttListenStreamData, SttListenStreamErrors, SttStatusData, SttStatusErrors, SttStatusResponses, SubmitData, SubmitErrors, SubmitResponses } from './types.gen'; +import type { CanStartTrialData, CanStartTrialErrors, CanStartTrialResponses, CreateConnectSessionData, CreateConnectSessionErrors, CreateConnectSessionResponses, CreateContactData, CreateContactErrors, CreateContactResponses, CreateConversationData, CreateConversationErrors, CreateConversationResponses, CreateReconnectSessionData, CreateReconnectSessionErrors, CreateReconnectSessionResponses, DeleteAccountData, DeleteAccountErrors, DeleteAccountResponses, GetMessagesData, GetMessagesErrors, GetMessagesResponses, GoogleListCalendarsData, GoogleListCalendarsErrors, GoogleListCalendarsResponses, GoogleListEventsData, GoogleListEventsErrors, GoogleListEventsResponses, ListConnectionsData, ListConnectionsErrors, ListConnectionsResponses, ListConversationsData, ListConversationsErrors, ListConversationsResponses, LlmChatCompletionsData, LlmChatCompletionsErrors, LlmChatCompletionsResponses, NangoWebhookData, NangoWebhookErrors, NangoWebhookResponses, OutlookListCalendarsData, OutlookListCalendarsErrors, OutlookListCalendarsResponses, OutlookListEventsData, OutlookListEventsErrors, OutlookListEventsResponses, SendMessageData, SendMessageErrors, SendMessageResponses, StartTrialData, StartTrialErrors, StartTrialResponses, SttListenBatchData, SttListenBatchErrors, SttListenBatchResponses, SttListenStreamData, SttListenStreamErrors, SttStatusData, SttStatusErrors, SttStatusResponses, SubmitData, SubmitErrors, SubmitResponses } from './types.gen'; export type Options = Options2 & { /** @@ -18,15 +18,15 @@ export type Options; }; -export const listCalendars = (options?: Options) => (options?.client ?? client).post({ +export const googleListCalendars = (options?: Options) => (options?.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], - url: '/calendar/calendars', + url: '/calendar/google/list-calendars', ...options }); -export const listEvents = (options: Options) => (options.client ?? client).post({ +export const googleListEvents = (options: Options) => (options.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], - url: '/calendar/events', + url: '/calendar/google/list-events', ...options, headers: { 'Content-Type': 'application/json', @@ -34,9 +34,15 @@ export const listEvents = (options: Option } }); -export const createEvent = (options: Options) => (options.client ?? client).post({ +export const outlookListCalendars = (options?: Options) => (options?.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], - url: '/calendar/events/create', + url: '/calendar/outlook/list-calendars', + ...options +}); + +export const outlookListEvents = (options: Options) => (options.client ?? client).post({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/calendar/outlook/list-events', ...options, headers: { 'Content-Type': 'application/json', diff --git a/packages/api-client/src/generated/types.gen.ts b/packages/api-client/src/generated/types.gen.ts index ed25ccb3df..40ed884d88 100644 --- a/packages/api-client/src/generated/types.gen.ts +++ b/packages/api-client/src/generated/types.gen.ts @@ -4,6 +4,14 @@ export type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); }; +export type AccessRole = 'freeBusyReader' | 'reader' | 'writer' | 'owner' | 'unknown'; + +export type AttendeeResponseStatus = 'needsAction' | 'declined' | 'tentative' | 'accepted' | 'unknown'; + +export type AttendeeType = 'required' | 'optional' | 'resource' | 'unknown'; + +export type AutoDeclineMode = 'declineNone' | 'declineAllConflictingInvitations' | 'declineOnlyNewConflictingInvitations' | 'unknown'; + export type BatchAlternatives = { confidence: number; transcript: string; @@ -34,6 +42,64 @@ export type BatchWord = { word: string; }; +export type BirthdayProperties = { + contact?: string | null; + customTypeName?: string | null; + type?: null | BirthdayPropertyType; +}; + +export type BirthdayPropertyType = 'birthday' | 'anniversary' | 'self' | 'other' | 'custom' | 'unknown'; + +export type BodyType = 'text' | 'html' | 'unknown'; + +export type Calendar = { + allowedOnlineMeetingProviders?: Array | null; + canEdit?: boolean | null; + canShare?: boolean | null; + canViewPrivateItems?: boolean | null; + changeKey?: string | null; + color?: null | CalendarColor; + defaultOnlineMeetingProvider?: null | OnlineMeetingProviderType; + hexColor?: string | null; + id: string; + isDefaultCalendar?: boolean | null; + isRemovable?: boolean | null; + isTallyingResponses?: boolean | null; + name?: string | null; + owner?: null | EmailAddress; +}; + +export type CalendarColor = 'auto' | 'lightBlue' | 'lightGreen' | 'lightOrange' | 'lightGray' | 'lightYellow' | 'lightTeal' | 'lightPink' | 'lightBrown' | 'lightRed' | 'maxColor' | 'unknown'; + +export type CalendarListEntry = { + accessRole?: null | AccessRole; + autoAcceptInvitations?: boolean | null; + backgroundColor?: string | null; + colorId?: string | null; + conferenceProperties?: null | ConferenceProperties; + dataOwner?: string | null; + defaultReminders?: Array | null; + deleted?: boolean | null; + description?: string | null; + etag?: string | null; + foregroundColor?: string | null; + hidden?: boolean | null; + id: string; + kind?: string | null; + location?: string | null; + notificationSettings?: null | NotificationSettings; + primary?: boolean | null; + selected?: boolean | null; + summary?: string | null; + summaryOverride?: string | null; + timeZone?: string | null; +}; + +export type CalendarNotification = { + method: NotificationMethod; + type: NotificationType; +}; + export type CanStartTrialReason = 'eligible' | 'not_eligible' | 'error'; export type CanStartTrialResponse = { @@ -43,6 +109,45 @@ export type CanStartTrialResponse = { export type CharTask = 'chat' | 'enhance' | 'title'; +export type ChatStatus = 'available' | 'doNotDisturb' | 'unknown'; + +export type ConferenceCreateRequest = { + conferenceSolutionKey?: null | ConferenceSolutionKey; + requestId?: string | null; + status?: null | ConferenceCreateRequestStatus; +}; + +export type ConferenceCreateRequestStatus = { + statusCode: ConferenceCreateStatusCode; +}; + +export type ConferenceCreateStatusCode = 'pending' | 'success' | 'failure' | 'unknown'; + +export type ConferenceData = { + conferenceId?: string | null; + conferenceSolution?: null | ConferenceSolution; + createRequest?: null | ConferenceCreateRequest; + entryPoints?: Array | null; + notes?: string | null; + signature?: string | null; +}; + +export type ConferenceProperties = { + allowedConferenceSolutionTypes?: Array | null; +}; + +export type ConferenceSolution = { + iconUri?: string | null; + key?: null | ConferenceSolutionKey; + name?: string | null; +}; + +export type ConferenceSolutionKey = { + type: ConferenceSolutionType; +}; + +export type ConferenceSolutionType = 'addOn' | 'hangoutsMeet' | 'eventNamedHangout' | 'eventHangout' | 'unknown'; + export type ConnectSessionResponse = { expires_at: string; token: string; @@ -85,17 +190,8 @@ export type CreateConversationResponse = { }; export type CreateEventRequest = { - attendees?: Array | null; calendar_id: string; - description?: string | null; - end: EventDateTime; - location?: string | null; - start: EventDateTime; - summary: string; -}; - -export type CreateEventResponse = { - event: unknown; + event: GoogleCreateEventBody; }; export type CreateReconnectSessionRequest = { @@ -103,6 +199,17 @@ export type CreateReconnectSessionRequest = { integration_id: string; }; +export type CustomLocation = { + label?: string | null; +}; + +export type DateTimeTimeZone = { + dateTime: string; + timeZone?: string | null; +}; + +export type DayOfWeek = 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'unknown'; + export type DeleteAccountResponse = { deleted: boolean; error?: string | null; @@ -117,10 +224,30 @@ export type DeviceInfo = { platform: string; }; -export type EventAttendee = { - displayName?: string | null; - email: string; - optional?: boolean | null; +export type EmailAddress = { + address?: string | null; + name?: string | null; +}; + +export type EntryPoint = { + accessCode?: string | null; + entryPointType: EntryPointType; + label?: string | null; + meetingCode?: string | null; + passcode?: string | null; + password?: string | null; + pin?: string | null; + uri: string; +}; + +export type EntryPointType = 'video' | 'phone' | 'sip' | 'more' | 'unknown'; + +export type EventAttachment = { + fileId?: string | null; + fileUrl?: string | null; + iconLink?: string | null; + mimeType?: string | null; + title?: string | null; }; export type EventDateTime = { @@ -129,6 +256,33 @@ export type EventDateTime = { timeZone?: string | null; }; +export type EventOrderBy = 'startTime' | 'updated'; + +export type EventPerson = { + displayName?: string | null; + email?: string | null; + id?: string | null; + self?: boolean | null; +}; + +export type EventShowAs = 'free' | 'tentative' | 'busy' | 'oof' | 'workingElsewhere' | 'unknown' | 'other'; + +export type EventSource = { + title: string; + url: string; +}; + +export type EventStatus = 'confirmed' | 'tentative' | 'cancelled' | 'unknown'; + +export type ExtendedProperties = { + private?: { + [key: string]: string; + } | null; + shared?: { + [key: string]: string; + } | null; +}; + export type FeedbackRequest = { description: string; deviceInfo: DeviceInfo; @@ -144,10 +298,44 @@ export type FeedbackResponse = { export type FeedbackType = 'bug' | 'feature'; +export type FocusTimeProperties = { + autoDeclineMode?: null | AutoDeclineMode; + chatStatus?: null | ChatStatus; + declineMessage?: string | null; +}; + +export type Gadget = { + display?: null | GadgetDisplay; + height?: number | null; + iconLink?: string | null; + link?: string | null; + preferences?: { + [key: string]: string; + } | null; + title?: string | null; + type?: string | null; + width?: number | null; +}; + +export type GadgetDisplay = 'chip' | 'icon' | 'unknown'; + +export type GoogleListEventsRequest = { + calendar_id: string; + max_results?: number | null; + order_by?: string | null; + page_token?: string | null; + single_events?: boolean | null; + time_max?: string | null; + time_min?: string | null; +}; + +export type Importance = 'low' | 'normal' | 'high' | 'unknown'; + export type Interval = 'monthly' | 'yearly'; -export type ListCalendarsResponse = { - calendars: Array; +export type ItemBody = { + content?: string | null; + contentType?: null | BodyType; }; export type ListConnectionsResponse = { @@ -160,17 +348,20 @@ export type ListConversationsQuery = { export type ListEventsRequest = { calendar_id: string; + event_types?: Array | null; + i_cal_uid?: string | null; max_results?: number | null; - order_by?: string | null; + order_by?: null | EventOrderBy; page_token?: string | null; + q?: string | null; + show_deleted?: boolean | null; + show_hidden_invitations?: boolean | null; single_events?: boolean | null; + sync_token?: string | null; time_max?: string | null; time_min?: string | null; -}; - -export type ListEventsResponse = { - events: Array; - next_page_token?: string | null; + time_zone?: string | null; + updated_min?: string | null; }; export type ListenCallbackRequest = { @@ -181,6 +372,17 @@ export type ListenCallbackResponse = { request_id: string; }; +export type Location = { + address?: null | PhysicalAddress; + coordinates?: null | OutlookGeoCoordinates; + displayName?: string | null; + locationType?: null | LocationType; + uniqueId?: string | null; + uniqueIdType?: string | null; +}; + +export type LocationType = 'default' | 'conferenceRoom' | 'homeAddress' | 'businessAddress' | 'geoCoordinates' | 'streetAddress' | 'hotel' | 'restaurant' | 'localBusiness' | 'postalAddress' | 'unknown'; + export type MessageResponse = { content?: string | null; createdAt?: string | null; @@ -188,14 +390,121 @@ export type MessageResponse = { messageType?: string | null; }; +export type NotificationMethod = 'email' | 'unknown'; + +export type NotificationSettings = { + notifications?: Array | null; +}; + +export type NotificationType = 'eventCreation' | 'eventChange' | 'eventCancellation' | 'eventResponse' | 'agenda' | 'unknown'; + +export type OfficeLocation = { + buildingId?: string | null; + deskId?: string | null; + floorId?: string | null; + floorSectionId?: string | null; + label?: string | null; +}; + +export type OnlineMeetingInfo = { + conferenceId?: string | null; + joinUrl?: string | null; + quickDial?: string | null; + tollFreeNumbers?: Array | null; + tollNumber?: string | null; +}; + +export type OnlineMeetingProviderType = 'teamsForBusiness' | 'skypeForBusiness' | 'skypeForConsumer' | 'unknown' | 'other'; + +export type OutOfOfficeProperties = { + autoDeclineMode?: null | AutoDeclineMode; + declineMessage?: string | null; +}; + +export type OutlookGeoCoordinates = { + accuracy?: number | null; + altitude?: number | null; + altitudeAccuracy?: number | null; + latitude?: number | null; + longitude?: number | null; +}; + +export type OutlookListEventsRequest = { + calendar_id: string; + max_results?: number | null; + order_by?: string | null; + time_max?: string | null; + time_min?: string | null; +}; + +export type PatternedRecurrence = { + pattern?: null | RecurrencePattern; + range?: null | RecurrenceRange; +}; + +export type PhysicalAddress = { + city?: string | null; + countryOrRegion?: string | null; + postalCode?: string | null; + state?: string | null; + street?: string | null; +}; + export type PipelineStatus = 'processing' | 'done' | 'error'; +export type Recipient = { + emailAddress?: null | EmailAddress; +}; + +export type RecurrencePattern = { + dayOfMonth?: number | null; + daysOfWeek?: Array | null; + firstDayOfWeek?: null | DayOfWeek; + index?: null | WeekIndex; + interval?: number | null; + month?: number | null; + type?: null | RecurrencePatternType; +}; + +export type RecurrencePatternType = 'daily' | 'weekly' | 'absoluteMonthly' | 'relativeMonthly' | 'absoluteYearly' | 'relativeYearly' | 'unknown'; + +export type RecurrenceRange = { + endDate?: string | null; + numberOfOccurrences?: number | null; + recurrenceTimeZone?: string | null; + startDate?: string | null; + type?: null | RecurrenceRangeType; +}; + +export type RecurrenceRangeType = 'endDate' | 'noEnd' | 'numbered' | 'unknown'; + +export type Reminder = { + method: ReminderMethod; + minutes: number; +}; + +export type ReminderMethod = 'email' | 'popup' | 'unknown'; + +export type Reminders = { + overrides?: Array | null; + useDefault?: boolean | null; +}; + +export type ResponseStatus = { + response?: null | ResponseType; + time?: string | null; +}; + +export type ResponseType = 'none' | 'organizer' | 'tentativelyAccepted' | 'accepted' | 'declined' | 'notResponded' | 'unknown'; + export type SendMessageRequest = { content: string; messageType?: string; sourceId?: string | null; }; +export type Sensitivity = 'normal' | 'personal' | 'private' | 'confidential' | 'unknown'; + export type StartTrialReason = 'started' | 'not_eligible'; export type StartTrialResponse = { @@ -279,18 +588,222 @@ export type SttStatusResponse = { status: PipelineStatus; }; +export type Transparency = 'opaque' | 'transparent' | 'unknown'; + +export type Visibility = 'default' | 'public' | 'private' | 'confidential' | 'unknown'; + export type WebhookResponse = { status: string; }; -export type ListCalendarsData = { +export type WeekIndex = 'first' | 'second' | 'third' | 'fourth' | 'last' | 'unknown'; + +export type WorkingLocationProperties = { + customLocation?: null | CustomLocation; + homeOffice?: unknown; + officeLocation?: null | OfficeLocation; + type?: null | WorkingLocationType; +}; + +export type WorkingLocationType = 'homeOffice' | 'officeLocation' | 'customLocation' | 'unknown'; + +export type GoogleAttendee = { + additionalGuests?: number | null; + comment?: string | null; + displayName?: string | null; + email?: string | null; + id?: string | null; + optional?: boolean | null; + organizer?: boolean | null; + resource?: boolean | null; + responseStatus?: null | AttendeeResponseStatus; + self?: boolean | null; +}; + +export type GoogleCreateEventBody = { + attendees?: Array | null; + colorId?: string | null; + conferenceData?: null | ConferenceData; + description?: string | null; + end?: EventDateTime; + eventType?: null | GoogleEventType; + extendedProperties?: null | ExtendedProperties; + guestsCanInviteOthers?: boolean | null; + guestsCanModify?: boolean | null; + guestsCanSeeOtherGuests?: boolean | null; + location?: string | null; + recurrence?: Array | null; + reminders?: null | Reminders; + source?: null | EventSource; + start?: EventDateTime; + summary?: string; + transparency?: null | Transparency; + visibility?: null | Visibility; +}; + +export type GoogleEvent = { + anyoneCanAddSelf?: boolean | null; + attachments?: Array | null; + attendees?: Array | null; + attendeesOmitted?: boolean | null; + birthdayProperties?: null | BirthdayProperties; + colorId?: string | null; + conferenceData?: null | ConferenceData; + created?: string | null; + creator?: null | EventPerson; + description?: string | null; + end?: null | EventDateTime; + endTimeUnspecified?: boolean | null; + etag?: string | null; + eventType?: null | GoogleEventType; + extendedProperties?: null | ExtendedProperties; + focusTimeProperties?: null | FocusTimeProperties; + gadget?: null | Gadget; + guestsCanInviteOthers?: boolean | null; + guestsCanModify?: boolean | null; + guestsCanSeeOtherGuests?: boolean | null; + hangoutLink?: string | null; + htmlLink?: string | null; + iCalUID?: string | null; + id: string; + kind?: string | null; + location?: string | null; + locked?: boolean | null; + organizer?: null | EventPerson; + originalStartTime?: null | EventDateTime; + outOfOfficeProperties?: null | OutOfOfficeProperties; + privateCopy?: boolean | null; + recurrence?: Array | null; + recurringEventId?: string | null; + reminders?: null | Reminders; + sequence?: number | null; + source?: null | EventSource; + start?: null | EventDateTime; + status?: null | EventStatus; + summary?: string | null; + transparency?: null | Transparency; + updated?: string | null; + visibility?: null | Visibility; + workingLocationProperties?: null | WorkingLocationProperties; +}; + +export type GoogleEventType = 'default' | 'birthday' | 'focusTime' | 'fromGmail' | 'outOfOffice' | 'workingLocation' | 'unknown'; + +export type GoogleListCalendarsResponse = { + etag?: string | null; + items?: Array; + kind?: string | null; + nextPageToken?: string | null; + nextSyncToken?: string | null; +}; + +export type GoogleListEventsResponse = { + accessRole?: null | AccessRole; + defaultReminders?: Array | null; + description?: string | null; + etag?: string | null; + items?: Array; + kind?: string | null; + nextPageToken?: string | null; + nextSyncToken?: string | null; + summary?: string | null; + timeZone?: string | null; + updated?: string | null; +}; + +export type OutlookAttendee = { + emailAddress?: null | EmailAddress; + status?: null | ResponseStatus; + type?: null | AttendeeType; +}; + +export type OutlookCreateEventBody = { + allowNewTimeProposals?: boolean | null; + attendees?: Array | null; + body?: null | ItemBody; + categories?: Array | null; + end?: DateTimeTimeZone; + hideAttendees?: boolean | null; + importance?: null | Importance; + isAllDay?: boolean | null; + isOnlineMeeting?: boolean | null; + isReminderOn?: boolean | null; + location?: null | Location; + onlineMeetingProvider?: null | OnlineMeetingProviderType; + recurrence?: null | PatternedRecurrence; + reminderMinutesBeforeStart?: number | null; + responseRequested?: boolean | null; + sensitivity?: null | Sensitivity; + showAs?: null | EventShowAs; + start?: DateTimeTimeZone; + subject?: string; +}; + +export type OutlookEvent = { + allowNewTimeProposals?: boolean | null; + attendees?: Array | null; + body?: null | ItemBody; + bodyPreview?: string | null; + categories?: Array | null; + changeKey?: string | null; + createdDateTime?: string | null; + end?: null | DateTimeTimeZone; + hasAttachments?: boolean | null; + hideAttendees?: boolean | null; + iCalUId?: string | null; + id: string; + importance?: null | Importance; + isAllDay?: boolean | null; + isCancelled?: boolean | null; + isDraft?: boolean | null; + isOnlineMeeting?: boolean | null; + isOrganizer?: boolean | null; + isReminderOn?: boolean | null; + lastModifiedDateTime?: string | null; + location?: null | Location; + locations?: Array | null; + onlineMeeting?: null | OnlineMeetingInfo; + onlineMeetingProvider?: null | OnlineMeetingProviderType; + onlineMeetingUrl?: string | null; + organizer?: null | Recipient; + originalEndTimeZone?: string | null; + originalStartTimeZone?: string | null; + recurrence?: null | PatternedRecurrence; + reminderMinutesBeforeStart?: number | null; + responseRequested?: boolean | null; + responseStatus?: null | ResponseStatus; + sensitivity?: null | Sensitivity; + seriesMasterId?: string | null; + showAs?: null | EventShowAs; + start?: null | DateTimeTimeZone; + subject?: string | null; + transactionId?: string | null; + type?: null | OutlookEventType; + webLink?: string | null; +}; + +export type OutlookEventType = 'singleInstance' | 'occurrence' | 'exception' | 'seriesMaster' | 'unknown'; + +export type OutlookListCalendarsResponse = { + '@odata.context'?: string | null; + '@odata.nextLink'?: string | null; + value?: Array; +}; + +export type OutlookListEventsResponse = { + '@odata.context'?: string | null; + '@odata.nextLink'?: string | null; + value?: Array; +}; + +export type GoogleListCalendarsData = { body?: never; path?: never; query?: never; - url: '/calendar/calendars'; + url: '/calendar/google/list-calendars'; }; -export type ListCalendarsErrors = { +export type GoogleListCalendarsErrors = { /** * Unauthorized */ @@ -301,23 +814,50 @@ export type ListCalendarsErrors = { 500: unknown; }; -export type ListCalendarsResponses = { +export type GoogleListCalendarsResponses = { /** - * Calendars fetched + * Google calendars fetched */ - 200: ListCalendarsResponse; + 200: GoogleListCalendarsResponse; }; -export type ListCalendarsResponse2 = ListCalendarsResponses[keyof ListCalendarsResponses]; +export type GoogleListCalendarsResponse2 = GoogleListCalendarsResponses[keyof GoogleListCalendarsResponses]; + +export type GoogleListEventsData = { + body: GoogleListEventsRequest; + path?: never; + query?: never; + url: '/calendar/google/list-events'; +}; -export type ListEventsData = { - body: ListEventsRequest; +export type GoogleListEventsErrors = { + /** + * Unauthorized + */ + 401: unknown; + /** + * Internal server error + */ + 500: unknown; +}; + +export type GoogleListEventsResponses = { + /** + * Google events fetched + */ + 200: GoogleListEventsResponse; +}; + +export type GoogleListEventsResponse2 = GoogleListEventsResponses[keyof GoogleListEventsResponses]; + +export type OutlookListCalendarsData = { + body?: never; path?: never; query?: never; - url: '/calendar/events'; + url: '/calendar/outlook/list-calendars'; }; -export type ListEventsErrors = { +export type OutlookListCalendarsErrors = { /** * Unauthorized */ @@ -328,23 +868,23 @@ export type ListEventsErrors = { 500: unknown; }; -export type ListEventsResponses = { +export type OutlookListCalendarsResponses = { /** - * Events fetched + * Outlook calendars fetched */ - 200: ListEventsResponse; + 200: OutlookListCalendarsResponse; }; -export type ListEventsResponse2 = ListEventsResponses[keyof ListEventsResponses]; +export type OutlookListCalendarsResponse2 = OutlookListCalendarsResponses[keyof OutlookListCalendarsResponses]; -export type CreateEventData = { - body: CreateEventRequest; +export type OutlookListEventsData = { + body: OutlookListEventsRequest; path?: never; query?: never; - url: '/calendar/events/create'; + url: '/calendar/outlook/list-events'; }; -export type CreateEventErrors = { +export type OutlookListEventsErrors = { /** * Unauthorized */ @@ -355,14 +895,14 @@ export type CreateEventErrors = { 500: unknown; }; -export type CreateEventResponses = { +export type OutlookListEventsResponses = { /** - * Event created + * Outlook events fetched */ - 200: CreateEventResponse; + 200: OutlookListEventsResponse; }; -export type CreateEventResponse2 = CreateEventResponses[keyof CreateEventResponses]; +export type OutlookListEventsResponse2 = OutlookListEventsResponses[keyof OutlookListEventsResponses]; export type SubmitData = { body: FeedbackRequest; diff --git a/plugins/google-calendar/src/convert.rs b/plugins/google-calendar/src/convert.rs index bc08ebf80b..cb894e70e0 100644 --- a/plugins/google-calendar/src/convert.rs +++ b/plugins/google-calendar/src/convert.rs @@ -137,3 +137,110 @@ fn extract_video_entry_point(event: &Event) -> Option { }) .map(|ep| ep.uri.clone()) } + +#[cfg(test)] +mod tests { + use super::*; + use hypr_google_calendar::{ + Attendee, AttendeeResponseStatus, EventDateTime, EventStatus as GoogleEventStatus, + }; + + #[test] + fn status_maps_correctly() { + assert!(matches!(convert_status(None), EventStatus::Confirmed)); + assert!(matches!( + convert_status(Some(GoogleEventStatus::Confirmed)), + EventStatus::Confirmed + )); + assert!(matches!( + convert_status(Some(GoogleEventStatus::Unknown)), + EventStatus::Confirmed + )); + assert!(matches!( + convert_status(Some(GoogleEventStatus::Tentative)), + EventStatus::Tentative + )); + assert!(matches!( + convert_status(Some(GoogleEventStatus::Cancelled)), + EventStatus::Cancelled + )); + } + + #[test] + fn attendee_status_needs_action_and_unknown_are_pending() { + assert!(matches!( + convert_attendee_status(&Some(AttendeeResponseStatus::NeedsAction)), + AttendeeStatus::Pending + )); + assert!(matches!( + convert_attendee_status(&None), + AttendeeStatus::Pending + )); + assert!(matches!( + convert_attendee_status(&Some(AttendeeResponseStatus::Unknown)), + AttendeeStatus::Pending + )); + } + + #[test] + fn attendee_role_organizer_is_chair() { + let attendee = Attendee { + organizer: Some(true), + optional: Some(false), + ..Default::default() + }; + assert!(matches!( + convert_attendee(&attendee).role, + AttendeeRole::Chair + )); + } + + #[test] + fn attendee_role_optional_beats_non_organizer() { + let attendee = Attendee { + organizer: Some(false), + optional: Some(true), + ..Default::default() + }; + assert!(matches!( + convert_attendee(&attendee).role, + AttendeeRole::Optional + )); + } + + #[test] + fn attendee_role_defaults_to_required() { + let attendee = Attendee::default(); + assert!(matches!( + convert_attendee(&attendee).role, + AttendeeRole::Required + )); + } + + #[test] + fn all_day_event_converts_to_midnight_utc() { + let date = chrono::NaiveDate::from_ymd_opt(2024, 6, 15).unwrap(); + let edt = EventDateTime { + date: Some(date), + date_time: None, + time_zone: None, + }; + let iso = event_datetime_to_iso(&edt).unwrap(); + assert!(iso.starts_with("2024-06-15T00:00:00+00:00")); + } + + #[test] + fn timed_event_preserves_offset() { + use chrono::{FixedOffset, TimeZone}; + let offset = FixedOffset::east_opt(9 * 3600).unwrap(); + let dt = offset.with_ymd_and_hms(2024, 6, 15, 10, 0, 0).unwrap(); + let edt = EventDateTime { + date: None, + date_time: Some(dt), + time_zone: None, + }; + let iso = event_datetime_to_iso(&edt).unwrap(); + assert!(iso.contains("10:00:00")); + assert!(iso.contains("+09:00")); + } +} diff --git a/plugins/google-calendar/src/ext.rs b/plugins/google-calendar/src/ext.rs index e4ebcb2e05..b9857ee4d3 100644 --- a/plugins/google-calendar/src/ext.rs +++ b/plugins/google-calendar/src/ext.rs @@ -18,10 +18,9 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager> GoogleCalendarExt<'a, R, M> { match token { Some(t) if !t.is_empty() => { let config = self.manager.state::(); - let client = self.manager.state::(); - fetch::list_calendars(&client, &config.api_base_url, &t).await + fetch::list_calendars(&config.api_base_url, &t).await } - _ => crate::fixture::list_calendars(), + _ => Err("not authenticated".to_string()), } } @@ -32,10 +31,9 @@ impl<'a, R: tauri::Runtime, M: tauri::Manager> GoogleCalendarExt<'a, R, M> { match token { Some(t) if !t.is_empty() => { let config = self.manager.state::(); - let client = self.manager.state::(); - fetch::list_events(&client, &config.api_base_url, &t, filter).await + fetch::list_events(&config.api_base_url, &t, filter).await } - _ => crate::fixture::list_events(filter), + _ => Err("not authenticated".to_string()), } } } diff --git a/plugins/google-calendar/src/fetch.rs b/plugins/google-calendar/src/fetch.rs index 30b57fde85..0732831d96 100644 --- a/plugins/google-calendar/src/fetch.rs +++ b/plugins/google-calendar/src/fetch.rs @@ -2,46 +2,42 @@ use hypr_google_calendar::{CalendarListEntry, Event}; use crate::types::EventFilter; +fn make_client(api_base_url: &str, access_token: &str) -> Result { + let auth_value = format!("Bearer {access_token}") + .parse() + .map_err(|e: reqwest::header::InvalidHeaderValue| e.to_string())?; + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert(reqwest::header::AUTHORIZATION, auth_value); + let http = reqwest::Client::builder() + .default_headers(headers) + .build() + .map_err(|e| e.to_string())?; + Ok(hypr_api_client::Client::new_with_client(api_base_url, http)) +} + pub async fn list_calendars( - client: &reqwest::Client, api_base_url: &str, access_token: &str, ) -> Result, String> { - let url = format!("{}/calendar/calendars", api_base_url.trim_end_matches('/')); + let client = make_client(api_base_url, access_token)?; + let response = client - .post(&url) - .header("Authorization", format!("Bearer {}", access_token)) - .header("Accept", "application/json") - .send() + .google_list_calendars() .await .map_err(|e| e.to_string())?; - if !response.status().is_success() { - let status = response.status(); - let body = response.text().await.unwrap_or_default(); - return Err(format!("API error {}: {}", status, body)); - } - - let body: hypr_api_client::types::ListCalendarsResponse = - response.json().await.map_err(|e| e.to_string())?; - - Ok(body - .calendars - .iter() - .filter_map(|v| serde_json::from_value::(v.clone()).ok()) - .collect()) + Ok(response.into_inner().items) } pub async fn list_events( - client: &reqwest::Client, api_base_url: &str, access_token: &str, filter: EventFilter, ) -> Result, String> { - let url = format!("{}/calendar/events", api_base_url.trim_end_matches('/')); + let client = make_client(api_base_url, access_token)?; - let body = hypr_api_client::types::ListEventsRequest { - calendar_id: filter.calendar_tracking_id.clone(), + let body = hypr_api_client::types::GoogleListEventsRequest { + calendar_id: filter.calendar_tracking_id, time_min: Some(filter.from.to_rfc3339()), time_max: Some(filter.to.to_rfc3339()), max_results: None, @@ -51,57 +47,9 @@ pub async fn list_events( }; let response = client - .post(&url) - .header("Authorization", format!("Bearer {}", access_token)) - .header("Accept", "application/json") - .header("Content-Type", "application/json") - .json(&body) - .send() + .google_list_events(&body) .await .map_err(|e| e.to_string())?; - if !response.status().is_success() { - let status = response.status(); - let err_body = response.text().await.unwrap_or_default(); - return Err(format!("API error {}: {}", status, err_body)); - } - - let res: hypr_api_client::types::ListEventsResponse = - response.json().await.map_err(|e| e.to_string())?; - - Ok(res - .events - .iter() - .filter_map(|v| serde_json::from_value::(v.clone()).ok()) - .filter(|e| { - let (start, end) = match (&e.start, &e.end) { - (Some(s), Some(ed)) => { - let start_utc = s - .date - .as_ref() - .map(|d| d.and_hms_opt(0, 0, 0).unwrap().and_utc()) - .or_else(|| { - s.date_time - .as_ref() - .map(|dt| dt.with_timezone(&chrono::Utc)) - }); - let end_utc = ed - .date - .as_ref() - .map(|d| d.and_hms_opt(0, 0, 0).unwrap().and_utc()) - .or_else(|| { - ed.date_time - .as_ref() - .map(|dt| dt.with_timezone(&chrono::Utc)) - }); - let (Some(s), Some(e)) = (start_utc, end_utc) else { - return false; - }; - (s, e) - } - _ => return false, - }; - start < filter.to && end > filter.from - }) - .collect()) + Ok(response.into_inner().items) } diff --git a/plugins/google-calendar/src/fixture.rs b/plugins/google-calendar/src/fixture.rs deleted file mode 100644 index 7143356bbb..0000000000 --- a/plugins/google-calendar/src/fixture.rs +++ /dev/null @@ -1,207 +0,0 @@ -use chrono::{Duration, Utc}; - -use hypr_google_calendar::{CalendarListEntry, Event, EventDateTime}; - -use crate::types::EventFilter; - -fn load_calendars() -> Vec { - vec![ - CalendarListEntry { - id: "primary".to_string(), - kind: None, - etag: None, - summary: Some("Primary".to_string()), - description: None, - location: None, - time_zone: None, - summary_override: None, - color_id: None, - background_color: None, - foreground_color: None, - hidden: None, - selected: None, - primary: None, - deleted: None, - access_role: None, - data_owner: None, - default_reminders: None, - notification_settings: None, - conference_properties: None, - auto_accept_invitations: None, - }, - CalendarListEntry { - id: "work".to_string(), - kind: None, - etag: None, - summary: Some("Work".to_string()), - description: None, - location: None, - time_zone: None, - summary_override: None, - color_id: None, - background_color: None, - foreground_color: None, - hidden: None, - selected: None, - primary: None, - deleted: None, - access_role: None, - data_owner: None, - default_reminders: None, - notification_settings: None, - conference_properties: None, - auto_accept_invitations: None, - }, - ] -} - -fn load_events(filter: EventFilter) -> Vec { - let now = Utc::now(); - let base = vec![ - Event { - id: "evt-1".to_string(), - kind: None, - etag: None, - status: None, - html_link: None, - created: None, - updated: None, - summary: Some("Fixture Meeting".to_string()), - description: None, - location: Some("Conference Room A".to_string()), - color_id: None, - creator: None, - organizer: None, - start: Some(EventDateTime { - date: None, - date_time: Some( - (now + Duration::hours(2)) - .with_timezone(&chrono::FixedOffset::east_opt(0).unwrap()), - ), - time_zone: None, - }), - end: Some(EventDateTime { - date: None, - date_time: Some( - (now + Duration::hours(3)) - .with_timezone(&chrono::FixedOffset::east_opt(0).unwrap()), - ), - time_zone: None, - }), - end_time_unspecified: None, - recurrence: None, - recurring_event_id: None, - original_start_time: None, - transparency: None, - visibility: None, - ical_uid: None, - sequence: None, - attendees: None, - attendees_omitted: None, - extended_properties: None, - hangout_link: None, - conference_data: None, - gadget: None, - anyone_can_add_self: None, - guests_can_invite_others: None, - guests_can_modify: None, - guests_can_see_other_guests: None, - private_copy: None, - locked: None, - reminders: None, - source: None, - working_location_properties: None, - out_of_office_properties: None, - focus_time_properties: None, - attachments: None, - birthday_properties: None, - event_type: None, - }, - Event { - id: "evt-2".to_string(), - kind: None, - etag: None, - status: None, - html_link: None, - created: None, - updated: None, - summary: Some("Fixture All Day Event".to_string()), - description: None, - location: None, - color_id: None, - creator: None, - organizer: None, - start: Some(EventDateTime { - date: Some(now.date_naive()), - date_time: None, - time_zone: None, - }), - end: Some(EventDateTime { - date: Some((now + Duration::days(1)).date_naive()), - date_time: None, - time_zone: None, - }), - end_time_unspecified: None, - recurrence: None, - recurring_event_id: None, - original_start_time: None, - transparency: None, - visibility: None, - ical_uid: None, - sequence: None, - attendees: None, - attendees_omitted: None, - extended_properties: None, - hangout_link: None, - conference_data: None, - gadget: None, - anyone_can_add_self: None, - guests_can_invite_others: None, - guests_can_modify: None, - guests_can_see_other_guests: None, - private_copy: None, - locked: None, - reminders: None, - source: None, - working_location_properties: None, - out_of_office_properties: None, - focus_time_properties: None, - attachments: None, - birthday_properties: None, - event_type: None, - }, - ]; - - base.into_iter() - .filter(|e| { - let (start, end) = match (&e.start, &e.end) { - (Some(s), Some(ed)) => { - let start_utc = s - .date - .as_ref() - .map(|d| d.and_hms_opt(0, 0, 0).unwrap().and_utc()) - .or_else(|| s.date_time.as_ref().map(|dt| dt.with_timezone(&Utc))); - let end_utc = ed - .date - .as_ref() - .map(|d| d.and_hms_opt(0, 0, 0).unwrap().and_utc()) - .or_else(|| ed.date_time.as_ref().map(|dt| dt.with_timezone(&Utc))); - let (Some(s), Some(ev)) = (start_utc, end_utc) else { - return false; - }; - (s, ev) - } - _ => return false, - }; - start < filter.to && end > filter.from - }) - .collect() -} - -pub fn list_calendars() -> Result, String> { - Ok(load_calendars()) -} - -pub fn list_events(filter: EventFilter) -> Result, String> { - Ok(load_events(filter)) -} diff --git a/plugins/google-calendar/src/lib.rs b/plugins/google-calendar/src/lib.rs index d5081a314f..c62ca5a5af 100644 --- a/plugins/google-calendar/src/lib.rs +++ b/plugins/google-calendar/src/lib.rs @@ -2,7 +2,6 @@ mod commands; mod convert; mod ext; mod fetch; -mod fixture; mod types; pub use ext::{GoogleCalendarExt, GoogleCalendarPluginExt}; @@ -34,7 +33,6 @@ pub fn init() -> tauri::plugin::TauriPlugin { .setup(move |app, _api| { use tauri::Manager; - app.manage(reqwest::Client::new()); app.manage(PluginConfig { api_base_url }); Ok(()) @@ -85,16 +83,16 @@ mod test { } #[tokio::test] - async fn test_list_calendars() { + async fn test_list_calendars_requires_auth() { let app = create_app(tauri::test::mock_builder()); - let calendars = app.google_calendar().list_calendars().await; - assert!(calendars.is_ok()); + let result = app.google_calendar().list_calendars().await; + assert!(result.is_err()); } #[tokio::test] - async fn test_list_events() { + async fn test_list_events_requires_auth() { let app = create_app(tauri::test::mock_builder()); - let events = app + let result = app .google_calendar() .list_events(types::EventFilter { from: chrono::Utc::now(), @@ -102,6 +100,6 @@ mod test { calendar_tracking_id: "primary".to_string(), }) .await; - assert!(events.is_ok()); + assert!(result.is_err()); } } From 2a5ccbfa17069a95ff2fd1692ba96cec5a58bf78 Mon Sep 17 00:00:00 2001 From: Yujong Lee Date: Sat, 28 Feb 2026 12:47:35 +0900 Subject: [PATCH 2/2] various fixes Signed-off-by: Yujong Lee --- Cargo.lock | 9 ++++++--- crates/progenitor-utils/src/lib.rs | 12 ++++++++++- plugins/google-calendar/Cargo.toml | 1 + plugins/google-calendar/src/commands.rs | 5 +++-- plugins/google-calendar/src/error.rs | 27 +++++++++++++++++++++++++ plugins/google-calendar/src/ext.rs | 19 +++++++++++------ plugins/google-calendar/src/fetch.rs | 18 ++++++++--------- plugins/google-calendar/src/lib.rs | 2 ++ 8 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 plugins/google-calendar/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index d7418edee9..7fe7b504b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,11 +540,8 @@ dependencies = [ "google-calendar", "nango", "outlook-calendar", - "sentry", "serde", - "serde_json", "thiserror 2.0.18", - "tracing", "utoipa", ] @@ -553,6 +550,8 @@ name = "api-client" version = "0.1.0" dependencies = [ "chrono", + "google-calendar", + "outlook-calendar", "progenitor-client 0.13.0", "progenitor-utils", "reqwest 0.13.2", @@ -7953,6 +7952,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "urlencoding", + "utoipa", ] [[package]] @@ -10211,6 +10211,7 @@ dependencies = [ "ractor", "serde", "specta", + "strum 0.27.2", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -12153,6 +12154,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "urlencoding", + "utoipa", ] [[package]] @@ -17913,6 +17915,7 @@ dependencies = [ "tauri-plugin", "tauri-plugin-auth", "tauri-specta", + "thiserror 2.0.18", "tokio", "tracing", ] diff --git a/crates/progenitor-utils/src/lib.rs b/crates/progenitor-utils/src/lib.rs index e0aa5ddd06..aa27013615 100644 --- a/crates/progenitor-utils/src/lib.rs +++ b/crates/progenitor-utils/src/lib.rs @@ -82,9 +82,19 @@ impl OpenApiSpec { } pub fn generate(&self, filename: &str) { + self.generate_with_replacements(filename, &[]); + } + + pub fn generate_with_replacements(&self, filename: &str, replacements: &[(&str, &str)]) { let openapi: openapiv3::OpenAPI = serde_json::from_value(self.inner.clone()).expect("filtered spec is not valid OpenAPI"); - let tokens = progenitor::Generator::default() + + let mut settings = progenitor::GenerationSettings::new(); + for (type_name, replace_name) in replacements { + settings.with_replacement(*type_name, *replace_name, std::iter::empty()); + } + + let tokens = progenitor::Generator::new(&settings) .generate_tokens(&openapi) .expect("progenitor code generation failed"); diff --git a/plugins/google-calendar/Cargo.toml b/plugins/google-calendar/Cargo.toml index d8ba08ccdb..53ebc41506 100644 --- a/plugins/google-calendar/Cargo.toml +++ b/plugins/google-calendar/Cargo.toml @@ -27,5 +27,6 @@ reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } specta = { workspace = true, features = ["chrono"] } +thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing = { workspace = true } diff --git a/plugins/google-calendar/src/commands.rs b/plugins/google-calendar/src/commands.rs index 4524510e32..8bab1745be 100644 --- a/plugins/google-calendar/src/commands.rs +++ b/plugins/google-calendar/src/commands.rs @@ -2,13 +2,14 @@ use hypr_calendar_interface::CalendarEvent; use hypr_google_calendar::CalendarListEntry; use crate::GoogleCalendarPluginExt; +use crate::error::Error; use crate::types::EventFilter; #[tauri::command] #[specta::specta] pub async fn list_calendars( app: tauri::AppHandle, -) -> Result, String> { +) -> Result, Error> { app.google_calendar().list_calendars().await } @@ -17,7 +18,7 @@ pub async fn list_calendars( pub async fn list_events( app: tauri::AppHandle, filter: EventFilter, -) -> Result, String> { +) -> Result, Error> { let calendar_id = filter.calendar_tracking_id.clone(); let events = app.google_calendar().list_events(filter).await?; Ok(crate::convert::convert_events(events, &calendar_id)) diff --git a/plugins/google-calendar/src/error.rs b/plugins/google-calendar/src/error.rs new file mode 100644 index 0000000000..0a14ccbb0c --- /dev/null +++ b/plugins/google-calendar/src/error.rs @@ -0,0 +1,27 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("not authenticated")] + NotAuthenticated, + #[error("invalid auth header: {0}")] + InvalidAuthHeader(#[from] reqwest::header::InvalidHeaderValue), + #[error("http client error: {0}")] + HttpClient(#[from] reqwest::Error), + #[error("auth plugin error: {0}")] + Auth(String), + #[error("api error: {0}")] + Api(String), +} + +impl serde::Serialize for Error { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +impl specta::Type for Error { + fn inline(_type_map: &mut specta::TypeMap, _generics: specta::Generics) -> specta::DataType { + specta::DataType::Primitive(specta::datatype::PrimitiveType::String) + } +} diff --git a/plugins/google-calendar/src/ext.rs b/plugins/google-calendar/src/ext.rs index b9857ee4d3..a8bb14732e 100644 --- a/plugins/google-calendar/src/ext.rs +++ b/plugins/google-calendar/src/ext.rs @@ -1,6 +1,7 @@ use hypr_google_calendar::{CalendarListEntry, Event}; use tauri_plugin_auth::AuthPluginExt; +use crate::error::Error; use crate::fetch; use crate::types::EventFilter; @@ -12,28 +13,34 @@ pub struct GoogleCalendarExt<'a, R: tauri::Runtime, M: tauri::Manager> { impl<'a, R: tauri::Runtime, M: tauri::Manager> GoogleCalendarExt<'a, R, M> { #[tracing::instrument(skip_all)] - pub async fn list_calendars(&self) -> Result, String> { - let token = self.manager.access_token().map_err(|e| e.to_string())?; + pub async fn list_calendars(&self) -> Result, Error> { + let token = self + .manager + .access_token() + .map_err(|e| Error::Auth(e.to_string()))?; match token { Some(t) if !t.is_empty() => { let config = self.manager.state::(); fetch::list_calendars(&config.api_base_url, &t).await } - _ => Err("not authenticated".to_string()), + _ => Err(Error::NotAuthenticated), } } #[tracing::instrument(skip_all)] - pub async fn list_events(&self, filter: EventFilter) -> Result, String> { - let token = self.manager.access_token().map_err(|e| e.to_string())?; + pub async fn list_events(&self, filter: EventFilter) -> Result, Error> { + let token = self + .manager + .access_token() + .map_err(|e| Error::Auth(e.to_string()))?; match token { Some(t) if !t.is_empty() => { let config = self.manager.state::(); fetch::list_events(&config.api_base_url, &t, filter).await } - _ => Err("not authenticated".to_string()), + _ => Err(Error::NotAuthenticated), } } } diff --git a/plugins/google-calendar/src/fetch.rs b/plugins/google-calendar/src/fetch.rs index 0732831d96..60efbb4637 100644 --- a/plugins/google-calendar/src/fetch.rs +++ b/plugins/google-calendar/src/fetch.rs @@ -1,30 +1,28 @@ use hypr_google_calendar::{CalendarListEntry, Event}; +use crate::error::Error; use crate::types::EventFilter; -fn make_client(api_base_url: &str, access_token: &str) -> Result { - let auth_value = format!("Bearer {access_token}") - .parse() - .map_err(|e: reqwest::header::InvalidHeaderValue| e.to_string())?; +fn make_client(api_base_url: &str, access_token: &str) -> Result { + let auth_value = format!("Bearer {access_token}").parse()?; let mut headers = reqwest::header::HeaderMap::new(); headers.insert(reqwest::header::AUTHORIZATION, auth_value); let http = reqwest::Client::builder() .default_headers(headers) - .build() - .map_err(|e| e.to_string())?; + .build()?; Ok(hypr_api_client::Client::new_with_client(api_base_url, http)) } pub async fn list_calendars( api_base_url: &str, access_token: &str, -) -> Result, String> { +) -> Result, Error> { let client = make_client(api_base_url, access_token)?; let response = client .google_list_calendars() .await - .map_err(|e| e.to_string())?; + .map_err(|e| Error::Api(e.to_string()))?; Ok(response.into_inner().items) } @@ -33,7 +31,7 @@ pub async fn list_events( api_base_url: &str, access_token: &str, filter: EventFilter, -) -> Result, String> { +) -> Result, Error> { let client = make_client(api_base_url, access_token)?; let body = hypr_api_client::types::GoogleListEventsRequest { @@ -49,7 +47,7 @@ pub async fn list_events( let response = client .google_list_events(&body) .await - .map_err(|e| e.to_string())?; + .map_err(|e| Error::Api(e.to_string()))?; Ok(response.into_inner().items) } diff --git a/plugins/google-calendar/src/lib.rs b/plugins/google-calendar/src/lib.rs index c62ca5a5af..2c109c4b81 100644 --- a/plugins/google-calendar/src/lib.rs +++ b/plugins/google-calendar/src/lib.rs @@ -1,9 +1,11 @@ mod commands; mod convert; +mod error; mod ext; mod fetch; mod types; +pub use error::Error; pub use ext::{GoogleCalendarExt, GoogleCalendarPluginExt}; pub use hypr_google_calendar::{CalendarListEntry, Event}; pub use types::EventFilter;