@elevasis/core 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1326 -552
- package/dist/index.js +869 -154
- package/dist/knowledge/index.d.ts +487 -209
- package/dist/knowledge/index.js +104 -1
- package/dist/organization-model/index.d.ts +1326 -552
- package/dist/organization-model/index.js +869 -154
- package/dist/test-utils/index.d.ts +357 -72
- package/dist/test-utils/index.js +795 -142
- package/package.json +5 -5
- package/src/README.md +14 -14
- package/src/__tests__/publish.test.ts +24 -24
- package/src/__tests__/template-core-compatibility.test.ts +9 -12
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2102 -2096
- package/src/_gen/__tests__/scaffold-contracts.test.ts +30 -30
- package/src/auth/multi-tenancy/credentials/__tests__/encryption.test.ts +217 -217
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +69 -69
- package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +37 -37
- package/src/auth/multi-tenancy/index.ts +26 -26
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -104
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +143 -143
- package/src/auth/multi-tenancy/memberships/index.ts +26 -26
- package/src/auth/multi-tenancy/memberships/membership.ts +130 -130
- package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -194
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -136
- package/src/auth/multi-tenancy/permissions.test.ts +42 -42
- package/src/auth/multi-tenancy/permissions.ts +123 -123
- package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -78
- package/src/auth/multi-tenancy/role-management/index.ts +16 -16
- package/src/auth/multi-tenancy/theme-presets.ts +45 -45
- package/src/auth/multi-tenancy/types.ts +57 -57
- package/src/auth/multi-tenancy/users/api-schemas.ts +165 -165
- package/src/business/README.md +2 -2
- package/src/business/acquisition/activity-events.test.ts +250 -250
- package/src/business/acquisition/activity-events.ts +93 -93
- package/src/business/acquisition/api-schemas.test.ts +1883 -1843
- package/src/business/acquisition/api-schemas.ts +1492 -1497
- package/src/business/acquisition/build-templates.test.ts +240 -240
- package/src/business/acquisition/build-templates.ts +98 -98
- package/src/business/acquisition/crm-next-action.test.ts +262 -262
- package/src/business/acquisition/crm-next-action.ts +220 -220
- package/src/business/acquisition/crm-priority.test.ts +216 -216
- package/src/business/acquisition/crm-priority.ts +349 -349
- package/src/business/acquisition/crm-state-actions.test.ts +153 -153
- package/src/business/acquisition/deal-ownership.test.ts +351 -351
- package/src/business/acquisition/deal-ownership.ts +120 -120
- package/src/business/acquisition/derive-actions.test.ts +129 -104
- package/src/business/acquisition/derive-actions.ts +74 -84
- package/src/business/acquisition/index.ts +171 -170
- package/src/business/acquisition/ontology-validation.ts +309 -0
- package/src/business/acquisition/stateful.ts +30 -30
- package/src/business/acquisition/types.ts +396 -396
- package/src/business/clients/api-schemas.test.ts +115 -115
- package/src/business/clients/api-schemas.ts +158 -158
- package/src/business/clients/index.ts +1 -1
- package/src/business/crm/api-schemas.ts +40 -40
- package/src/business/crm/index.ts +1 -1
- package/src/business/deals/api-schemas.ts +87 -87
- package/src/business/deals/index.ts +1 -1
- package/src/business/index.ts +5 -5
- package/src/business/projects/types.ts +144 -144
- package/src/commands/queue/types/task.ts +15 -15
- package/src/execution/core/runner-types.ts +61 -61
- package/src/execution/core/sse-executions.ts +7 -7
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -10
- package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -16
- package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -4
- package/src/execution/engine/agent/core/types.ts +25 -25
- package/src/execution/engine/agent/index.ts +6 -6
- package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -24
- package/src/execution/engine/index.ts +443 -443
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +298 -298
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -55
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -107
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -48
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -99
- package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -1
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -363
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -162
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -316
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -18
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -194
- package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -7
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -204
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +105 -105
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -428
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -2
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1474 -1474
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +103 -103
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -88
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -141
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -76
- package/src/execution/engine/tools/integration/server/adapters/signature-api/signature-api-tools.ts +182 -182
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +310 -310
- package/src/execution/engine/tools/integration/service.test.ts +239 -239
- package/src/execution/engine/tools/integration/service.ts +172 -172
- package/src/execution/engine/tools/integration/tool.ts +255 -255
- package/src/execution/engine/tools/lead-service-types.ts +1005 -1005
- package/src/execution/engine/tools/messages.ts +43 -43
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -7
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -6
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +6 -6
- package/src/execution/engine/tools/platform/acquisition/types.ts +280 -280
- package/src/execution/engine/tools/platform/email/types.ts +97 -97
- package/src/execution/engine/tools/registry.ts +704 -704
- package/src/execution/engine/tools/tool-maps.ts +831 -831
- package/src/execution/engine/tools/types.ts +234 -234
- package/src/execution/engine/workflow/types.ts +202 -202
- package/src/execution/external/__tests__/api-schemas.test.ts +127 -127
- package/src/execution/external/api-schemas.ts +40 -40
- package/src/execution/external/index.ts +1 -1
- package/src/index.ts +18 -18
- package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -420
- package/src/integrations/credentials/api-schemas.ts +146 -146
- package/src/integrations/credentials/schemas.ts +200 -200
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -7
- package/src/integrations/oauth/provider-registry.ts +74 -74
- package/src/integrations/oauth/server/credentials.ts +43 -43
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -327
- package/src/integrations/webhook-endpoints/api-schemas.ts +103 -103
- package/src/integrations/webhook-endpoints/types.ts +58 -58
- package/src/knowledge/README.md +32 -32
- package/src/knowledge/__tests__/queries.test.ts +626 -535
- package/src/knowledge/format.ts +99 -99
- package/src/knowledge/index.ts +5 -5
- package/src/knowledge/published.ts +5 -5
- package/src/knowledge/queries.ts +269 -218
- package/src/operations/activities/api-schemas.ts +80 -80
- package/src/operations/activities/types.ts +64 -64
- package/src/organization-model/README.md +149 -149
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -210
- package/src/organization-model/__tests__/defaults.test.ts +168 -168
- package/src/organization-model/__tests__/domains/actions.test.ts +78 -56
- package/src/organization-model/__tests__/domains/customers.test.ts +299 -299
- package/src/organization-model/__tests__/domains/entities.test.ts +56 -56
- package/src/organization-model/__tests__/domains/goals.test.ts +493 -493
- package/src/organization-model/__tests__/domains/identity.test.ts +280 -280
- package/src/organization-model/__tests__/domains/navigation.test.ts +268 -268
- package/src/organization-model/__tests__/domains/offerings.test.ts +414 -414
- package/src/organization-model/__tests__/domains/policies.test.ts +323 -323
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +293 -293
- package/src/organization-model/__tests__/domains/resources.test.ts +382 -283
- package/src/organization-model/__tests__/domains/roles.test.ts +463 -463
- package/src/organization-model/__tests__/domains/statuses.test.ts +246 -246
- package/src/organization-model/__tests__/domains/systems.test.ts +209 -209
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +362 -361
- package/src/organization-model/__tests__/foundation.test.ts +77 -77
- package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -144
- package/src/organization-model/__tests__/graph.test.ts +1246 -887
- package/src/organization-model/__tests__/icons.test.ts +10 -1
- package/src/organization-model/__tests__/knowledge.test.ts +251 -15
- package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -438
- package/src/organization-model/__tests__/migration-helpers.test.ts +591 -591
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +103 -103
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +535 -506
- package/src/organization-model/__tests__/resolve.test.ts +274 -164
- package/src/organization-model/__tests__/schema.test.ts +834 -301
- package/src/organization-model/__tests__/surface-projection.test.ts +284 -284
- package/src/organization-model/catalogs/lead-gen.ts +144 -144
- package/src/organization-model/content-kinds/config.ts +36 -36
- package/src/organization-model/content-kinds/index.ts +76 -72
- package/src/organization-model/content-kinds/pipeline.ts +68 -68
- package/src/organization-model/content-kinds/registry.ts +44 -44
- package/src/organization-model/content-kinds/status.ts +71 -71
- package/src/organization-model/content-kinds/template.ts +83 -83
- package/src/organization-model/content-kinds/types.ts +117 -117
- package/src/organization-model/contracts.ts +27 -27
- package/src/organization-model/defaults.ts +40 -50
- package/src/organization-model/domains/actions.ts +333 -239
- package/src/organization-model/domains/customers.ts +78 -78
- package/src/organization-model/domains/entities.ts +144 -144
- package/src/organization-model/domains/goals.ts +83 -83
- package/src/organization-model/domains/knowledge.ts +117 -101
- package/src/organization-model/domains/navigation.ts +139 -139
- package/src/organization-model/domains/offerings.ts +71 -71
- package/src/organization-model/domains/policies.ts +102 -102
- package/src/organization-model/domains/projects.ts +14 -14
- package/src/organization-model/domains/prospecting.ts +395 -395
- package/src/organization-model/domains/resources.ts +167 -132
- package/src/organization-model/domains/roles.ts +96 -96
- package/src/organization-model/domains/sales.test.ts +218 -218
- package/src/organization-model/domains/sales.ts +380 -380
- package/src/organization-model/domains/shared.ts +63 -63
- package/src/organization-model/domains/statuses.ts +339 -339
- package/src/organization-model/domains/systems.ts +217 -172
- package/src/organization-model/foundation.ts +75 -75
- package/src/organization-model/graph/build.ts +1016 -888
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/link.ts +10 -10
- package/src/organization-model/graph/schema.ts +76 -70
- package/src/organization-model/graph/types.ts +73 -67
- package/src/organization-model/helpers.ts +289 -241
- package/src/organization-model/icons.ts +78 -66
- package/src/organization-model/index.ts +130 -128
- package/src/organization-model/migration-helpers.ts +247 -244
- package/src/organization-model/ontology.ts +661 -0
- package/src/organization-model/organization-graph.mdx +110 -90
- package/src/organization-model/organization-model.mdx +226 -219
- package/src/organization-model/published.ts +289 -235
- package/src/organization-model/resolve.ts +146 -91
- package/src/organization-model/schema.ts +790 -671
- package/src/organization-model/surface-projection.ts +212 -212
- package/src/organization-model/types.ts +177 -167
- package/src/platform/api/types.ts +38 -38
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/index.ts +23 -23
- package/src/platform/registry/__tests__/command-view.test.ts +10 -10
- package/src/platform/registry/__tests__/resource-link.test.ts +35 -35
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +20 -20
- package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -245
- package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2053
- package/src/platform/registry/__tests__/validation.test.ts +1347 -1347
- package/src/platform/registry/command-view.ts +10 -10
- package/src/platform/registry/index.ts +103 -103
- package/src/platform/registry/resource-link.ts +32 -32
- package/src/platform/registry/resource-registry.ts +890 -890
- package/src/platform/registry/serialization.ts +295 -295
- package/src/platform/registry/serialized-types.ts +166 -166
- package/src/platform/registry/stats-types.ts +68 -68
- package/src/platform/registry/types.ts +425 -425
- package/src/platform/registry/validation.ts +745 -745
- package/src/platform/utils/__tests__/validation.test.ts +1084 -1084
- package/src/platform/utils/validation.ts +425 -425
- package/src/projects/api-schemas.test.ts +39 -39
- package/src/projects/api-schemas.ts +291 -291
- package/src/reference/_generated/contracts.md +2101 -2096
- package/src/reference/glossary.md +76 -76
- package/src/scaffold-registry/__tests__/index.test.ts +206 -206
- package/src/scaffold-registry/__tests__/schema.test.ts +166 -166
- package/src/scaffold-registry/index.ts +392 -392
- package/src/scaffold-registry/schema.ts +243 -243
- package/src/server.ts +289 -289
- package/src/supabase/database.types.ts +3153 -3153
- package/src/test-utils/README.md +37 -37
- package/src/test-utils/entities.ts +108 -108
- package/src/test-utils/fixtures/memberships.ts +82 -82
- package/src/test-utils/index.ts +12 -12
- package/src/test-utils/organization-model.ts +65 -65
- package/src/test-utils/published.ts +6 -6
- package/src/test-utils/rls/RLSTestContext.ts +588 -588
- package/src/test-utils/test-utils.test.ts +44 -44
|
@@ -1,428 +1,428 @@
|
|
|
1
|
-
import type { calendar_v3 } from '@googleapis/calendar'
|
|
2
|
-
import type { BaseIntegrationAdapter } from '../../../base-integration-adapter'
|
|
3
|
-
import { ToolingError } from '../../../../types'
|
|
4
|
-
import type { ExecutionContext } from '../../../../../base/types'
|
|
5
|
-
import { getOAuthCredentials } from '../../../../../../../integrations/oauth/server/credentials'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Google Calendar credentials format
|
|
9
|
-
* Stored in credentials table, encrypted
|
|
10
|
-
*/
|
|
11
|
-
interface GoogleCalendarCredentials {
|
|
12
|
-
accessToken: string
|
|
13
|
-
refreshToken?: string
|
|
14
|
-
expiresAt?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Date/time value from Google Calendar API
|
|
19
|
-
*/
|
|
20
|
-
export interface CalendarDateTime {
|
|
21
|
-
dateTime?: string // ISO 8601 datetime (when time is specified)
|
|
22
|
-
date?: string // YYYY-MM-DD (for all-day events)
|
|
23
|
-
timeZone?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A Google Calendar event (plausible-use fields only)
|
|
28
|
-
*/
|
|
29
|
-
export interface CalendarEvent {
|
|
30
|
-
id: string
|
|
31
|
-
summary?: string
|
|
32
|
-
description?: string
|
|
33
|
-
location?: string
|
|
34
|
-
start: CalendarDateTime
|
|
35
|
-
end: CalendarDateTime
|
|
36
|
-
status?: 'confirmed' | 'tentative' | 'cancelled'
|
|
37
|
-
htmlLink?: string
|
|
38
|
-
created?: string
|
|
39
|
-
updated?: string
|
|
40
|
-
organizer?: { email?: string; displayName?: string }
|
|
41
|
-
attendees?: Array<{ email?: string; displayName?: string; responseStatus?: string }>
|
|
42
|
-
recurringEventId?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* listEvents parameters.
|
|
47
|
-
*
|
|
48
|
-
* `credentialName` is required when calling the adapter directly (HTTP-handler
|
|
49
|
-
* path). It is ignored on the dispatch path, where credentials are injected
|
|
50
|
-
* by the execution-engine pipeline.
|
|
51
|
-
*/
|
|
52
|
-
interface ListEventsParams {
|
|
53
|
-
organizationId: string
|
|
54
|
-
credentialName?: string
|
|
55
|
-
calendarId?: string
|
|
56
|
-
timeMin?: string
|
|
57
|
-
timeMax?: string
|
|
58
|
-
maxResults?: number
|
|
59
|
-
singleEvents?: boolean
|
|
60
|
-
orderBy?: 'startTime' | 'updated'
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* listEvents result
|
|
65
|
-
*/
|
|
66
|
-
export interface ListEventsResult {
|
|
67
|
-
items: CalendarEvent[]
|
|
68
|
-
nextPageToken?: string
|
|
69
|
-
summary?: string
|
|
70
|
-
timeZone?: string
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* getEvent parameters
|
|
75
|
-
*/
|
|
76
|
-
interface GetEventParams {
|
|
77
|
-
organizationId: string
|
|
78
|
-
credentialName?: string
|
|
79
|
-
calendarId?: string
|
|
80
|
-
eventId: string
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Google Calendar adapter for calendar and event operations via Google Calendar API v3
|
|
85
|
-
*
|
|
86
|
-
* Read-only methods:
|
|
87
|
-
* - listEvents: List events from a calendar
|
|
88
|
-
* - getEvent: Get a single event by ID
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* const adapter = new GoogleCalendarAdapter()
|
|
92
|
-
* const result = await adapter.listEvents({
|
|
93
|
-
* organizationId: 'org-uuid',
|
|
94
|
-
* calendarId: 'primary',
|
|
95
|
-
* maxResults: 10
|
|
96
|
-
* })
|
|
97
|
-
*/
|
|
98
|
-
export class GoogleCalendarAdapter implements BaseIntegrationAdapter {
|
|
99
|
-
readonly name = 'google-calendar'
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Call Google Calendar API method
|
|
103
|
-
* @param method - Method name ('listEvents' | 'getEvent')
|
|
104
|
-
* @param params - Method parameters (must include organizationId)
|
|
105
|
-
* @param credentials - OAuth2 access token
|
|
106
|
-
* @param context - Execution context for logging and tracing
|
|
107
|
-
*/
|
|
108
|
-
async call(
|
|
109
|
-
method: string,
|
|
110
|
-
params: unknown,
|
|
111
|
-
credentials: Record<string, unknown>,
|
|
112
|
-
context?: ExecutionContext
|
|
113
|
-
): Promise<unknown> {
|
|
114
|
-
if (!this.validateCredentials(credentials)) {
|
|
115
|
-
throw new ToolingError('credentials_invalid', 'Invalid Google Calendar credentials', {
|
|
116
|
-
integration: 'google-calendar',
|
|
117
|
-
method
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
const calCreds = credentials as unknown as GoogleCalendarCredentials
|
|
121
|
-
|
|
122
|
-
const client = await this.createClient(calCreds)
|
|
123
|
-
|
|
124
|
-
switch (method) {
|
|
125
|
-
case 'listEvents':
|
|
126
|
-
return this.listEvents(client, params as ListEventsParams, context)
|
|
127
|
-
case 'getEvent':
|
|
128
|
-
return this.getEvent(client, params as GetEventParams, context)
|
|
129
|
-
default:
|
|
130
|
-
throw new ToolingError('method_not_found', `Unknown method: ${method}`, {
|
|
131
|
-
integration: 'google-calendar',
|
|
132
|
-
method
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* List events from a calendar — callable directly (no method dispatch overhead)
|
|
139
|
-
*/
|
|
140
|
-
async listEvents(
|
|
141
|
-
clientOrParams: calendar_v3.Calendar | ListEventsParams,
|
|
142
|
-
paramsOrContext?: ListEventsParams | ExecutionContext,
|
|
143
|
-
maybeContext?: ExecutionContext
|
|
144
|
-
): Promise<ListEventsResult> {
|
|
145
|
-
// Support both direct call (listEvents(params, context)) and
|
|
146
|
-
// dispatch call (listEvents(client, params, context))
|
|
147
|
-
let client: calendar_v3.Calendar
|
|
148
|
-
let params: ListEventsParams
|
|
149
|
-
let context: ExecutionContext | undefined
|
|
150
|
-
|
|
151
|
-
if ('calendarId' in (clientOrParams as object) || 'organizationId' in (clientOrParams as object)) {
|
|
152
|
-
// Called directly: listEvents(params, context?)
|
|
153
|
-
params = clientOrParams as ListEventsParams
|
|
154
|
-
context = paramsOrContext as ExecutionContext | undefined
|
|
155
|
-
if (!params.credentialName) {
|
|
156
|
-
throw new ToolingError('validation_error', 'Missing required field: credentialName', { params })
|
|
157
|
-
}
|
|
158
|
-
const creds = await this.getCredentialsForOrg(params.organizationId, params.credentialName)
|
|
159
|
-
client = await this.createClient(creds)
|
|
160
|
-
} else {
|
|
161
|
-
// Called via dispatch: listEvents(client, params, context?)
|
|
162
|
-
client = clientOrParams as calendar_v3.Calendar
|
|
163
|
-
params = paramsOrContext as ListEventsParams
|
|
164
|
-
context = maybeContext
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return this._listEvents(client, params, context)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get a single event by ID — callable directly
|
|
172
|
-
*/
|
|
173
|
-
async getEvent(
|
|
174
|
-
clientOrParams: calendar_v3.Calendar | GetEventParams,
|
|
175
|
-
paramsOrContext?: GetEventParams | ExecutionContext,
|
|
176
|
-
maybeContext?: ExecutionContext
|
|
177
|
-
): Promise<CalendarEvent> {
|
|
178
|
-
let client: calendar_v3.Calendar
|
|
179
|
-
let params: GetEventParams
|
|
180
|
-
let context: ExecutionContext | undefined
|
|
181
|
-
|
|
182
|
-
if ('eventId' in (clientOrParams as object) || 'organizationId' in (clientOrParams as object)) {
|
|
183
|
-
// Called directly: getEvent(params, context?)
|
|
184
|
-
params = clientOrParams as GetEventParams
|
|
185
|
-
context = paramsOrContext as ExecutionContext | undefined
|
|
186
|
-
if (!params.credentialName) {
|
|
187
|
-
throw new ToolingError('validation_error', 'Missing required field: credentialName', { params })
|
|
188
|
-
}
|
|
189
|
-
const creds = await this.getCredentialsForOrg(params.organizationId, params.credentialName)
|
|
190
|
-
client = await this.createClient(creds)
|
|
191
|
-
} else {
|
|
192
|
-
// Called via dispatch: getEvent(client, params, context?)
|
|
193
|
-
client = clientOrParams as calendar_v3.Calendar
|
|
194
|
-
params = paramsOrContext as GetEventParams
|
|
195
|
-
context = maybeContext
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return this._getEvent(client, params, context)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Create authenticated Google Calendar client (lazy loads @googleapis/calendar SDK)
|
|
203
|
-
* CRITICAL: Use dynamic import to prevent OOM and module-level crashes
|
|
204
|
-
*/
|
|
205
|
-
private async createClient(creds: GoogleCalendarCredentials): Promise<calendar_v3.Calendar> {
|
|
206
|
-
const { calendar } = await import('@googleapis/calendar')
|
|
207
|
-
const { OAuth2Client } = await import('google-auth-library')
|
|
208
|
-
|
|
209
|
-
// Get client credentials from environment for token refresh
|
|
210
|
-
const { clientId, clientSecret } = getOAuthCredentials('google-calendar')
|
|
211
|
-
|
|
212
|
-
const oauth2Client = new OAuth2Client(clientId, clientSecret)
|
|
213
|
-
oauth2Client.setCredentials({
|
|
214
|
-
access_token: creds.accessToken,
|
|
215
|
-
refresh_token: creds.refreshToken
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
return calendar({ version: 'v3', auth: oauth2Client })
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Retrieve credentials for an organization from the credentials table.
|
|
223
|
-
* Used when the adapter is called directly (not via the dispatch pipeline).
|
|
224
|
-
* The caller passes the credential name explicitly -- names are user-chosen
|
|
225
|
-
* at OAuth-modal time, so they cannot be hardcoded.
|
|
226
|
-
*/
|
|
227
|
-
private async getCredentialsForOrg(
|
|
228
|
-
organizationId: string,
|
|
229
|
-
credentialName: string
|
|
230
|
-
): Promise<GoogleCalendarCredentials> {
|
|
231
|
-
// Dynamic import avoids pulling Supabase into browser bundles
|
|
232
|
-
const { createClient } = await import('@supabase/supabase-js')
|
|
233
|
-
const { decryptCredentialValue } = await import(
|
|
234
|
-
'../../../../../../../auth/multi-tenancy/credentials/server/service'
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
const supabaseUrl = process.env.SUPABASE_URL
|
|
238
|
-
const supabaseKey = process.env.SUPABASE_SERVICE_KEY
|
|
239
|
-
if (!supabaseUrl || !supabaseKey) {
|
|
240
|
-
throw new ToolingError('credentials_invalid', 'Missing Supabase environment variables', { organizationId })
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const supabase = createClient(supabaseUrl, supabaseKey)
|
|
244
|
-
const { data, error } = await supabase
|
|
245
|
-
.from('credentials')
|
|
246
|
-
.select('encrypted_value')
|
|
247
|
-
.eq('organization_id', organizationId)
|
|
248
|
-
.eq('name', credentialName)
|
|
249
|
-
.single()
|
|
250
|
-
|
|
251
|
-
if (error || !data) {
|
|
252
|
-
throw new ToolingError('credentials_invalid', 'Google Calendar credential not found', {
|
|
253
|
-
organizationId,
|
|
254
|
-
credentialName
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return decryptCredentialValue(data.encrypted_value) as unknown as GoogleCalendarCredentials
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Internal list events implementation
|
|
263
|
-
*/
|
|
264
|
-
private async _listEvents(
|
|
265
|
-
client: calendar_v3.Calendar,
|
|
266
|
-
params: ListEventsParams,
|
|
267
|
-
context?: ExecutionContext
|
|
268
|
-
): Promise<ListEventsResult> {
|
|
269
|
-
if (!params.organizationId) {
|
|
270
|
-
throw new ToolingError('validation_error', 'Missing required field: organizationId', { params })
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const calendarId = params.calendarId ?? 'primary'
|
|
274
|
-
const maxResults = Math.min(params.maxResults ?? 10, 250)
|
|
275
|
-
const singleEvents = params.singleEvents ?? true
|
|
276
|
-
const orderBy = params.orderBy ?? 'startTime'
|
|
277
|
-
const timeMin = params.timeMin ?? new Date().toISOString()
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
const response = await client.events.list({
|
|
281
|
-
calendarId,
|
|
282
|
-
timeMin,
|
|
283
|
-
timeMax: params.timeMax,
|
|
284
|
-
maxResults,
|
|
285
|
-
singleEvents,
|
|
286
|
-
orderBy
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
const items = (response.data.items ?? []).map(this.transformEvent)
|
|
290
|
-
|
|
291
|
-
if (context?.logger) {
|
|
292
|
-
context.logger.info(
|
|
293
|
-
`[GoogleCalendarAdapter] Events listed: organizationId=${context.organizationId} executionId=${context.executionId} calendarId=${calendarId} count=${items.length}`
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return {
|
|
298
|
-
items,
|
|
299
|
-
nextPageToken: response.data.nextPageToken ?? undefined,
|
|
300
|
-
summary: response.data.summary ?? undefined,
|
|
301
|
-
timeZone: response.data.timeZone ?? undefined
|
|
302
|
-
}
|
|
303
|
-
} catch (error: any) {
|
|
304
|
-
throw this.handleApiError(error, calendarId)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Internal get event implementation
|
|
310
|
-
*/
|
|
311
|
-
private async _getEvent(
|
|
312
|
-
client: calendar_v3.Calendar,
|
|
313
|
-
params: GetEventParams,
|
|
314
|
-
context?: ExecutionContext
|
|
315
|
-
): Promise<CalendarEvent> {
|
|
316
|
-
if (!params.organizationId) {
|
|
317
|
-
throw new ToolingError('validation_error', 'Missing required field: organizationId', { params })
|
|
318
|
-
}
|
|
319
|
-
if (!params.eventId) {
|
|
320
|
-
throw new ToolingError('validation_error', 'Missing required field: eventId', { params })
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const calendarId = params.calendarId ?? 'primary'
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
const response = await client.events.get({
|
|
327
|
-
calendarId,
|
|
328
|
-
eventId: params.eventId
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
const event = this.transformEvent(response.data)
|
|
332
|
-
|
|
333
|
-
if (context?.logger) {
|
|
334
|
-
context.logger.info(
|
|
335
|
-
`[GoogleCalendarAdapter] Event retrieved: organizationId=${context.organizationId} executionId=${context.executionId} calendarId=${calendarId} eventId=${params.eventId}`
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return event
|
|
340
|
-
} catch (error: any) {
|
|
341
|
-
throw this.handleApiError(error, calendarId)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Transform a raw Google Calendar API event into the typed CalendarEvent shape
|
|
347
|
-
*/
|
|
348
|
-
private transformEvent(raw: calendar_v3.Schema$Event): CalendarEvent {
|
|
349
|
-
return {
|
|
350
|
-
id: raw.id ?? '',
|
|
351
|
-
summary: raw.summary ?? undefined,
|
|
352
|
-
description: raw.description ?? undefined,
|
|
353
|
-
location: raw.location ?? undefined,
|
|
354
|
-
start: {
|
|
355
|
-
dateTime: raw.start?.dateTime ?? undefined,
|
|
356
|
-
date: raw.start?.date ?? undefined,
|
|
357
|
-
timeZone: raw.start?.timeZone ?? undefined
|
|
358
|
-
},
|
|
359
|
-
end: {
|
|
360
|
-
dateTime: raw.end?.dateTime ?? undefined,
|
|
361
|
-
date: raw.end?.date ?? undefined,
|
|
362
|
-
timeZone: raw.end?.timeZone ?? undefined
|
|
363
|
-
},
|
|
364
|
-
status: (raw.status as CalendarEvent['status']) ?? undefined,
|
|
365
|
-
htmlLink: raw.htmlLink ?? undefined,
|
|
366
|
-
created: raw.created ?? undefined,
|
|
367
|
-
updated: raw.updated ?? undefined,
|
|
368
|
-
organizer: raw.organizer
|
|
369
|
-
? {
|
|
370
|
-
email: raw.organizer.email ?? undefined,
|
|
371
|
-
displayName: raw.organizer.displayName ?? undefined
|
|
372
|
-
}
|
|
373
|
-
: undefined,
|
|
374
|
-
attendees: raw.attendees?.map((a) => ({
|
|
375
|
-
email: a.email ?? undefined,
|
|
376
|
-
displayName: a.displayName ?? undefined,
|
|
377
|
-
responseStatus: a.responseStatus ?? undefined
|
|
378
|
-
})),
|
|
379
|
-
recurringEventId: raw.recurringEventId ?? undefined
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Handle Google Calendar API errors with meaningful messages
|
|
385
|
-
*/
|
|
386
|
-
private handleApiError(error: any, calendarId: string): ToolingError {
|
|
387
|
-
const code = error.code || error.status || error.response?.status
|
|
388
|
-
|
|
389
|
-
if (code === 404) {
|
|
390
|
-
return new ToolingError('api_error', `Calendar or event not found: ${calendarId}`, {
|
|
391
|
-
calendarId,
|
|
392
|
-
statusCode: 404
|
|
393
|
-
})
|
|
394
|
-
}
|
|
395
|
-
if (code === 403) {
|
|
396
|
-
return new ToolingError('permission_denied', 'Access denied. Check calendar sharing permissions.', {
|
|
397
|
-
calendarId,
|
|
398
|
-
statusCode: 403
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
if (code === 401) {
|
|
402
|
-
return new ToolingError('credentials_invalid', 'Authentication expired. Token refresh required.', {
|
|
403
|
-
calendarId,
|
|
404
|
-
statusCode: 401
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
if (code === 429) {
|
|
408
|
-
return new ToolingError('rate_limit_exceeded', 'Rate limited. Retry after delay.', {
|
|
409
|
-
calendarId,
|
|
410
|
-
statusCode: 429
|
|
411
|
-
})
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return new ToolingError('api_error', `Google Calendar API error: ${error.message}`, {
|
|
415
|
-
calendarId,
|
|
416
|
-
statusCode: code,
|
|
417
|
-
details: error
|
|
418
|
-
})
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Validate credentials structure
|
|
423
|
-
* Required by BaseIntegrationAdapter interface
|
|
424
|
-
*/
|
|
425
|
-
validateCredentials(creds: Record<string, unknown>): boolean {
|
|
426
|
-
return typeof creds.accessToken === 'string' && creds.accessToken.length > 0
|
|
427
|
-
}
|
|
428
|
-
}
|
|
1
|
+
import type { calendar_v3 } from '@googleapis/calendar'
|
|
2
|
+
import type { BaseIntegrationAdapter } from '../../../base-integration-adapter'
|
|
3
|
+
import { ToolingError } from '../../../../types'
|
|
4
|
+
import type { ExecutionContext } from '../../../../../base/types'
|
|
5
|
+
import { getOAuthCredentials } from '../../../../../../../integrations/oauth/server/credentials'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Google Calendar credentials format
|
|
9
|
+
* Stored in credentials table, encrypted
|
|
10
|
+
*/
|
|
11
|
+
interface GoogleCalendarCredentials {
|
|
12
|
+
accessToken: string
|
|
13
|
+
refreshToken?: string
|
|
14
|
+
expiresAt?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Date/time value from Google Calendar API
|
|
19
|
+
*/
|
|
20
|
+
export interface CalendarDateTime {
|
|
21
|
+
dateTime?: string // ISO 8601 datetime (when time is specified)
|
|
22
|
+
date?: string // YYYY-MM-DD (for all-day events)
|
|
23
|
+
timeZone?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A Google Calendar event (plausible-use fields only)
|
|
28
|
+
*/
|
|
29
|
+
export interface CalendarEvent {
|
|
30
|
+
id: string
|
|
31
|
+
summary?: string
|
|
32
|
+
description?: string
|
|
33
|
+
location?: string
|
|
34
|
+
start: CalendarDateTime
|
|
35
|
+
end: CalendarDateTime
|
|
36
|
+
status?: 'confirmed' | 'tentative' | 'cancelled'
|
|
37
|
+
htmlLink?: string
|
|
38
|
+
created?: string
|
|
39
|
+
updated?: string
|
|
40
|
+
organizer?: { email?: string; displayName?: string }
|
|
41
|
+
attendees?: Array<{ email?: string; displayName?: string; responseStatus?: string }>
|
|
42
|
+
recurringEventId?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* listEvents parameters.
|
|
47
|
+
*
|
|
48
|
+
* `credentialName` is required when calling the adapter directly (HTTP-handler
|
|
49
|
+
* path). It is ignored on the dispatch path, where credentials are injected
|
|
50
|
+
* by the execution-engine pipeline.
|
|
51
|
+
*/
|
|
52
|
+
interface ListEventsParams {
|
|
53
|
+
organizationId: string
|
|
54
|
+
credentialName?: string
|
|
55
|
+
calendarId?: string
|
|
56
|
+
timeMin?: string
|
|
57
|
+
timeMax?: string
|
|
58
|
+
maxResults?: number
|
|
59
|
+
singleEvents?: boolean
|
|
60
|
+
orderBy?: 'startTime' | 'updated'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* listEvents result
|
|
65
|
+
*/
|
|
66
|
+
export interface ListEventsResult {
|
|
67
|
+
items: CalendarEvent[]
|
|
68
|
+
nextPageToken?: string
|
|
69
|
+
summary?: string
|
|
70
|
+
timeZone?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* getEvent parameters
|
|
75
|
+
*/
|
|
76
|
+
interface GetEventParams {
|
|
77
|
+
organizationId: string
|
|
78
|
+
credentialName?: string
|
|
79
|
+
calendarId?: string
|
|
80
|
+
eventId: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Google Calendar adapter for calendar and event operations via Google Calendar API v3
|
|
85
|
+
*
|
|
86
|
+
* Read-only methods:
|
|
87
|
+
* - listEvents: List events from a calendar
|
|
88
|
+
* - getEvent: Get a single event by ID
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* const adapter = new GoogleCalendarAdapter()
|
|
92
|
+
* const result = await adapter.listEvents({
|
|
93
|
+
* organizationId: 'org-uuid',
|
|
94
|
+
* calendarId: 'primary',
|
|
95
|
+
* maxResults: 10
|
|
96
|
+
* })
|
|
97
|
+
*/
|
|
98
|
+
export class GoogleCalendarAdapter implements BaseIntegrationAdapter {
|
|
99
|
+
readonly name = 'google-calendar'
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Call Google Calendar API method
|
|
103
|
+
* @param method - Method name ('listEvents' | 'getEvent')
|
|
104
|
+
* @param params - Method parameters (must include organizationId)
|
|
105
|
+
* @param credentials - OAuth2 access token
|
|
106
|
+
* @param context - Execution context for logging and tracing
|
|
107
|
+
*/
|
|
108
|
+
async call(
|
|
109
|
+
method: string,
|
|
110
|
+
params: unknown,
|
|
111
|
+
credentials: Record<string, unknown>,
|
|
112
|
+
context?: ExecutionContext
|
|
113
|
+
): Promise<unknown> {
|
|
114
|
+
if (!this.validateCredentials(credentials)) {
|
|
115
|
+
throw new ToolingError('credentials_invalid', 'Invalid Google Calendar credentials', {
|
|
116
|
+
integration: 'google-calendar',
|
|
117
|
+
method
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
const calCreds = credentials as unknown as GoogleCalendarCredentials
|
|
121
|
+
|
|
122
|
+
const client = await this.createClient(calCreds)
|
|
123
|
+
|
|
124
|
+
switch (method) {
|
|
125
|
+
case 'listEvents':
|
|
126
|
+
return this.listEvents(client, params as ListEventsParams, context)
|
|
127
|
+
case 'getEvent':
|
|
128
|
+
return this.getEvent(client, params as GetEventParams, context)
|
|
129
|
+
default:
|
|
130
|
+
throw new ToolingError('method_not_found', `Unknown method: ${method}`, {
|
|
131
|
+
integration: 'google-calendar',
|
|
132
|
+
method
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* List events from a calendar — callable directly (no method dispatch overhead)
|
|
139
|
+
*/
|
|
140
|
+
async listEvents(
|
|
141
|
+
clientOrParams: calendar_v3.Calendar | ListEventsParams,
|
|
142
|
+
paramsOrContext?: ListEventsParams | ExecutionContext,
|
|
143
|
+
maybeContext?: ExecutionContext
|
|
144
|
+
): Promise<ListEventsResult> {
|
|
145
|
+
// Support both direct call (listEvents(params, context)) and
|
|
146
|
+
// dispatch call (listEvents(client, params, context))
|
|
147
|
+
let client: calendar_v3.Calendar
|
|
148
|
+
let params: ListEventsParams
|
|
149
|
+
let context: ExecutionContext | undefined
|
|
150
|
+
|
|
151
|
+
if ('calendarId' in (clientOrParams as object) || 'organizationId' in (clientOrParams as object)) {
|
|
152
|
+
// Called directly: listEvents(params, context?)
|
|
153
|
+
params = clientOrParams as ListEventsParams
|
|
154
|
+
context = paramsOrContext as ExecutionContext | undefined
|
|
155
|
+
if (!params.credentialName) {
|
|
156
|
+
throw new ToolingError('validation_error', 'Missing required field: credentialName', { params })
|
|
157
|
+
}
|
|
158
|
+
const creds = await this.getCredentialsForOrg(params.organizationId, params.credentialName)
|
|
159
|
+
client = await this.createClient(creds)
|
|
160
|
+
} else {
|
|
161
|
+
// Called via dispatch: listEvents(client, params, context?)
|
|
162
|
+
client = clientOrParams as calendar_v3.Calendar
|
|
163
|
+
params = paramsOrContext as ListEventsParams
|
|
164
|
+
context = maybeContext
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return this._listEvents(client, params, context)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get a single event by ID — callable directly
|
|
172
|
+
*/
|
|
173
|
+
async getEvent(
|
|
174
|
+
clientOrParams: calendar_v3.Calendar | GetEventParams,
|
|
175
|
+
paramsOrContext?: GetEventParams | ExecutionContext,
|
|
176
|
+
maybeContext?: ExecutionContext
|
|
177
|
+
): Promise<CalendarEvent> {
|
|
178
|
+
let client: calendar_v3.Calendar
|
|
179
|
+
let params: GetEventParams
|
|
180
|
+
let context: ExecutionContext | undefined
|
|
181
|
+
|
|
182
|
+
if ('eventId' in (clientOrParams as object) || 'organizationId' in (clientOrParams as object)) {
|
|
183
|
+
// Called directly: getEvent(params, context?)
|
|
184
|
+
params = clientOrParams as GetEventParams
|
|
185
|
+
context = paramsOrContext as ExecutionContext | undefined
|
|
186
|
+
if (!params.credentialName) {
|
|
187
|
+
throw new ToolingError('validation_error', 'Missing required field: credentialName', { params })
|
|
188
|
+
}
|
|
189
|
+
const creds = await this.getCredentialsForOrg(params.organizationId, params.credentialName)
|
|
190
|
+
client = await this.createClient(creds)
|
|
191
|
+
} else {
|
|
192
|
+
// Called via dispatch: getEvent(client, params, context?)
|
|
193
|
+
client = clientOrParams as calendar_v3.Calendar
|
|
194
|
+
params = paramsOrContext as GetEventParams
|
|
195
|
+
context = maybeContext
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return this._getEvent(client, params, context)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create authenticated Google Calendar client (lazy loads @googleapis/calendar SDK)
|
|
203
|
+
* CRITICAL: Use dynamic import to prevent OOM and module-level crashes
|
|
204
|
+
*/
|
|
205
|
+
private async createClient(creds: GoogleCalendarCredentials): Promise<calendar_v3.Calendar> {
|
|
206
|
+
const { calendar } = await import('@googleapis/calendar')
|
|
207
|
+
const { OAuth2Client } = await import('google-auth-library')
|
|
208
|
+
|
|
209
|
+
// Get client credentials from environment for token refresh
|
|
210
|
+
const { clientId, clientSecret } = getOAuthCredentials('google-calendar')
|
|
211
|
+
|
|
212
|
+
const oauth2Client = new OAuth2Client(clientId, clientSecret)
|
|
213
|
+
oauth2Client.setCredentials({
|
|
214
|
+
access_token: creds.accessToken,
|
|
215
|
+
refresh_token: creds.refreshToken
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
return calendar({ version: 'v3', auth: oauth2Client })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Retrieve credentials for an organization from the credentials table.
|
|
223
|
+
* Used when the adapter is called directly (not via the dispatch pipeline).
|
|
224
|
+
* The caller passes the credential name explicitly -- names are user-chosen
|
|
225
|
+
* at OAuth-modal time, so they cannot be hardcoded.
|
|
226
|
+
*/
|
|
227
|
+
private async getCredentialsForOrg(
|
|
228
|
+
organizationId: string,
|
|
229
|
+
credentialName: string
|
|
230
|
+
): Promise<GoogleCalendarCredentials> {
|
|
231
|
+
// Dynamic import avoids pulling Supabase into browser bundles
|
|
232
|
+
const { createClient } = await import('@supabase/supabase-js')
|
|
233
|
+
const { decryptCredentialValue } = await import(
|
|
234
|
+
'../../../../../../../auth/multi-tenancy/credentials/server/service'
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
const supabaseUrl = process.env.SUPABASE_URL
|
|
238
|
+
const supabaseKey = process.env.SUPABASE_SERVICE_KEY
|
|
239
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
240
|
+
throw new ToolingError('credentials_invalid', 'Missing Supabase environment variables', { organizationId })
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const supabase = createClient(supabaseUrl, supabaseKey)
|
|
244
|
+
const { data, error } = await supabase
|
|
245
|
+
.from('credentials')
|
|
246
|
+
.select('encrypted_value')
|
|
247
|
+
.eq('organization_id', organizationId)
|
|
248
|
+
.eq('name', credentialName)
|
|
249
|
+
.single()
|
|
250
|
+
|
|
251
|
+
if (error || !data) {
|
|
252
|
+
throw new ToolingError('credentials_invalid', 'Google Calendar credential not found', {
|
|
253
|
+
organizationId,
|
|
254
|
+
credentialName
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return decryptCredentialValue(data.encrypted_value) as unknown as GoogleCalendarCredentials
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Internal list events implementation
|
|
263
|
+
*/
|
|
264
|
+
private async _listEvents(
|
|
265
|
+
client: calendar_v3.Calendar,
|
|
266
|
+
params: ListEventsParams,
|
|
267
|
+
context?: ExecutionContext
|
|
268
|
+
): Promise<ListEventsResult> {
|
|
269
|
+
if (!params.organizationId) {
|
|
270
|
+
throw new ToolingError('validation_error', 'Missing required field: organizationId', { params })
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const calendarId = params.calendarId ?? 'primary'
|
|
274
|
+
const maxResults = Math.min(params.maxResults ?? 10, 250)
|
|
275
|
+
const singleEvents = params.singleEvents ?? true
|
|
276
|
+
const orderBy = params.orderBy ?? 'startTime'
|
|
277
|
+
const timeMin = params.timeMin ?? new Date().toISOString()
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const response = await client.events.list({
|
|
281
|
+
calendarId,
|
|
282
|
+
timeMin,
|
|
283
|
+
timeMax: params.timeMax,
|
|
284
|
+
maxResults,
|
|
285
|
+
singleEvents,
|
|
286
|
+
orderBy
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
const items = (response.data.items ?? []).map(this.transformEvent)
|
|
290
|
+
|
|
291
|
+
if (context?.logger) {
|
|
292
|
+
context.logger.info(
|
|
293
|
+
`[GoogleCalendarAdapter] Events listed: organizationId=${context.organizationId} executionId=${context.executionId} calendarId=${calendarId} count=${items.length}`
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
items,
|
|
299
|
+
nextPageToken: response.data.nextPageToken ?? undefined,
|
|
300
|
+
summary: response.data.summary ?? undefined,
|
|
301
|
+
timeZone: response.data.timeZone ?? undefined
|
|
302
|
+
}
|
|
303
|
+
} catch (error: any) {
|
|
304
|
+
throw this.handleApiError(error, calendarId)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Internal get event implementation
|
|
310
|
+
*/
|
|
311
|
+
private async _getEvent(
|
|
312
|
+
client: calendar_v3.Calendar,
|
|
313
|
+
params: GetEventParams,
|
|
314
|
+
context?: ExecutionContext
|
|
315
|
+
): Promise<CalendarEvent> {
|
|
316
|
+
if (!params.organizationId) {
|
|
317
|
+
throw new ToolingError('validation_error', 'Missing required field: organizationId', { params })
|
|
318
|
+
}
|
|
319
|
+
if (!params.eventId) {
|
|
320
|
+
throw new ToolingError('validation_error', 'Missing required field: eventId', { params })
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const calendarId = params.calendarId ?? 'primary'
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const response = await client.events.get({
|
|
327
|
+
calendarId,
|
|
328
|
+
eventId: params.eventId
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
const event = this.transformEvent(response.data)
|
|
332
|
+
|
|
333
|
+
if (context?.logger) {
|
|
334
|
+
context.logger.info(
|
|
335
|
+
`[GoogleCalendarAdapter] Event retrieved: organizationId=${context.organizationId} executionId=${context.executionId} calendarId=${calendarId} eventId=${params.eventId}`
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return event
|
|
340
|
+
} catch (error: any) {
|
|
341
|
+
throw this.handleApiError(error, calendarId)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Transform a raw Google Calendar API event into the typed CalendarEvent shape
|
|
347
|
+
*/
|
|
348
|
+
private transformEvent(raw: calendar_v3.Schema$Event): CalendarEvent {
|
|
349
|
+
return {
|
|
350
|
+
id: raw.id ?? '',
|
|
351
|
+
summary: raw.summary ?? undefined,
|
|
352
|
+
description: raw.description ?? undefined,
|
|
353
|
+
location: raw.location ?? undefined,
|
|
354
|
+
start: {
|
|
355
|
+
dateTime: raw.start?.dateTime ?? undefined,
|
|
356
|
+
date: raw.start?.date ?? undefined,
|
|
357
|
+
timeZone: raw.start?.timeZone ?? undefined
|
|
358
|
+
},
|
|
359
|
+
end: {
|
|
360
|
+
dateTime: raw.end?.dateTime ?? undefined,
|
|
361
|
+
date: raw.end?.date ?? undefined,
|
|
362
|
+
timeZone: raw.end?.timeZone ?? undefined
|
|
363
|
+
},
|
|
364
|
+
status: (raw.status as CalendarEvent['status']) ?? undefined,
|
|
365
|
+
htmlLink: raw.htmlLink ?? undefined,
|
|
366
|
+
created: raw.created ?? undefined,
|
|
367
|
+
updated: raw.updated ?? undefined,
|
|
368
|
+
organizer: raw.organizer
|
|
369
|
+
? {
|
|
370
|
+
email: raw.organizer.email ?? undefined,
|
|
371
|
+
displayName: raw.organizer.displayName ?? undefined
|
|
372
|
+
}
|
|
373
|
+
: undefined,
|
|
374
|
+
attendees: raw.attendees?.map((a) => ({
|
|
375
|
+
email: a.email ?? undefined,
|
|
376
|
+
displayName: a.displayName ?? undefined,
|
|
377
|
+
responseStatus: a.responseStatus ?? undefined
|
|
378
|
+
})),
|
|
379
|
+
recurringEventId: raw.recurringEventId ?? undefined
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Handle Google Calendar API errors with meaningful messages
|
|
385
|
+
*/
|
|
386
|
+
private handleApiError(error: any, calendarId: string): ToolingError {
|
|
387
|
+
const code = error.code || error.status || error.response?.status
|
|
388
|
+
|
|
389
|
+
if (code === 404) {
|
|
390
|
+
return new ToolingError('api_error', `Calendar or event not found: ${calendarId}`, {
|
|
391
|
+
calendarId,
|
|
392
|
+
statusCode: 404
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
if (code === 403) {
|
|
396
|
+
return new ToolingError('permission_denied', 'Access denied. Check calendar sharing permissions.', {
|
|
397
|
+
calendarId,
|
|
398
|
+
statusCode: 403
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
if (code === 401) {
|
|
402
|
+
return new ToolingError('credentials_invalid', 'Authentication expired. Token refresh required.', {
|
|
403
|
+
calendarId,
|
|
404
|
+
statusCode: 401
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
if (code === 429) {
|
|
408
|
+
return new ToolingError('rate_limit_exceeded', 'Rate limited. Retry after delay.', {
|
|
409
|
+
calendarId,
|
|
410
|
+
statusCode: 429
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return new ToolingError('api_error', `Google Calendar API error: ${error.message}`, {
|
|
415
|
+
calendarId,
|
|
416
|
+
statusCode: code,
|
|
417
|
+
details: error
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Validate credentials structure
|
|
423
|
+
* Required by BaseIntegrationAdapter interface
|
|
424
|
+
*/
|
|
425
|
+
validateCredentials(creds: Record<string, unknown>): boolean {
|
|
426
|
+
return typeof creds.accessToken === 'string' && creds.accessToken.length > 0
|
|
427
|
+
}
|
|
428
|
+
}
|