fix types

This commit is contained in:
Vikhyath Mondreti
2026-02-02 17:13:23 -08:00
parent 5a0becf76f
commit 42767fc4f4
17 changed files with 224 additions and 41 deletions

View File

@@ -12,6 +12,33 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('GitHubLatestCommitAPI')
interface GitHubErrorResponse {
message?: string
}
interface GitHubCommitResponse {
sha: string
html_url: string
commit: {
message: string
author: { name: string; email: string; date: string }
committer: { name: string; email: string; date: string }
}
author?: { login: string; avatar_url: string; html_url: string }
committer?: { login: string; avatar_url: string; html_url: string }
stats?: { additions: number; deletions: number; total: number }
files?: Array<{
filename: string
status: string
additions: number
deletions: number
changes: number
patch?: string
raw_url?: string
blob_url?: string
}>
}
const GitHubLatestCommitSchema = z.object({
owner: z.string().min(1, 'Owner is required'),
repo: z.string().min(1, 'Repo is required'),
@@ -61,7 +88,7 @@ export async function POST(request: NextRequest) {
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
const errorData = (await response.json().catch(() => ({}))) as GitHubErrorResponse
logger.error(`[${requestId}] GitHub API error`, {
status: response.status,
error: errorData,
@@ -72,7 +99,7 @@ export async function POST(request: NextRequest) {
)
}
const data = await response.json()
const data = (await response.json()) as GitHubCommitResponse
const content = `Latest commit: "${data.commit.message}" by ${data.commit.author.name} on ${data.commit.author.date}. SHA: ${data.sha}`

View File

@@ -19,6 +19,21 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('GoogleDriveDownloadAPI')
/** Google API error response structure */
interface GoogleApiErrorResponse {
error?: {
message?: string
code?: number
status?: string
}
}
/** Google Drive revisions list response */
interface GoogleDriveRevisionsResponse {
revisions?: GoogleDriveRevision[]
nextPageToken?: string
}
const GoogleDriveDownloadSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
fileId: z.string().min(1, 'File ID is required'),
@@ -76,7 +91,9 @@ export async function POST(request: NextRequest) {
)
if (!metadataResponse.ok) {
const errorDetails = await metadataResponse.json().catch(() => ({}))
const errorDetails = (await metadataResponse
.json()
.catch(() => ({}))) as GoogleApiErrorResponse
logger.error(`[${requestId}] Failed to get file metadata`, {
status: metadataResponse.status,
error: errorDetails,
@@ -87,7 +104,7 @@ export async function POST(request: NextRequest) {
)
}
const metadata: GoogleDriveFile = await metadataResponse.json()
const metadata = (await metadataResponse.json()) as GoogleDriveFile
const fileMimeType = metadata.mimeType
let fileBuffer: Buffer
@@ -119,7 +136,9 @@ export async function POST(request: NextRequest) {
)
if (!exportResponse.ok) {
const exportError = await exportResponse.json().catch(() => ({}))
const exportError = (await exportResponse
.json()
.catch(() => ({}))) as GoogleApiErrorResponse
logger.error(`[${requestId}] Failed to export file`, {
status: exportResponse.status,
error: exportError,
@@ -154,7 +173,9 @@ export async function POST(request: NextRequest) {
)
if (!downloadResponse.ok) {
const downloadError = await downloadResponse.json().catch(() => ({}))
const downloadError = (await downloadResponse
.json()
.catch(() => ({}))) as GoogleApiErrorResponse
logger.error(`[${requestId}] Failed to download file`, {
status: downloadResponse.status,
error: downloadError,
@@ -182,8 +203,8 @@ export async function POST(request: NextRequest) {
)
if (revisionsResponse.ok) {
const revisionsData = await revisionsResponse.json()
metadata.revisions = revisionsData.revisions as GoogleDriveRevision[]
const revisionsData = (await revisionsResponse.json()) as GoogleDriveRevisionsResponse
metadata.revisions = revisionsData.revisions
logger.info(`[${requestId}] Fetched file revisions`, {
fileId,
revisionCount: metadata.revisions?.length || 0,

View File

@@ -68,14 +68,14 @@ export async function GET(request: NextRequest) {
const contentType = imageResponse.headers.get('content-type') || 'image/jpeg'
const imageBlob = await imageResponse.blob()
const imageArrayBuffer = await imageResponse.arrayBuffer()
if (imageBlob.size === 0) {
logger.error(`[${requestId}] Empty image blob received`)
if (imageArrayBuffer.byteLength === 0) {
logger.error(`[${requestId}] Empty image received`)
return new NextResponse('Empty image received', { status: 404 })
}
return new NextResponse(imageBlob, {
return new NextResponse(imageArrayBuffer, {
headers: {
'Content-Type': contentType,
'Access-Control-Allow-Origin': '*',

View File

@@ -10,6 +10,24 @@ import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic'
/** Microsoft Graph API error response structure */
interface GraphApiError {
error?: {
code?: string
message?: string
}
}
/** Microsoft Graph API drive item metadata response */
interface DriveItemMetadata {
id?: string
name?: string
folder?: Record<string, unknown>
file?: {
mimeType?: string
}
}
const logger = createLogger('OneDriveDownloadAPI')
const OneDriveDownloadSchema = z.object({
@@ -61,7 +79,7 @@ export async function POST(request: NextRequest) {
)
if (!metadataResponse.ok) {
const errorDetails = await metadataResponse.json().catch(() => ({}))
const errorDetails = (await metadataResponse.json().catch(() => ({}))) as GraphApiError
logger.error(`[${requestId}] Failed to get file metadata`, {
status: metadataResponse.status,
error: errorDetails,
@@ -72,7 +90,7 @@ export async function POST(request: NextRequest) {
)
}
const metadata = await metadataResponse.json()
const metadata = (await metadataResponse.json()) as DriveItemMetadata
if (metadata.folder && !metadata.file) {
logger.error(`[${requestId}] Attempted to download a folder`, {
@@ -110,7 +128,7 @@ export async function POST(request: NextRequest) {
)
if (!downloadResponse.ok) {
const downloadError = await downloadResponse.json().catch(() => ({}))
const downloadError = (await downloadResponse.json().catch(() => ({}))) as GraphApiError
logger.error(`[${requestId}] Failed to download file`, {
status: downloadResponse.status,
error: downloadError,

View File

@@ -5,7 +5,6 @@ import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import {
secureFetchWithPinnedIP,
validateMicrosoftGraphId,
validateUrlWithDNS,
} from '@/lib/core/security/input-validation.server'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -41,6 +40,48 @@ const OneDriveUploadSchema = z.object({
conflictBehavior: z.enum(['fail', 'replace', 'rename']).optional().nullable(),
})
/** Microsoft Graph DriveItem response */
interface OneDriveFileData {
id: string
name: string
size: number
webUrl: string
createdDateTime: string
lastModifiedDateTime: string
file?: { mimeType: string }
parentReference?: { id: string; path: string }
'@microsoft.graph.downloadUrl'?: string
}
/** Microsoft Graph Excel range response */
interface ExcelRangeData {
address?: string
addressLocal?: string
values?: unknown[][]
}
/** Validates Microsoft Graph item IDs (alphanumeric with some special chars) */
function validateMicrosoftGraphId(
id: string,
paramName: string
): { isValid: boolean; error?: string } {
// Microsoft Graph IDs are typically alphanumeric, may include hyphens and exclamation marks
const validIdPattern = /^[a-zA-Z0-9!-]+$/
if (!validIdPattern.test(id)) {
return {
isValid: false,
error: `Invalid ${paramName}: contains invalid characters`,
}
}
if (id.length > 256) {
return {
isValid: false,
error: `Invalid ${paramName}: exceeds maximum length`,
}
}
return { isValid: true }
}
async function secureFetchGraph(
url: string,
options: {
@@ -215,7 +256,7 @@ export async function POST(request: NextRequest) {
)
}
const fileData = await uploadResponse.json()
const fileData = (await uploadResponse.json()) as OneDriveFileData
let excelWriteResult: any | undefined
const shouldWriteExcelContent =
@@ -241,7 +282,7 @@ export async function POST(request: NextRequest) {
)
if (sessionResp.ok) {
const sessionData = await sessionResp.json()
const sessionData = (await sessionResp.json()) as { id?: string }
workbookSessionId = sessionData?.id
}
@@ -262,7 +303,7 @@ export async function POST(request: NextRequest) {
'listUrl'
)
if (listResp.ok) {
const listData = await listResp.json()
const listData = (await listResp.json()) as { value?: Array<{ name?: string }> }
const firstSheetName = listData?.value?.[0]?.name
if (firstSheetName) {
sheetName = firstSheetName
@@ -348,7 +389,7 @@ export async function POST(request: NextRequest) {
details: errorText,
}
} else {
const writeData = await excelWriteResponse.json()
const writeData = (await excelWriteResponse.json()) as ExcelRangeData
const addr = writeData.address || writeData.addressLocal
const v = writeData.values || []
excelWriteResult = {
@@ -356,7 +397,7 @@ export async function POST(request: NextRequest) {
updatedRange: addr,
updatedRows: Array.isArray(v) ? v.length : undefined,
updatedColumns: Array.isArray(v) && v[0] ? v[0].length : undefined,
updatedCells: Array.isArray(v) && v[0] ? v.length * (v[0] as any[]).length : undefined,
updatedCells: Array.isArray(v) && v[0] ? v.length * v[0].length : undefined,
}
}

View File

@@ -13,6 +13,18 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('PipedriveGetFilesAPI')
interface PipedriveFile {
id?: number
name?: string
url?: string
}
interface PipedriveApiResponse {
success: boolean
data?: PipedriveFile[]
error?: string
}
const PipedriveGetFilesSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
deal_id: z.string().optional().nullable(),
@@ -70,7 +82,7 @@ export async function POST(request: NextRequest) {
},
})
const data = await response.json()
const data = (await response.json()) as PipedriveApiResponse
if (!data.success) {
logger.error(`[${requestId}] Pipedrive API request failed`, { data })

View File

@@ -3,10 +3,9 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { validateAwsRegion, validateS3BucketName } from '@/lib/core/security/input-validation'
import {
secureFetchWithPinnedIP,
validateAwsRegion,
validateS3BucketName,
validateUrlWithDNS,
} from '@/lib/core/security/input-validation.server'
import { generateRequestId } from '@/lib/core/utils/request'

View File

@@ -13,6 +13,36 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('TwilioGetRecordingAPI')
interface TwilioRecordingResponse {
sid?: string
call_sid?: string
duration?: string
status?: string
channels?: number
source?: string
price?: string
price_unit?: string
uri?: string
error_code?: number
message?: string
error_message?: string
}
interface TwilioErrorResponse {
message?: string
}
interface TwilioTranscription {
transcription_text?: string
status?: string
price?: string
price_unit?: string
}
interface TwilioTranscriptionsResponse {
transcriptions?: TwilioTranscription[]
}
const TwilioGetRecordingSchema = z.object({
accountSid: z.string().min(1, 'Account SID is required'),
authToken: z.string().min(1, 'Auth token is required'),
@@ -67,7 +97,7 @@ export async function POST(request: NextRequest) {
})
if (!infoResponse.ok) {
const errorData = await infoResponse.json().catch(() => ({}))
const errorData = (await infoResponse.json().catch(() => ({}))) as TwilioErrorResponse
logger.error(`[${requestId}] Twilio API error`, {
status: infoResponse.status,
error: errorData,
@@ -78,7 +108,7 @@ export async function POST(request: NextRequest) {
)
}
const data = await infoResponse.json()
const data = (await infoResponse.json()) as TwilioRecordingResponse
if (data.error_code) {
return NextResponse.json({
@@ -126,7 +156,8 @@ export async function POST(request: NextRequest) {
)
if (transcriptionResponse.ok) {
const transcriptionData = await transcriptionResponse.json()
const transcriptionData =
(await transcriptionResponse.json()) as TwilioTranscriptionsResponse
if (transcriptionData.transcriptions && transcriptionData.transcriptions.length > 0) {
const transcription = transcriptionData.transcriptions[0]

View File

@@ -13,6 +13,40 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('ZoomGetRecordingsAPI')
interface ZoomRecordingFile {
id?: string
meeting_id?: string
recording_start?: string
recording_end?: string
file_type?: string
file_extension?: string
file_size?: number
play_url?: string
download_url?: string
status?: string
recording_type?: string
}
interface ZoomRecordingsResponse {
uuid?: string
id?: string | number
account_id?: string
host_id?: string
topic?: string
type?: number
start_time?: string
duration?: number
total_size?: number
recording_count?: number
share_url?: string
recording_files?: ZoomRecordingFile[]
}
interface ZoomErrorResponse {
message?: string
code?: number
}
const ZoomGetRecordingsSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
meetingId: z.string().min(1, 'Meeting ID is required'),
@@ -72,7 +106,7 @@ export async function POST(request: NextRequest) {
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
const errorData = (await response.json().catch(() => ({}))) as ZoomErrorResponse
logger.error(`[${requestId}] Zoom API error`, {
status: response.status,
error: errorData,
@@ -83,7 +117,7 @@ export async function POST(request: NextRequest) {
)
}
const data = await response.json()
const data = (await response.json()) as ZoomRecordingsResponse
const files: Array<{
name: string
mimeType: string
@@ -152,7 +186,7 @@ export async function POST(request: NextRequest) {
total_size: data.total_size,
recording_count: data.recording_count,
share_url: data.share_url,
recording_files: (data.recording_files || []).map((file: any) => ({
recording_files: (data.recording_files || []).map((file: ZoomRecordingFile) => ({
id: file.id,
meeting_id: file.meeting_id,
recording_start: file.recording_start,

View File

@@ -779,7 +779,7 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
reason: { type: 'string', description: 'Reason for moderation action' },
archived: { type: 'string', description: 'Archive status (true/false)' },
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
files: { type: 'file[]', description: 'Files to attach (UserFile array)' },
files: { type: 'array', description: 'Files to attach (UserFile array)' },
limit: { type: 'number', description: 'Message limit' },
autoArchiveDuration: { type: 'number', description: 'Thread auto-archive duration in minutes' },
channelType: { type: 'number', description: 'Discord channel type (0=text, 2=voice, etc.)' },

View File

@@ -924,10 +924,10 @@ const googleSlidesV2SubBlocks = (GoogleSlidesBlock.subBlocks || []).flatMap((sub
{
id: 'imageFileReference',
title: 'Image',
type: 'short-input',
type: 'short-input' as const,
canonicalParamId: 'imageFile',
placeholder: 'Reference image from previous blocks',
mode: 'advanced',
mode: 'advanced' as const,
required: true,
condition: { field: 'operation', value: 'add_image' },
},
@@ -950,9 +950,9 @@ export const GoogleSlidesV2Block: BlockConfig<GoogleSlidesResponse> = {
hideFromToolbar: false,
subBlocks: googleSlidesV2SubBlocks,
tools: {
...GoogleSlidesBlock.tools,
access: GoogleSlidesBlock.tools!.access,
config: {
...GoogleSlidesBlock.tools?.config,
tool: GoogleSlidesBlock.tools!.config!.tool,
params: (params) => {
const baseParams = GoogleSlidesBlock.tools?.config?.params
if (!baseParams) {

View File

@@ -1025,7 +1025,7 @@ Return ONLY the comment text - no explanations.`,
commentId: { type: 'string', description: 'Comment ID for update/delete operations' },
// Attachment operation inputs
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
files: { type: 'file[]', description: 'Files to attach (UserFile array)' },
files: { type: 'array', description: 'Files to attach (UserFile array)' },
attachmentId: { type: 'string', description: 'Attachment ID for delete operation' },
// Worklog operation inputs
timeSpentSeconds: {

View File

@@ -392,7 +392,7 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
body: { type: 'string', description: 'Email content' },
contentType: { type: 'string', description: 'Content type (Text or HTML)' },
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
attachments: { type: 'file[]', description: 'Files to attach (UserFile array)' },
attachments: { type: 'array', description: 'Files to attach (UserFile array)' },
// Forward operation inputs
messageId: { type: 'string', description: 'Message ID to forward' },
comment: { type: 'string', description: 'Optional comment for forwarding' },

View File

@@ -600,7 +600,7 @@ Return ONLY the HTML content.`,
mailTemplateId: { type: 'string', description: 'Template ID for sending mail' },
dynamicTemplateData: { type: 'json', description: 'Dynamic template data' },
attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' },
attachments: { type: 'file[]', description: 'Files to attach (UserFile array)' },
attachments: { type: 'array', description: 'Files to attach (UserFile array)' },
// Contact inputs
email: { type: 'string', description: 'Contact email' },
firstName: { type: 'string', description: 'Contact first name' },

View File

@@ -279,7 +279,7 @@ export const SftpBlock: BlockConfig<SftpUploadResult> = {
privateKey: { type: 'string', description: 'Private key for authentication' },
passphrase: { type: 'string', description: 'Passphrase for encrypted key' },
remotePath: { type: 'string', description: 'Remote path on the SFTP server' },
files: { type: 'file[]', description: 'Files to upload (UserFile array)' },
files: { type: 'array', description: 'Files to upload (UserFile array)' },
fileContent: { type: 'string', description: 'Direct content to upload' },
fileName: { type: 'string', description: 'File name for direct content' },
overwrite: { type: 'boolean', description: 'Overwrite existing files' },

View File

@@ -196,7 +196,7 @@ export const SmtpBlock: BlockConfig<SmtpSendMailResult> = {
cc: { type: 'string', description: 'CC recipients (comma-separated)' },
bcc: { type: 'string', description: 'BCC recipients (comma-separated)' },
replyTo: { type: 'string', description: 'Reply-to email address' },
attachments: { type: 'file[]', description: 'Files to attach (UserFile array)' },
attachments: { type: 'array', description: 'Files to attach (UserFile array)' },
},
outputs: {

View File

@@ -351,7 +351,7 @@ export const TelegramBlock: BlockConfig<TelegramResponse> = {
type: 'json',
description: 'Files to attach (UI upload)',
},
files: { type: 'file[]', description: 'Files to attach (UserFile array)' },
files: { type: 'array', description: 'Files to attach (UserFile array)' },
caption: { type: 'string', description: 'Caption for media' },
messageId: { type: 'string', description: 'Message ID to delete' },
},