Skip to content

Commit 6c9fce5

Browse files
aadamgoughAdam GoughAdam Goughicecrasher321
authored
Feat(microsoftteams-file): new trigger + file upload (#1590)
* adding file logic and chat trigger * working trig * teams specific logic * greptile comments * lint * cleaned up * save modal changes * created a interface for subscriptions * removed trigger task * reduce comments * removed trig task * removed comment * simplified * added tele logic back * addressed some more comments * simplified db call * cleaned up utils * helper telegram * removed fallback * removed scope * simplify to use helpers * fix credential resolution * add logs * fix * fix attachment case --------- Co-authored-by: Adam Gough <[email protected]> Co-authored-by: Adam Gough <[email protected]> Co-authored-by: Vikhyath Mondreti <[email protected]>
1 parent b296323 commit 6c9fce5

File tree

25 files changed

+1512
-330
lines changed

25 files changed

+1512
-330
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { db } from '@sim/db'
2+
import { webhook as webhookTable, workflow as workflowTable } from '@sim/db/schema'
3+
import { and, eq } from 'drizzle-orm'
4+
import { type NextRequest, NextResponse } from 'next/server'
5+
import { verifyCronAuth } from '@/lib/auth/internal'
6+
import { createLogger } from '@/lib/logs/console/logger'
7+
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
8+
9+
const logger = createLogger('TeamsSubscriptionRenewal')
10+
11+
/**
12+
* Cron endpoint to renew Microsoft Teams chat subscriptions before they expire
13+
*
14+
* Teams subscriptions expire after ~3 days and must be renewed.
15+
* Configured in helm/sim/values.yaml under cronjobs.jobs.renewSubscriptions
16+
*/
17+
export async function GET(request: NextRequest) {
18+
try {
19+
const authError = verifyCronAuth(request, 'Teams subscription renewal')
20+
if (authError) {
21+
return authError
22+
}
23+
24+
logger.info('Starting Teams subscription renewal job')
25+
26+
let totalRenewed = 0
27+
let totalFailed = 0
28+
let totalChecked = 0
29+
30+
// Get all active Microsoft Teams webhooks with their workflows
31+
const webhooksWithWorkflows = await db
32+
.select({
33+
webhook: webhookTable,
34+
workflow: workflowTable,
35+
})
36+
.from(webhookTable)
37+
.innerJoin(workflowTable, eq(webhookTable.workflowId, workflowTable.id))
38+
.where(and(eq(webhookTable.isActive, true), eq(webhookTable.provider, 'microsoftteams')))
39+
40+
logger.info(
41+
`Found ${webhooksWithWorkflows.length} active Teams webhooks, checking for expiring subscriptions`
42+
)
43+
44+
// Renewal threshold: 48 hours before expiration
45+
const renewalThreshold = new Date(Date.now() + 48 * 60 * 60 * 1000)
46+
47+
for (const { webhook, workflow } of webhooksWithWorkflows) {
48+
const config = (webhook.providerConfig as Record<string, any>) || {}
49+
50+
// Check if this is a Teams chat subscription that needs renewal
51+
if (config.triggerId !== 'microsoftteams_chat_subscription') continue
52+
53+
const expirationStr = config.subscriptionExpiration as string | undefined
54+
if (!expirationStr) continue
55+
56+
const expiresAt = new Date(expirationStr)
57+
if (expiresAt > renewalThreshold) continue // Not expiring soon
58+
59+
totalChecked++
60+
61+
try {
62+
logger.info(
63+
`Renewing Teams subscription for webhook ${webhook.id} (expires: ${expiresAt.toISOString()})`
64+
)
65+
66+
const credentialId = config.credentialId as string | undefined
67+
const externalSubscriptionId = config.externalSubscriptionId as string | undefined
68+
69+
if (!credentialId || !externalSubscriptionId) {
70+
logger.error(`Missing credentialId or externalSubscriptionId for webhook ${webhook.id}`)
71+
totalFailed++
72+
continue
73+
}
74+
75+
// Get fresh access token
76+
const accessToken = await refreshAccessTokenIfNeeded(
77+
credentialId,
78+
workflow.userId,
79+
`renewal-${webhook.id}`
80+
)
81+
82+
if (!accessToken) {
83+
logger.error(`Failed to get access token for webhook ${webhook.id}`)
84+
totalFailed++
85+
continue
86+
}
87+
88+
// Extend subscription to maximum lifetime (4230 minutes = ~3 days)
89+
const maxLifetimeMinutes = 4230
90+
const newExpirationDateTime = new Date(
91+
Date.now() + maxLifetimeMinutes * 60 * 1000
92+
).toISOString()
93+
94+
const res = await fetch(
95+
`https://graph.microsoft.com/v1.0/subscriptions/${externalSubscriptionId}`,
96+
{
97+
method: 'PATCH',
98+
headers: {
99+
Authorization: `Bearer ${accessToken}`,
100+
'Content-Type': 'application/json',
101+
},
102+
body: JSON.stringify({ expirationDateTime: newExpirationDateTime }),
103+
}
104+
)
105+
106+
if (!res.ok) {
107+
const error = await res.json()
108+
logger.error(
109+
`Failed to renew Teams subscription ${externalSubscriptionId} for webhook ${webhook.id}`,
110+
{ status: res.status, error: error.error }
111+
)
112+
totalFailed++
113+
continue
114+
}
115+
116+
const payload = await res.json()
117+
118+
// Update webhook config with new expiration
119+
const updatedConfig = {
120+
...config,
121+
subscriptionExpiration: payload.expirationDateTime,
122+
}
123+
124+
await db
125+
.update(webhookTable)
126+
.set({ providerConfig: updatedConfig, updatedAt: new Date() })
127+
.where(eq(webhookTable.id, webhook.id))
128+
129+
logger.info(
130+
`Successfully renewed Teams subscription for webhook ${webhook.id}. New expiration: ${payload.expirationDateTime}`
131+
)
132+
totalRenewed++
133+
} catch (error) {
134+
logger.error(`Error renewing subscription for webhook ${webhook.id}:`, error)
135+
totalFailed++
136+
}
137+
}
138+
139+
logger.info(
140+
`Teams subscription renewal job completed. Checked: ${totalChecked}, Renewed: ${totalRenewed}, Failed: ${totalFailed}`
141+
)
142+
143+
return NextResponse.json({
144+
success: true,
145+
checked: totalChecked,
146+
renewed: totalRenewed,
147+
failed: totalFailed,
148+
total: webhooksWithWorkflows.length,
149+
})
150+
} catch (error) {
151+
logger.error('Error in Teams subscription renewal job:', error)
152+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
153+
}
154+
}

apps/sim/app/api/webhooks/[id]/route.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,18 @@ export async function DELETE(
408408
}
409409
}
410410

411-
// If it's a Telegram webhook, delete it from Telegram first
411+
// Delete Microsoft Teams subscription if applicable
412+
if (foundWebhook.provider === 'microsoftteams') {
413+
const { deleteTeamsSubscription } = await import('@/lib/webhooks/webhook-helpers')
414+
logger.info(`[${requestId}] Deleting Teams subscription for webhook ${id}`)
415+
await deleteTeamsSubscription(foundWebhook, webhookData.workflow, requestId)
416+
// Don't fail webhook deletion if subscription cleanup fails
417+
}
418+
419+
// Delete Telegram webhook if applicable
412420
if (foundWebhook.provider === 'telegram') {
413421
try {
414-
const { botToken } = foundWebhook.providerConfig as { botToken: string }
422+
const { botToken } = (foundWebhook.providerConfig || {}) as { botToken?: string }
415423

416424
if (!botToken) {
417425
logger.warn(`[${requestId}] Missing botToken for Telegram webhook deletion.`, {
@@ -426,19 +434,15 @@ export async function DELETE(
426434
const telegramApiUrl = `https://api.telegram.org/bot${botToken}/deleteWebhook`
427435
const telegramResponse = await fetch(telegramApiUrl, {
428436
method: 'POST',
429-
headers: {
430-
'Content-Type': 'application/json',
431-
},
437+
headers: { 'Content-Type': 'application/json' },
432438
})
433439

434440
const responseBody = await telegramResponse.json()
435441
if (!telegramResponse.ok || !responseBody.ok) {
436442
const errorMessage =
437443
responseBody.description ||
438444
`Failed to delete Telegram webhook. Status: ${telegramResponse.status}`
439-
logger.error(`[${requestId}] ${errorMessage}`, {
440-
response: responseBody,
441-
})
445+
logger.error(`[${requestId}] ${errorMessage}`, { response: responseBody })
442446
return NextResponse.json(
443447
{ error: 'Failed to delete webhook from Telegram', details: errorMessage },
444448
{ status: 500 }
@@ -453,10 +457,7 @@ export async function DELETE(
453457
stack: error.stack,
454458
})
455459
return NextResponse.json(
456-
{
457-
error: 'Failed to delete webhook from Telegram',
458-
details: error.message,
459-
},
460+
{ error: 'Failed to delete webhook from Telegram', details: error.message },
460461
{ status: 500 }
461462
)
462463
}

0 commit comments

Comments
 (0)