|
1 | | -import type { Logger } from '@sim/logger' |
2 | 1 | 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' |
12 | 3 | import type { ToolFileData } from '@/tools/types' |
13 | 4 |
|
14 | 5 | const logger = createLogger('MicrosoftTeamsUtils') |
15 | 6 |
|
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 | | - |
170 | 7 | interface ParsedMention { |
171 | 8 | name: string |
172 | 9 | fullTag: string |
|
0 commit comments