Skip to content

Commit bd6a2d0

Browse files
Enhance attachment handling and logging: improve error handling, enforce required partId, and add detailed logging for downloads
Fixes bug with 404 attachments Signed-off-by: Shahm Najeeb <Shahm_Najeeb@outlook.com>
1 parent 0d65c70 commit bd6a2d0

4 files changed

Lines changed: 64 additions & 30 deletions

File tree

app/api/accounts/[accountId]/emails/[uid]/attachments/[partId]/route.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ export const runtime = 'nodejs'
1717
export const dynamic = 'force-dynamic'
1818
export const revalidate = 0
1919

20-
export async function GET(
21-
request: NextRequest,
22-
{params}: { params: Promise<{ accountId: string; uid: string; partId: string }> }
23-
) {
20+
export async function GET(request: NextRequest, {params}: {
21+
params: Promise<{ accountId: string; uid: string; partId: string }>
22+
}) {
2423
try {
2524
// Check authentication
2625
const isAuthenticated = await requireAuth()
@@ -33,29 +32,32 @@ export async function GET(
3332

3433
const {accountId, uid, partId} = await params
3534

36-
// Rate limiting
37-
const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "unknown"
38-
const rateLimit = checkRateLimit(`attachment:${ip}`, RateLimitSettings.attachmentsLimit, RateLimitSettings.windowMs)
39-
40-
if (!rateLimit.allowed) {
41-
return NextResponse.json(
42-
{error: "Too many requests"},
43-
{status: 429}
44-
)
45-
}
46-
4735
// Parse query params
4836
const searchParams = request.nextUrl.searchParams
4937
const folder = searchParams.get("folder")
5038
const filename = searchParams.get("filename") || "attachment"
5139

40+
console.log(`[API] Attachment request - accountId: ${accountId}, uid: ${uid}, partId: ${partId}, folder: ${folder}, filename: ${filename}`)
41+
5242
if (!folder) {
43+
console.error(`[API] Missing folder parameter`)
5344
return NextResponse.json(
5445
{error: "Folder parameter required"},
5546
{status: 400}
5647
)
5748
}
5849

50+
// Rate limiting
51+
const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "unknown"
52+
const rateLimit = checkRateLimit(`attachment:${ip}`, RateLimitSettings.attachmentsLimit, RateLimitSettings.windowMs)
53+
54+
if (!rateLimit.allowed) {
55+
return NextResponse.json(
56+
{error: "Too many requests"},
57+
{status: 429}
58+
)
59+
}
60+
5961
const uidNum = Number.parseInt(uid, 10)
6062
if (isNaN(uidNum)) {
6163
return NextResponse.json(

components/email-viewer.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,26 +107,43 @@ export function EmailViewer({accountId, folder, uid}: EmailViewerProps) {
107107

108108
async function downloadAttachment(att: EmailAttachment) {
109109
try {
110-
const response = await fetch(
111-
`/api/accounts/${accountId}/emails/${uid}/attachments/${att.partId}?folder=${encodeURIComponent(folder)}&filename=${encodeURIComponent(att.filename || 'attachment')}`
112-
)
110+
const url = `/api/accounts/${accountId}/emails/${uid}/attachments/${att.partId}?folder=${encodeURIComponent(folder)}&filename=${encodeURIComponent(att.filename || 'attachment')}`
111+
console.log(`[EmailViewer] Downloading attachment:`, {
112+
partId: att.partId,
113+
filename: att.filename,
114+
url: url,
115+
accountId,
116+
uid,
117+
folder
118+
})
119+
120+
const response = await fetch(url)
113121

114122
if (!response.ok) {
115-
console.error("Failed to download attachment")
123+
const errorText = await response.text()
124+
console.error("Failed to download attachment", {
125+
status: response.status,
126+
statusText: response.statusText,
127+
url: url,
128+
errorBody: errorText
129+
})
130+
alert(`Failed to download attachment: ${response.status} ${response.statusText}\n${errorText}`)
116131
return
117132
}
118133

119134
const blob = await response.blob()
120-
const url = window.URL.createObjectURL(blob)
135+
const downloadUrl = window.URL.createObjectURL(blob)
121136
const a = document.createElement('a')
122-
a.href = url
137+
a.href = downloadUrl
123138
a.download = att.filename || 'attachment'
124139
document.body.appendChild(a)
125140
a.click()
126-
window.URL.revokeObjectURL(url)
141+
window.URL.revokeObjectURL(downloadUrl)
127142
document.body.removeChild(a)
143+
console.log(`[EmailViewer] Successfully downloaded: ${att.filename}`)
128144
} catch (error) {
129145
console.error("Error downloading attachment:", error)
146+
alert(`Error downloading attachment: ${error instanceof Error ? error.message : 'Unknown error'}`)
130147
}
131148
}
132149

lib/imap-service.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,13 +385,17 @@ export async function getEmail(params: GetEmailParams): Promise<EmailDetail | nu
385385
html: sanitizedHtml,
386386
text: parsed.text,
387387
attachments:
388-
parsed.attachments?.map((att: any, index: number) => ({
389-
filename: att.filename,
390-
contentType: att.contentType,
391-
size: att.size,
392-
contentId: att.contentId,
393-
partId: att.partId || `${index + 1}`,
394-
})) || [],
388+
parsed.attachments?.map((att: any, index: number) => {
389+
const partId = `${index + 1}`
390+
console.log(`[IMAP] Mapping attachment ${index}: ${att.filename}, partId: ${partId}`)
391+
return {
392+
filename: att.filename,
393+
contentType: att.contentType,
394+
size: att.size,
395+
contentId: att.contentId,
396+
partId: partId,
397+
}
398+
}) || [],
395399
}
396400

397401
resolve(emailData)
@@ -485,6 +489,17 @@ export async function getAttachment(params: GetAttachmentParams): Promise<{
485489
const attachments = parsed.attachments || []
486490
const index = parseInt(partId, 10) - 1
487491

492+
console.log(`[IMAP] Looking for attachment with partId ${partId} (index ${index}), total attachments: ${attachments.length}`)
493+
494+
if (attachments.length > 0) {
495+
console.log(`[IMAP] Available attachments:`, attachments.map((a: any, i: number) => ({
496+
index: i,
497+
filename: a.filename,
498+
size: a.size,
499+
contentType: a.contentType
500+
})))
501+
}
502+
488503
if (index >= 0 && index < attachments.length) {
489504
const attachment = attachments[index]
490505

types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface EmailAttachment {
2929
contentType?: string
3030
size?: number
3131
contentId?: string
32-
partId?: string
32+
partId: string // Required for downloading attachments
3333
}
3434

3535
export interface EmailFolder {

0 commit comments

Comments
 (0)