Skip to content

Commit 1c857cd

Browse files
committed
fix circular impport
1 parent 7570e50 commit 1c857cd

File tree

4 files changed

+170
-174
lines changed

4 files changed

+170
-174
lines changed

apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ import { checkInternalAuth } from '@/lib/auth/hybrid'
55
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
66
import { generateRequestId } from '@/lib/core/utils/request'
77
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
8+
import { uploadFilesForTeamsMessage } from '@/tools/microsoft_teams/server-utils'
89
import type { GraphApiErrorResponse, GraphChatMessage } from '@/tools/microsoft_teams/types'
9-
import {
10-
resolveMentionsForChannel,
11-
type TeamsMention,
12-
uploadFilesForTeamsMessage,
13-
} from '@/tools/microsoft_teams/utils'
10+
import { resolveMentionsForChannel, type TeamsMention } from '@/tools/microsoft_teams/utils'
1411

1512
export const dynamic = 'force-dynamic'
1613

apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ import { checkInternalAuth } from '@/lib/auth/hybrid'
55
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
66
import { generateRequestId } from '@/lib/core/utils/request'
77
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
8+
import { uploadFilesForTeamsMessage } from '@/tools/microsoft_teams/server-utils'
89
import type { GraphApiErrorResponse, GraphChatMessage } from '@/tools/microsoft_teams/types'
9-
import {
10-
resolveMentionsForChat,
11-
type TeamsMention,
12-
uploadFilesForTeamsMessage,
13-
} from '@/tools/microsoft_teams/utils'
10+
import { resolveMentionsForChat, type TeamsMention } from '@/tools/microsoft_teams/utils'
1411

1512
export const dynamic = 'force-dynamic'
1613

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* Server-side utilities for Microsoft Teams integration.
3+
* This file contains functions that require server-side dependencies and should
4+
* only be imported by API routes, NOT by tool definitions (to avoid circular imports).
5+
*/
6+
import type { Logger } from '@sim/logger'
7+
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
8+
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
9+
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
10+
import type { UserFile } from '@/executor/types'
11+
import type { GraphApiErrorResponse, GraphDriveItem } from '@/tools/microsoft_teams/types'
12+
13+
/** Maximum file size for Teams direct upload (4MB) */
14+
const MAX_TEAMS_FILE_SIZE = 4 * 1024 * 1024
15+
16+
/** Output format for uploaded files */
17+
export interface TeamsFileOutput {
18+
name: string
19+
mimeType: string
20+
data: string
21+
size: number
22+
}
23+
24+
/** Attachment reference for Teams message */
25+
export interface TeamsAttachmentRef {
26+
id: string
27+
contentType: 'reference'
28+
contentUrl: string
29+
name: string
30+
}
31+
32+
/** Result from processing and uploading files for Teams */
33+
export interface TeamsFileUploadResult {
34+
attachments: TeamsAttachmentRef[]
35+
filesOutput: TeamsFileOutput[]
36+
}
37+
38+
/**
39+
* Process and upload files to OneDrive for Teams message attachments.
40+
* Handles size validation, downloading from storage, uploading to OneDrive,
41+
* and creating attachment references.
42+
*/
43+
export async function uploadFilesForTeamsMessage(params: {
44+
rawFiles: RawFileInput[]
45+
accessToken: string
46+
requestId: string
47+
logger: Logger
48+
}): Promise<TeamsFileUploadResult> {
49+
const { rawFiles, accessToken, requestId, logger: log } = params
50+
const attachments: TeamsAttachmentRef[] = []
51+
const filesOutput: TeamsFileOutput[] = []
52+
53+
if (!rawFiles || rawFiles.length === 0) {
54+
return { attachments, filesOutput }
55+
}
56+
57+
log.info(`[${requestId}] Processing ${rawFiles.length} file(s) for upload to OneDrive`)
58+
59+
const userFiles = processFilesToUserFiles(rawFiles, requestId, log) as UserFile[]
60+
61+
for (const file of userFiles) {
62+
// Check size limit
63+
if (file.size > MAX_TEAMS_FILE_SIZE) {
64+
const sizeMB = (file.size / (1024 * 1024)).toFixed(2)
65+
log.error(
66+
`[${requestId}] File ${file.name} is ${sizeMB}MB, exceeds 4MB limit for direct upload`
67+
)
68+
throw new Error(
69+
`File "${file.name}" (${sizeMB}MB) exceeds the 4MB limit for Teams attachments. Use smaller files or upload to SharePoint/OneDrive first.`
70+
)
71+
}
72+
73+
log.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
74+
75+
// Download file from storage
76+
const buffer = await downloadFileFromStorage(file, requestId, log)
77+
filesOutput.push({
78+
name: file.name,
79+
mimeType: file.type || 'application/octet-stream',
80+
data: buffer.toString('base64'),
81+
size: buffer.length,
82+
})
83+
84+
// Upload to OneDrive
85+
const uploadUrl =
86+
'https://graph.microsoft.com/v1.0/me/drive/root:/TeamsAttachments/' +
87+
encodeURIComponent(file.name) +
88+
':/content'
89+
90+
log.info(`[${requestId}] Uploading to OneDrive: ${uploadUrl}`)
91+
92+
const uploadResponse = await secureFetchWithValidation(
93+
uploadUrl,
94+
{
95+
method: 'PUT',
96+
headers: {
97+
Authorization: `Bearer ${accessToken}`,
98+
'Content-Type': file.type || 'application/octet-stream',
99+
},
100+
body: buffer,
101+
},
102+
'uploadUrl'
103+
)
104+
105+
if (!uploadResponse.ok) {
106+
const errorData = (await uploadResponse.json().catch(() => ({}))) as GraphApiErrorResponse
107+
log.error(`[${requestId}] Teams upload failed:`, errorData)
108+
throw new Error(
109+
`Failed to upload file to Teams: ${errorData.error?.message || 'Unknown error'}`
110+
)
111+
}
112+
113+
const uploadedFile = (await uploadResponse.json()) as GraphDriveItem
114+
log.info(`[${requestId}] File uploaded to OneDrive successfully`, {
115+
id: uploadedFile.id,
116+
webUrl: uploadedFile.webUrl,
117+
})
118+
119+
// Get file details for attachment reference
120+
const fileDetailsUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${uploadedFile.id}?$select=id,name,webDavUrl,eTag,size`
121+
122+
const fileDetailsResponse = await secureFetchWithValidation(
123+
fileDetailsUrl,
124+
{
125+
method: 'GET',
126+
headers: {
127+
Authorization: `Bearer ${accessToken}`,
128+
},
129+
},
130+
'fileDetailsUrl'
131+
)
132+
133+
if (!fileDetailsResponse.ok) {
134+
const errorData = (await fileDetailsResponse
135+
.json()
136+
.catch(() => ({}))) as GraphApiErrorResponse
137+
log.error(`[${requestId}] Failed to get file details:`, errorData)
138+
throw new Error(`Failed to get file details: ${errorData.error?.message || 'Unknown error'}`)
139+
}
140+
141+
const fileDetails = (await fileDetailsResponse.json()) as GraphDriveItem
142+
log.info(`[${requestId}] Got file details`, {
143+
webDavUrl: fileDetails.webDavUrl,
144+
eTag: fileDetails.eTag,
145+
})
146+
147+
// Create attachment reference
148+
const attachmentId = fileDetails.eTag?.match(/\{([a-f0-9-]+)\}/i)?.[1] || fileDetails.id
149+
150+
attachments.push({
151+
id: attachmentId,
152+
contentType: 'reference',
153+
contentUrl: fileDetails.webDavUrl!,
154+
name: file.name,
155+
})
156+
157+
log.info(`[${requestId}] Created attachment reference for ${file.name}`)
158+
}
159+
160+
log.info(
161+
`[${requestId}] All ${attachments.length} file(s) uploaded and attachment references created`
162+
)
163+
164+
return { attachments, filesOutput }
165+
}

apps/sim/tools/microsoft_teams/utils.ts

Lines changed: 1 addition & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,9 @@
1-
import type { Logger } from '@sim/logger'
21
import { createLogger } from '@sim/logger'
3-
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
4-
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
5-
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
6-
import type { UserFile } from '@/executor/types'
7-
import type {
8-
GraphApiErrorResponse,
9-
GraphDriveItem,
10-
MicrosoftTeamsAttachment,
11-
} from '@/tools/microsoft_teams/types'
2+
import type { MicrosoftTeamsAttachment } from '@/tools/microsoft_teams/types'
123
import type { ToolFileData } from '@/tools/types'
134

145
const logger = createLogger('MicrosoftTeamsUtils')
156

16-
/** Maximum file size for Teams direct upload (4MB) */
17-
const MAX_TEAMS_FILE_SIZE = 4 * 1024 * 1024
18-
19-
/** Output format for uploaded files */
20-
export interface TeamsFileOutput {
21-
name: string
22-
mimeType: string
23-
data: string
24-
size: number
25-
}
26-
27-
/** Attachment reference for Teams message */
28-
export interface TeamsAttachmentRef {
29-
id: string
30-
contentType: 'reference'
31-
contentUrl: string
32-
name: string
33-
}
34-
35-
/** Result from processing and uploading files for Teams */
36-
export interface TeamsFileUploadResult {
37-
attachments: TeamsAttachmentRef[]
38-
filesOutput: TeamsFileOutput[]
39-
}
40-
41-
/**
42-
* Process and upload files to OneDrive for Teams message attachments.
43-
* Handles size validation, downloading from storage, uploading to OneDrive,
44-
* and creating attachment references.
45-
*/
46-
export async function uploadFilesForTeamsMessage(params: {
47-
rawFiles: RawFileInput[]
48-
accessToken: string
49-
requestId: string
50-
logger: Logger
51-
}): Promise<TeamsFileUploadResult> {
52-
const { rawFiles, accessToken, requestId, logger: log } = params
53-
const attachments: TeamsAttachmentRef[] = []
54-
const filesOutput: TeamsFileOutput[] = []
55-
56-
if (!rawFiles || rawFiles.length === 0) {
57-
return { attachments, filesOutput }
58-
}
59-
60-
log.info(`[${requestId}] Processing ${rawFiles.length} file(s) for upload to OneDrive`)
61-
62-
const userFiles = processFilesToUserFiles(rawFiles, requestId, log) as UserFile[]
63-
64-
for (const file of userFiles) {
65-
// Check size limit
66-
if (file.size > MAX_TEAMS_FILE_SIZE) {
67-
const sizeMB = (file.size / (1024 * 1024)).toFixed(2)
68-
log.error(
69-
`[${requestId}] File ${file.name} is ${sizeMB}MB, exceeds 4MB limit for direct upload`
70-
)
71-
throw new Error(
72-
`File "${file.name}" (${sizeMB}MB) exceeds the 4MB limit for Teams attachments. Use smaller files or upload to SharePoint/OneDrive first.`
73-
)
74-
}
75-
76-
log.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
77-
78-
// Download file from storage
79-
const buffer = await downloadFileFromStorage(file, requestId, log)
80-
filesOutput.push({
81-
name: file.name,
82-
mimeType: file.type || 'application/octet-stream',
83-
data: buffer.toString('base64'),
84-
size: buffer.length,
85-
})
86-
87-
// Upload to OneDrive
88-
const uploadUrl =
89-
'https://graph.microsoft.com/v1.0/me/drive/root:/TeamsAttachments/' +
90-
encodeURIComponent(file.name) +
91-
':/content'
92-
93-
log.info(`[${requestId}] Uploading to OneDrive: ${uploadUrl}`)
94-
95-
const uploadResponse = await secureFetchWithValidation(
96-
uploadUrl,
97-
{
98-
method: 'PUT',
99-
headers: {
100-
Authorization: `Bearer ${accessToken}`,
101-
'Content-Type': file.type || 'application/octet-stream',
102-
},
103-
body: buffer,
104-
},
105-
'uploadUrl'
106-
)
107-
108-
if (!uploadResponse.ok) {
109-
const errorData = (await uploadResponse.json().catch(() => ({}))) as GraphApiErrorResponse
110-
log.error(`[${requestId}] Teams upload failed:`, errorData)
111-
throw new Error(
112-
`Failed to upload file to Teams: ${errorData.error?.message || 'Unknown error'}`
113-
)
114-
}
115-
116-
const uploadedFile = (await uploadResponse.json()) as GraphDriveItem
117-
log.info(`[${requestId}] File uploaded to OneDrive successfully`, {
118-
id: uploadedFile.id,
119-
webUrl: uploadedFile.webUrl,
120-
})
121-
122-
// Get file details for attachment reference
123-
const fileDetailsUrl = `https://graph.microsoft.com/v1.0/me/drive/items/${uploadedFile.id}?$select=id,name,webDavUrl,eTag,size`
124-
125-
const fileDetailsResponse = await secureFetchWithValidation(
126-
fileDetailsUrl,
127-
{
128-
method: 'GET',
129-
headers: {
130-
Authorization: `Bearer ${accessToken}`,
131-
},
132-
},
133-
'fileDetailsUrl'
134-
)
135-
136-
if (!fileDetailsResponse.ok) {
137-
const errorData = (await fileDetailsResponse
138-
.json()
139-
.catch(() => ({}))) as GraphApiErrorResponse
140-
log.error(`[${requestId}] Failed to get file details:`, errorData)
141-
throw new Error(`Failed to get file details: ${errorData.error?.message || 'Unknown error'}`)
142-
}
143-
144-
const fileDetails = (await fileDetailsResponse.json()) as GraphDriveItem
145-
log.info(`[${requestId}] Got file details`, {
146-
webDavUrl: fileDetails.webDavUrl,
147-
eTag: fileDetails.eTag,
148-
})
149-
150-
// Create attachment reference
151-
const attachmentId = fileDetails.eTag?.match(/\{([a-f0-9-]+)\}/i)?.[1] || fileDetails.id
152-
153-
attachments.push({
154-
id: attachmentId,
155-
contentType: 'reference',
156-
contentUrl: fileDetails.webDavUrl!,
157-
name: file.name,
158-
})
159-
160-
log.info(`[${requestId}] Created attachment reference for ${file.name}`)
161-
}
162-
163-
log.info(
164-
`[${requestId}] All ${attachments.length} file(s) uploaded and attachment references created`
165-
)
166-
167-
return { attachments, filesOutput }
168-
}
169-
1707
interface ParsedMention {
1718
name: string
1729
fullTag: string

0 commit comments

Comments
 (0)