메일관리 기능 구현 완료
This commit is contained in:
@@ -109,7 +109,7 @@ export class MailReceiveBasicService {
|
||||
tls: true,
|
||||
};
|
||||
|
||||
console.log(`📧 IMAP 연결 시도 - 호스트: ${imapConfig.host}, 포트: ${imapConfig.port}, 이메일: ${imapConfig.user}`);
|
||||
// console.log(`📧 IMAP 연결 시도 - 호스트: ${imapConfig.host}, 포트: ${imapConfig.port}, 이메일: ${imapConfig.user}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const imap = this.createImapConnection(imapConfig);
|
||||
@@ -117,26 +117,26 @@ export class MailReceiveBasicService {
|
||||
|
||||
// 30초 타임아웃 설정
|
||||
const timeout = setTimeout(() => {
|
||||
console.error('❌ IMAP 연결 타임아웃 (30초)');
|
||||
// console.error('❌ IMAP 연결 타임아웃 (30초)');
|
||||
imap.end();
|
||||
reject(new Error('IMAP 연결 타임아웃'));
|
||||
}, 30000);
|
||||
|
||||
imap.once('ready', () => {
|
||||
console.log('✅ IMAP 연결 성공! INBOX 열기 시도...');
|
||||
// console.log('✅ IMAP 연결 성공! INBOX 열기 시도...');
|
||||
clearTimeout(timeout);
|
||||
|
||||
imap.openBox('INBOX', true, (err: any, box: any) => {
|
||||
if (err) {
|
||||
console.error('❌ INBOX 열기 실패:', err);
|
||||
// console.error('❌ INBOX 열기 실패:', err);
|
||||
imap.end();
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
console.log(`📬 INBOX 열림 - 전체 메일 수: ${box.messages.total}`);
|
||||
// console.log(`📬 INBOX 열림 - 전체 메일 수: ${box.messages.total}`);
|
||||
const totalMessages = box.messages.total;
|
||||
if (totalMessages === 0) {
|
||||
console.log('📭 메일함이 비어있습니다');
|
||||
// console.log('📭 메일함이 비어있습니다');
|
||||
imap.end();
|
||||
return resolve([]);
|
||||
}
|
||||
@@ -145,19 +145,19 @@ export class MailReceiveBasicService {
|
||||
const start = Math.max(1, totalMessages - limit + 1);
|
||||
const end = totalMessages;
|
||||
|
||||
console.log(`📨 메일 가져오기 시작 - 범위: ${start}~${end}`);
|
||||
// console.log(`📨 메일 가져오기 시작 - 범위: ${start}~${end}`);
|
||||
const fetch = imap.seq.fetch(`${start}:${end}`, {
|
||||
bodies: ['HEADER', 'TEXT'],
|
||||
struct: true,
|
||||
});
|
||||
|
||||
console.log(`📦 fetch 객체 생성 완료`);
|
||||
// console.log(`📦 fetch 객체 생성 완료`);
|
||||
|
||||
let processedCount = 0;
|
||||
const totalToProcess = end - start + 1;
|
||||
|
||||
fetch.on('message', (msg: any, seqno: any) => {
|
||||
console.log(`📬 메일 #${seqno} 처리 시작`);
|
||||
// console.log(`📬 메일 #${seqno} 처리 시작`);
|
||||
let header: string = '';
|
||||
let body: string = '';
|
||||
let attributes: any = null;
|
||||
@@ -207,10 +207,10 @@ export class MailReceiveBasicService {
|
||||
};
|
||||
|
||||
mails.push(mail);
|
||||
console.log(`✓ 메일 #${seqno} 파싱 완료 (${mails.length}/${totalToProcess})`);
|
||||
// console.log(`✓ 메일 #${seqno} 파싱 완료 (${mails.length}/${totalToProcess})`);
|
||||
processedCount++;
|
||||
} catch (parseError) {
|
||||
console.error(`메일 #${seqno} 파싱 오류:`, parseError);
|
||||
// console.error(`메일 #${seqno} 파싱 오류:`, parseError);
|
||||
processedCount++;
|
||||
}
|
||||
}
|
||||
@@ -219,24 +219,24 @@ export class MailReceiveBasicService {
|
||||
});
|
||||
|
||||
fetch.once('error', (fetchErr: any) => {
|
||||
console.error('❌ 메일 fetch 에러:', fetchErr);
|
||||
// console.error('❌ 메일 fetch 에러:', fetchErr);
|
||||
imap.end();
|
||||
reject(fetchErr);
|
||||
});
|
||||
|
||||
fetch.once('end', () => {
|
||||
console.log(`📭 fetch 종료 - 처리 완료 대기 중... (현재: ${mails.length}개)`);
|
||||
// console.log(`📭 fetch 종료 - 처리 완료 대기 중... (현재: ${mails.length}개)`);
|
||||
|
||||
// 모든 메일 처리가 완료될 때까지 대기
|
||||
const checkComplete = setInterval(() => {
|
||||
console.log(`⏳ 대기 중 - 처리됨: ${processedCount}/${totalToProcess}, 메일: ${mails.length}개`);
|
||||
// console.log(`⏳ 대기 중 - 처리됨: ${processedCount}/${totalToProcess}, 메일: ${mails.length}개`);
|
||||
if (processedCount >= totalToProcess) {
|
||||
clearInterval(checkComplete);
|
||||
console.log(`✅ 메일 가져오기 완료 - 총 ${mails.length}개`);
|
||||
// console.log(`✅ 메일 가져오기 완료 - 총 ${mails.length}개`);
|
||||
imap.end();
|
||||
// 최신 메일이 위로 오도록 정렬
|
||||
mails.sort((a, b) => b.date.getTime() - a.date.getTime());
|
||||
console.log(`📤 메일 목록 반환: ${mails.length}개`);
|
||||
// console.log(`📤 메일 목록 반환: ${mails.length}개`);
|
||||
resolve(mails);
|
||||
}
|
||||
}, 100);
|
||||
@@ -244,7 +244,7 @@ export class MailReceiveBasicService {
|
||||
// 최대 10초 대기
|
||||
setTimeout(() => {
|
||||
clearInterval(checkComplete);
|
||||
console.log(`⚠️ 타임아웃 - 부분 반환: ${mails.length}/${totalToProcess}개`);
|
||||
// console.log(`⚠️ 타임아웃 - 부분 반환: ${mails.length}/${totalToProcess}개`);
|
||||
imap.end();
|
||||
mails.sort((a, b) => b.date.getTime() - a.date.getTime());
|
||||
resolve(mails);
|
||||
@@ -254,16 +254,16 @@ export class MailReceiveBasicService {
|
||||
});
|
||||
|
||||
imap.once('error', (imapErr: any) => {
|
||||
console.error('❌ IMAP 연결 에러:', imapErr.message || imapErr);
|
||||
// console.error('❌ IMAP 연결 에러:', imapErr.message || imapErr);
|
||||
clearTimeout(timeout);
|
||||
reject(imapErr);
|
||||
});
|
||||
|
||||
imap.once('end', () => {
|
||||
console.log('🔌 IMAP 연결 종료');
|
||||
// console.log('🔌 IMAP 연결 종료');
|
||||
});
|
||||
|
||||
console.log('🔗 IMAP.connect() 호출...');
|
||||
// console.log('🔗 IMAP.connect() 호출...');
|
||||
imap.connect();
|
||||
});
|
||||
}
|
||||
@@ -311,22 +311,36 @@ export class MailReceiveBasicService {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
console.log(`📬 INBOX 정보 - 전체 메일: ${box.messages.total}, 요청한 seqno: ${seqno}`);
|
||||
|
||||
if (seqno > box.messages.total || seqno < 1) {
|
||||
console.error(`❌ 유효하지 않은 seqno: ${seqno} (메일 총 개수: ${box.messages.total})`);
|
||||
imap.end();
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
const fetch = imap.seq.fetch(`${seqno}:${seqno}`, {
|
||||
bodies: '',
|
||||
struct: true,
|
||||
});
|
||||
|
||||
let mailDetail: MailDetail | null = null;
|
||||
let parsingComplete = false;
|
||||
|
||||
fetch.on('message', (msg: any, seqnum: any) => {
|
||||
console.log(`📨 메일 메시지 이벤트 발생 - seqnum: ${seqnum}`);
|
||||
|
||||
msg.on('body', (stream: any, info: any) => {
|
||||
console.log(`📝 메일 본문 스트림 시작 - which: ${info.which}`);
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk: any) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.once('end', async () => {
|
||||
console.log(`✅ 메일 본문 스트림 종료 - 버퍼 크기: ${buffer.length}`);
|
||||
try {
|
||||
const parsed = await simpleParser(buffer);
|
||||
console.log(`✅ 메일 파싱 완료 - 제목: ${parsed.subject}`);
|
||||
|
||||
const fromAddress = Array.isArray(parsed.from) ? parsed.from[0] : parsed.from;
|
||||
const toAddress = Array.isArray(parsed.to) ? parsed.to[0] : parsed.to;
|
||||
@@ -353,21 +367,48 @@ export class MailReceiveBasicService {
|
||||
size: att.size || 0,
|
||||
})),
|
||||
};
|
||||
parsingComplete = true;
|
||||
} catch (parseError) {
|
||||
console.error('메일 파싱 오류:', parseError);
|
||||
parsingComplete = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// msg 전체가 처리되었을 때 이벤트
|
||||
msg.once('end', () => {
|
||||
console.log(`📮 메일 메시지 처리 완료 - seqnum: ${seqnum}`);
|
||||
});
|
||||
});
|
||||
|
||||
fetch.once('error', (fetchErr: any) => {
|
||||
console.error(`❌ Fetch 에러:`, fetchErr);
|
||||
imap.end();
|
||||
reject(fetchErr);
|
||||
});
|
||||
|
||||
fetch.once('end', () => {
|
||||
imap.end();
|
||||
resolve(mailDetail);
|
||||
console.log(`🏁 Fetch 종료 - parsingComplete: ${parsingComplete}`);
|
||||
|
||||
// 비동기 파싱이 완료될 때까지 대기
|
||||
const waitForParsing = setInterval(() => {
|
||||
if (parsingComplete) {
|
||||
clearInterval(waitForParsing);
|
||||
console.log(`✅ 파싱 완료 대기 종료 - mailDetail이 ${mailDetail ? '존재함' : 'null'}`);
|
||||
imap.end();
|
||||
resolve(mailDetail);
|
||||
}
|
||||
}, 10); // 10ms마다 체크
|
||||
|
||||
// 타임아웃 설정 (10초)
|
||||
setTimeout(() => {
|
||||
if (!parsingComplete) {
|
||||
clearInterval(waitForParsing);
|
||||
console.error('❌ 파싱 타임아웃');
|
||||
imap.end();
|
||||
resolve(mailDetail); // 타임아웃 시에도 현재 상태 반환
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -492,6 +533,43 @@ export class MailReceiveBasicService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 오늘 수신한 메일 수 조회 (통계용)
|
||||
*/
|
||||
async getTodayReceivedCount(accountId?: string): Promise<number> {
|
||||
try {
|
||||
const accounts = accountId
|
||||
? [await mailAccountFileService.getAccountById(accountId)]
|
||||
: await mailAccountFileService.getAllAccounts();
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
let totalCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
if (!account) continue;
|
||||
|
||||
try {
|
||||
const mails = await this.fetchMailList(account.id, 100);
|
||||
const todayMails = mails.filter(mail => {
|
||||
const mailDate = new Date(mail.date);
|
||||
return mailDate >= today;
|
||||
});
|
||||
totalCount += todayMails.length;
|
||||
} catch (error) {
|
||||
// 개별 계정 오류는 무시하고 계속 진행
|
||||
console.error(`계정 ${account.id} 메일 조회 실패:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return totalCount;
|
||||
} catch (error) {
|
||||
console.error('오늘 수신 메일 수 조회 실패:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 첨부파일 다운로드
|
||||
*/
|
||||
@@ -533,19 +611,26 @@ export class MailReceiveBasicService {
|
||||
});
|
||||
|
||||
let attachmentResult: { filePath: string; filename: string; contentType: string } | null = null;
|
||||
let parsingComplete = false;
|
||||
|
||||
fetch.on('message', (msg: any, seqnum: any) => {
|
||||
console.log(`📎 메일 메시지 이벤트 발생 - seqnum: ${seqnum}`);
|
||||
|
||||
msg.on('body', (stream: any, info: any) => {
|
||||
console.log(`📎 메일 본문 스트림 시작`);
|
||||
let buffer = '';
|
||||
stream.on('data', (chunk: any) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
stream.once('end', async () => {
|
||||
console.log(`📎 메일 본문 스트림 종료 - 버퍼 크기: ${buffer.length}`);
|
||||
try {
|
||||
const parsed = await simpleParser(buffer);
|
||||
console.log(`📎 파싱 완료 - 첨부파일 개수: ${parsed.attachments?.length || 0}`);
|
||||
|
||||
if (parsed.attachments && parsed.attachments[attachmentIndex]) {
|
||||
const attachment = parsed.attachments[attachmentIndex];
|
||||
console.log(`📎 첨부파일 발견 (index ${attachmentIndex}): ${attachment.filename}`);
|
||||
|
||||
// 안전한 파일명 생성
|
||||
const safeFilename = this.sanitizeFilename(
|
||||
@@ -557,28 +642,51 @@ export class MailReceiveBasicService {
|
||||
|
||||
// 파일 저장
|
||||
await fs.writeFile(filePath, attachment.content);
|
||||
console.log(`📎 파일 저장 완료: ${filePath}`);
|
||||
|
||||
attachmentResult = {
|
||||
filePath,
|
||||
filename: attachment.filename || 'unnamed',
|
||||
contentType: attachment.contentType || 'application/octet-stream',
|
||||
};
|
||||
parsingComplete = true;
|
||||
} else {
|
||||
console.log(`❌ 첨부파일 index ${attachmentIndex}를 찾을 수 없음 (총 ${parsed.attachments?.length || 0}개)`);
|
||||
parsingComplete = true;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('첨부파일 파싱 오류:', parseError);
|
||||
parsingComplete = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
fetch.once('error', (fetchErr: any) => {
|
||||
console.error('❌ fetch 오류:', fetchErr);
|
||||
imap.end();
|
||||
reject(fetchErr);
|
||||
});
|
||||
|
||||
fetch.once('end', () => {
|
||||
imap.end();
|
||||
resolve(attachmentResult);
|
||||
console.log('📎 fetch.once("end") 호출됨 - 파싱 완료 대기 시작...');
|
||||
|
||||
// 파싱 완료를 기다림 (최대 5초)
|
||||
const checkComplete = setInterval(() => {
|
||||
if (parsingComplete) {
|
||||
console.log(`✅ 파싱 완료 확인 - attachmentResult: ${attachmentResult ? '있음' : '없음'}`);
|
||||
clearInterval(checkComplete);
|
||||
imap.end();
|
||||
resolve(attachmentResult);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(checkComplete);
|
||||
console.log(`⚠️ 타임아웃 - attachmentResult: ${attachmentResult ? '있음' : '없음'}`);
|
||||
imap.end();
|
||||
resolve(attachmentResult);
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import { mailSentHistoryService } from './mailSentHistoryService';
|
||||
export interface SendMailRequest {
|
||||
accountId: string;
|
||||
templateId?: string;
|
||||
modifiedTemplateComponents?: any[]; // 🎯 프론트엔드에서 수정된 템플릿 컴포넌트
|
||||
to: string[]; // 받는 사람
|
||||
cc?: string[]; // 참조 (Carbon Copy)
|
||||
bcc?: string[]; // 숨은참조 (Blind Carbon Copy)
|
||||
@@ -52,15 +53,29 @@ class MailSendSimpleService {
|
||||
throw new Error('비활성 상태의 계정입니다.');
|
||||
}
|
||||
|
||||
// 3. HTML 생성 (템플릿 또는 커스텀)
|
||||
htmlContent = request.customHtml || '';
|
||||
|
||||
if (!htmlContent && request.templateId) {
|
||||
// 3. HTML 생성 (템플릿 + 추가 메시지 병합)
|
||||
if (request.templateId) {
|
||||
// 템플릿 사용
|
||||
const template = await mailTemplateFileService.getTemplateById(request.templateId);
|
||||
if (!template) {
|
||||
throw new Error('템플릿을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 🎯 수정된 컴포넌트가 있으면 덮어쓰기
|
||||
if (request.modifiedTemplateComponents && request.modifiedTemplateComponents.length > 0) {
|
||||
console.log('✏️ 수정된 템플릿 컴포넌트 사용:', request.modifiedTemplateComponents.length);
|
||||
template.components = request.modifiedTemplateComponents;
|
||||
}
|
||||
|
||||
htmlContent = this.renderTemplate(template, request.variables);
|
||||
|
||||
// 템플릿 + 추가 메시지 병합
|
||||
if (request.customHtml && request.customHtml.trim()) {
|
||||
htmlContent = this.mergeTemplateAndCustomContent(htmlContent, request.customHtml);
|
||||
}
|
||||
} else {
|
||||
// 직접 작성
|
||||
htmlContent = request.customHtml || '';
|
||||
}
|
||||
|
||||
if (!htmlContent) {
|
||||
@@ -261,13 +276,25 @@ class MailSendSimpleService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 렌더링 (간단 버전)
|
||||
* 템플릿 렌더링 (일반 메일 양식)
|
||||
*/
|
||||
private renderTemplate(
|
||||
template: any,
|
||||
variables?: Record<string, string>
|
||||
): string {
|
||||
let html = '<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">';
|
||||
// 일반적인 메일 레이아웃 (전체 너비, 그림자 없음)
|
||||
let html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
|
||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #ffffff;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
`;
|
||||
|
||||
template.components.forEach((component: any) => {
|
||||
switch (component.type) {
|
||||
@@ -276,20 +303,23 @@ class MailSendSimpleService {
|
||||
if (variables) {
|
||||
content = this.replaceVariables(content, variables);
|
||||
}
|
||||
html += `<p style="margin: 16px 0; color: ${component.color || '#333'}; font-size: ${component.fontSize || '14px'};">${content}</p>`;
|
||||
// 텍스트는 왼쪽 정렬, 적절한 줄간격
|
||||
html += `<div style="margin: 0 0 20px 0; color: ${component.color || '#333'}; font-size: ${component.fontSize || '15px'}; line-height: 1.6; text-align: left;">${content}</div>`;
|
||||
break;
|
||||
case 'button':
|
||||
let buttonText = component.text || 'Button';
|
||||
if (variables) {
|
||||
buttonText = this.replaceVariables(buttonText, variables);
|
||||
}
|
||||
html += `<div style="text-align: center; margin: 24px 0;">
|
||||
<a href="${component.url || '#'}" style="display: inline-block; padding: 12px 24px; background-color: ${component.backgroundColor || '#007bff'}; color: ${component.textColor || '#fff'}; text-decoration: none; border-radius: 4px;">${buttonText}</a>
|
||||
// 버튼은 왼쪽 정렬 (text-align 제거)
|
||||
html += `<div style="margin: 30px 0; text-align: left;">
|
||||
<a href="${component.url || '#'}" style="display: inline-block; padding: 14px 28px; background-color: ${component.backgroundColor || '#007bff'}; color: ${component.textColor || '#fff'}; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 15px;">${buttonText}</a>
|
||||
</div>`;
|
||||
break;
|
||||
case 'image':
|
||||
html += `<div style="text-align: center; margin: 16px 0;">
|
||||
<img src="${component.src}" alt="${component.alt || ''}" style="max-width: 100%; height: auto;" />
|
||||
// 이미지는 왼쪽 정렬
|
||||
html += `<div style="margin: 20px 0; text-align: left;">
|
||||
<img src="${component.src}" alt="${component.alt || ''}" style="max-width: 100%; height: auto; display: block; border-radius: 4px;" />
|
||||
</div>`;
|
||||
break;
|
||||
case 'spacer':
|
||||
@@ -298,7 +328,13 @@ class MailSendSimpleService {
|
||||
}
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += `
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -320,6 +356,52 @@ class MailSendSimpleService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿과 추가 메시지 병합
|
||||
* 템플릿 HTML의 body 태그 끝 부분에 추가 메시지를 삽입
|
||||
*/
|
||||
private mergeTemplateAndCustomContent(templateHtml: string, customContent: string): string {
|
||||
// customContent에 HTML 태그가 없으면 기본 스타일 적용
|
||||
let formattedCustomContent = customContent;
|
||||
if (!customContent.includes('<')) {
|
||||
// 일반 텍스트인 경우 단락으로 변환
|
||||
const paragraphs = customContent
|
||||
.split('\n\n')
|
||||
.filter((p) => p.trim())
|
||||
.map((p) => `<p style="margin: 16px 0; line-height: 1.6;">${p.replace(/\n/g, '<br>')}</p>`)
|
||||
.join('');
|
||||
|
||||
formattedCustomContent = `
|
||||
<div style="margin-top: 32px; padding-top: 24px; border-top: 1px solid #e5e7eb;">
|
||||
${paragraphs}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// 이미 HTML인 경우 구분선만 추가
|
||||
formattedCustomContent = `
|
||||
<div style="margin-top: 32px; padding-top: 24px; border-top: 1px solid #e5e7eb;">
|
||||
${customContent}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// </body> 또는 </div> 태그 앞에 삽입
|
||||
if (templateHtml.includes('</body>')) {
|
||||
return templateHtml.replace('</body>', `${formattedCustomContent}</body>`);
|
||||
} else if (templateHtml.includes('</div>')) {
|
||||
// 마지막 </div> 앞에 삽입
|
||||
const lastDivIndex = templateHtml.lastIndexOf('</div>');
|
||||
return (
|
||||
templateHtml.substring(0, lastDivIndex) +
|
||||
formattedCustomContent +
|
||||
templateHtml.substring(lastDivIndex)
|
||||
);
|
||||
} else {
|
||||
// 태그가 없으면 단순 결합
|
||||
return templateHtml + formattedCustomContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SMTP 연결 테스트
|
||||
*/
|
||||
|
||||
@@ -141,26 +141,35 @@ class MailTemplateFileService {
|
||||
id: string,
|
||||
data: Partial<Omit<MailTemplate, 'id' | 'createdAt'>>
|
||||
): Promise<MailTemplate | null> {
|
||||
const existing = await this.getTemplateById(id);
|
||||
if (!existing) {
|
||||
return null;
|
||||
try {
|
||||
const existing = await this.getTemplateById(id);
|
||||
if (!existing) {
|
||||
// console.error(`❌ 템플릿을 찾을 수 없음: ${id}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const updated: MailTemplate = {
|
||||
...existing,
|
||||
...data,
|
||||
id: existing.id,
|
||||
createdAt: existing.createdAt,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// console.log(`📝 템플릿 저장 시도: ${id}, 크기: ${JSON.stringify(updated).length} bytes`);
|
||||
|
||||
await fs.writeFile(
|
||||
this.getTemplatePath(id),
|
||||
JSON.stringify(updated, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
// console.log(`✅ 템플릿 저장 성공: ${id}`);
|
||||
return updated;
|
||||
} catch (error) {
|
||||
// console.error(`❌ 템플릿 저장 실패: ${id}`, error);
|
||||
throw error; // 에러를 컨트롤러로 전달
|
||||
}
|
||||
|
||||
const updated: MailTemplate = {
|
||||
...existing,
|
||||
...data,
|
||||
id: existing.id,
|
||||
createdAt: existing.createdAt,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
this.getTemplatePath(id),
|
||||
JSON.stringify(updated, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user