fix: Rewrite JMAP attachment upload to use HTTP POST
- Use proper HTTP POST to upload blob to JMAP server
- Truemail JMAP requires /upload/{accountId}/ endpoint
- Simplified email creation with correct bodyStructure
- Better error logging for debugging
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import axios from 'axios';
|
||||
import { db } from '../../config/database.js';
|
||||
import { prilohy, registracie, ucastnici, kurzy, emailAccounts, userEmailAccounts } from '../../db/schema.js';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
@@ -152,6 +153,35 @@ V prípade otázok nás neváhajte kontaktovať.
|
||||
`.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload blob via HTTP POST to JMAP upload endpoint
|
||||
*/
|
||||
const uploadBlobToJmap = async (jmapConfig, fileBuffer, mimeType) => {
|
||||
const jmapServer = process.env.JMAP_SERVER || 'https://mail.truemail.sk/jmap/';
|
||||
// Truemail upload URL format
|
||||
const uploadUrl = `${jmapServer}upload/${jmapConfig.accountId}/`;
|
||||
|
||||
logger.info(`Nahrávam súbor na JMAP server: ${uploadUrl}`);
|
||||
|
||||
const response = await axios.post(uploadUrl, fileBuffer, {
|
||||
auth: {
|
||||
username: jmapConfig.username,
|
||||
password: jmapConfig.password,
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.data?.blobId) {
|
||||
logger.error('Upload nevrátil blobId', response.data);
|
||||
throw new Error('Nepodarilo sa nahrať prílohu na server');
|
||||
}
|
||||
|
||||
logger.info(`Súbor nahraný, blobId: ${response.data.blobId}`);
|
||||
return response.data.blobId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send email with HTML content and PDF attachment via JMAP
|
||||
*/
|
||||
@@ -166,11 +196,14 @@ const sendEmailWithAttachment = async (jmapConfig, to, subject, htmlBody, textBo
|
||||
throw new Error('Priečinok Odoslané nebol nájdený');
|
||||
}
|
||||
|
||||
// Read attachment file and encode to base64
|
||||
// Read attachment file
|
||||
const fileBuffer = await fs.readFile(attachment.path);
|
||||
const base64Content = fileBuffer.toString('base64');
|
||||
const mimeType = attachment.mimeType || 'application/pdf';
|
||||
|
||||
// Create email with attachment
|
||||
// Upload blob via HTTP POST (this is the correct JMAP way)
|
||||
const blobId = await uploadBlobToJmap(jmapConfig, fileBuffer, mimeType);
|
||||
|
||||
// Create email with uploaded blob
|
||||
const createResponse = await jmapRequest(jmapConfig, [
|
||||
[
|
||||
'Email/set',
|
||||
@@ -181,80 +214,6 @@ const sendEmailWithAttachment = async (jmapConfig, to, subject, htmlBody, textBo
|
||||
mailboxIds: {
|
||||
[sentMailbox.id]: true,
|
||||
},
|
||||
keywords: {
|
||||
$draft: true,
|
||||
},
|
||||
from: [{ email: jmapConfig.username }],
|
||||
to: [{ email: to }],
|
||||
subject: subject,
|
||||
htmlBody: [{ partId: 'html', type: 'text/html' }],
|
||||
textBody: [{ partId: 'text', type: 'text/plain' }],
|
||||
attachments: [
|
||||
{
|
||||
blobId: null, // Will be set via bodyValues
|
||||
type: attachment.mimeType || 'application/pdf',
|
||||
name: attachment.filename,
|
||||
disposition: 'attachment',
|
||||
},
|
||||
],
|
||||
bodyValues: {
|
||||
html: {
|
||||
value: htmlBody,
|
||||
},
|
||||
text: {
|
||||
value: textBody,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'set1',
|
||||
],
|
||||
]);
|
||||
|
||||
// Check if we need to upload blob first (for some JMAP servers)
|
||||
const createResult = createResponse.methodResponses[0][1];
|
||||
|
||||
if (createResult.notCreated?.draft) {
|
||||
// Try alternative approach - upload blob first, then create email
|
||||
logger.info('Skúšam alternatívny prístup s nahraním prílohy');
|
||||
|
||||
// Upload blob
|
||||
const uploadResponse = await jmapRequest(jmapConfig, [
|
||||
[
|
||||
'Blob/upload',
|
||||
{
|
||||
accountId: jmapConfig.accountId,
|
||||
create: {
|
||||
attachment1: {
|
||||
data: [{ 'data:asBase64': base64Content }],
|
||||
type: attachment.mimeType || 'application/pdf',
|
||||
},
|
||||
},
|
||||
},
|
||||
'blob1',
|
||||
],
|
||||
]);
|
||||
|
||||
const blobId = uploadResponse.methodResponses[0][1]?.created?.attachment1?.blobId;
|
||||
|
||||
if (!blobId) {
|
||||
// Try yet another approach - inline base64
|
||||
logger.info('Skúšam inline base64 prílohu');
|
||||
|
||||
const inlineResponse = await jmapRequest(jmapConfig, [
|
||||
[
|
||||
'Email/set',
|
||||
{
|
||||
accountId: jmapConfig.accountId,
|
||||
create: {
|
||||
draft: {
|
||||
mailboxIds: {
|
||||
[sentMailbox.id]: true,
|
||||
},
|
||||
keywords: {
|
||||
$draft: true,
|
||||
},
|
||||
from: [{ email: jmapConfig.username }],
|
||||
to: [{ email: to }],
|
||||
subject: subject,
|
||||
@@ -275,61 +234,13 @@ const sendEmailWithAttachment = async (jmapConfig, to, subject, htmlBody, textBo
|
||||
],
|
||||
},
|
||||
{
|
||||
type: attachment.mimeType || 'application/pdf',
|
||||
type: mimeType,
|
||||
name: attachment.filename,
|
||||
disposition: 'attachment',
|
||||
partId: 'attachment',
|
||||
},
|
||||
],
|
||||
},
|
||||
bodyValues: {
|
||||
html: { value: htmlBody },
|
||||
text: { value: textBody },
|
||||
attachment: { value: base64Content, isEncodingProblem: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'set2',
|
||||
],
|
||||
]);
|
||||
|
||||
const inlineResult = inlineResponse.methodResponses[0][1];
|
||||
const createdEmailId = inlineResult.created?.draft?.id;
|
||||
|
||||
if (!createdEmailId) {
|
||||
logger.error('Nepodarilo sa vytvoriť email s prílohou', inlineResult.notCreated?.draft);
|
||||
throw new Error('Nepodarilo sa vytvoriť email s prílohou');
|
||||
}
|
||||
|
||||
// Submit the email
|
||||
return await submitEmail(jmapConfig, createdEmailId);
|
||||
}
|
||||
|
||||
// Create email with uploaded blob
|
||||
const emailWithBlobResponse = await jmapRequest(jmapConfig, [
|
||||
[
|
||||
'Email/set',
|
||||
{
|
||||
accountId: jmapConfig.accountId,
|
||||
create: {
|
||||
draft: {
|
||||
mailboxIds: {
|
||||
[sentMailbox.id]: true,
|
||||
},
|
||||
from: [{ email: jmapConfig.username }],
|
||||
to: [{ email: to }],
|
||||
subject: subject,
|
||||
htmlBody: [{ partId: 'html', type: 'text/html' }],
|
||||
textBody: [{ partId: 'text', type: 'text/plain' }],
|
||||
attachments: [
|
||||
{
|
||||
blobId: blobId,
|
||||
type: attachment.mimeType || 'application/pdf',
|
||||
name: attachment.filename,
|
||||
disposition: 'attachment',
|
||||
},
|
||||
],
|
||||
},
|
||||
bodyValues: {
|
||||
html: { value: htmlBody },
|
||||
text: { value: textBody },
|
||||
@@ -337,23 +248,16 @@ const sendEmailWithAttachment = async (jmapConfig, to, subject, htmlBody, textBo
|
||||
},
|
||||
},
|
||||
},
|
||||
'set3',
|
||||
'set1',
|
||||
],
|
||||
]);
|
||||
|
||||
const createdEmailId = emailWithBlobResponse.methodResponses[0][1].created?.draft?.id;
|
||||
|
||||
if (!createdEmailId) {
|
||||
throw new Error('Nepodarilo sa vytvoriť email s prílohou');
|
||||
}
|
||||
|
||||
return await submitEmail(jmapConfig, createdEmailId);
|
||||
}
|
||||
|
||||
const createResult = createResponse.methodResponses[0][1];
|
||||
const createdEmailId = createResult.created?.draft?.id;
|
||||
|
||||
if (!createdEmailId) {
|
||||
throw new Error('Nepodarilo sa vytvoriť email');
|
||||
logger.error('Nepodarilo sa vytvoriť email s prílohou', createResult.notCreated?.draft);
|
||||
throw new Error('Nepodarilo sa vytvoriť email s prílohou');
|
||||
}
|
||||
|
||||
return await submitEmail(jmapConfig, createdEmailId);
|
||||
|
||||
Reference in New Issue
Block a user