mailbox/backend/utils/helpers.js

205 lines
6 KiB
JavaScript
Raw Permalink Normal View History

2025-12-16 08:18:15 +09:00
/**
* 백엔드 공통 헬퍼 함수
* mail.js, admin.js 등에서 공통으로 사용되는 유틸리티
*/
const Inbox = require("../models/Inbox");
const Sent = require("../models/Sent");
const Trash = require("../models/Trash");
const Spam = require("../models/Spam");
const Draft = require("../models/Draft");
const Important = require("../models/Important");
// 메일함 이름으로 Sequelize 모델 반환 맵
const MAILBOX_MODELS = {
INBOX: Inbox,
SENT: Sent,
TRASH: Trash,
SPAM: Spam,
DRAFTS: Draft,
IMPORTANT: Important,
};
/**
* 메일함 이름으로 Sequelize 모델 반환
* @param {string} mailboxName - 메일함 이름 (INBOX, SENT, TRASH )
* @returns {Model} Sequelize 모델
*/
const getModel = (mailboxName = "INBOX") => {
return MAILBOX_MODELS[mailboxName.toUpperCase()] || Inbox;
};
/**
* 이메일 데이터 정규화 (프론트엔드 호환성)
* @param {Object} email - 이메일 객체
* @param {string} mailboxName - 메일함 이름
* @returns {Object} 정규화된 이메일 데이터
*/
const normalizeEmail = (email, mailboxName) => {
if (!email) return null;
const data = email.toJSON ? email.toJSON() : email;
data.mailbox = mailboxName.toUpperCase();
// 보낸편지함 호환성: body 필드가 있고 html이 없으면 body를 html로 복사
if (data.body && !data.html) {
data.html = data.body;
}
return data;
};
/**
* 스토리지 사용량 계산 (본문 + 첨부파일 크기)
* mail.js와 admin.js에서 동일하게 사용
* @param {Array} items - 메일 항목 배열
* @returns {number} 바이트 크기
*/
const calculateStorageSize = (items) => {
let totalSize = 0;
items.forEach((item) => {
// 본문/제목 크기
if (item.subject) totalSize += Buffer.byteLength(item.subject, "utf8");
if (item.text) totalSize += Buffer.byteLength(item.text, "utf8");
if (item.html) totalSize += Buffer.byteLength(item.html, "utf8");
// 첨부파일 크기
let atts = item.attachments;
if (typeof atts === "string") {
try {
atts = JSON.parse(atts);
if (typeof atts === "string") atts = JSON.parse(atts);
} catch {
atts = [];
}
}
if (Array.isArray(atts)) {
atts.forEach((att) => (totalSize += att.size || 0));
}
});
return totalSize;
};
/**
* 첨부파일 배열 파싱 (JSON 문자열 처리)
* @param {Array|string} attachments - 첨부파일 데이터
* @returns {Array} 파싱된 첨부파일 배열
*/
const parseAttachments = (attachments) => {
if (!attachments) return [];
if (Array.isArray(attachments)) return attachments;
if (typeof attachments === "string") {
try {
let parsed = JSON.parse(attachments);
// 이중 JSON 문자열 처리
if (typeof parsed === "string") parsed = JSON.parse(parsed);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
return [];
};
/**
* 안전한 트랜잭션 롤백 헬퍼
* 연결이 끊어진 경우에도 서버 크래시 방지
* @param {Transaction} transaction - Sequelize 트랜잭션
*/
const safeRollback = async (transaction) => {
try {
await transaction.rollback();
} catch (rollbackError) {
console.error("롤백 오류 (무시됨):", rollbackError.message);
}
};
/**
* 이메일 문자열에서 순수 이메일 주소만 추출
* "Name" <email@domain.com> 또는 email@domain.com 형태 모두 지원
* @param {string} emailStr - 이메일 문자열
* @returns {string} 순수 이메일 주소
*/
const extractEmailAddress = (emailStr) => {
if (!emailStr) return "";
// <email> 형태에서 추출
const match = emailStr.match(/<([^>]+)>/);
if (match) return match[1].toLowerCase();
// 그냥 이메일만 있는 경우
return emailStr.trim().toLowerCase();
};
/**
* 이메일 조회 (소유권 검증 포함)
* @param {number} id - 이메일 ID
* @param {string} userEmail - 사용자 이메일
* @param {string} mailboxContext - 메일함 컨텍스트 (선택)
* @returns {Promise<Object|null>} { email, Model, box } 또는 null
*/
const findEmail = async (id, userEmail, mailboxContext) => {
const userEmailLower = userEmail.toLowerCase();
// 메일함이 명시된 경우 해당 모델에서 직접 조회
if (mailboxContext) {
const Model = getModel(mailboxContext);
const email = await Model.findByPk(id);
if (email) {
// from 필드에서 이메일 주소만 추출하여 비교
const fromEmail = extractEmailAddress(email.from);
const toContains =
email.to && email.to.toLowerCase().includes(userEmailLower);
const isOwner = fromEmail === userEmailLower || toContains;
if (isOwner) return { email, Model, box: mailboxContext };
}
return null;
}
// 폴백: 모든 메일함 순회 (mailboxContext 없이 호출 시)
const boxList = ["INBOX", "SENT", "TRASH", "SPAM", "DRAFTS", "IMPORTANT"];
for (const box of boxList) {
const Model = getModel(box);
const email = await Model.findByPk(id);
if (email) {
const fromEmail = extractEmailAddress(email.from);
const toContains =
email.to && email.to.toLowerCase().includes(userEmailLower);
const isOwner = fromEmail === userEmailLower || toContains;
if (isOwner) return { email, Model, box };
}
}
return null;
};
/**
* 기간 시작일 계산 (1d, 7d, 30d, all)
* @param {string} period - 기간 문자열
* @returns {Date|null} 시작일 또는 null (전체 기간)
*/
const getPeriodStartDate = (period) => {
const periodStart = new Date();
switch (period) {
case "1d":
periodStart.setDate(periodStart.getDate() - 1);
return periodStart;
case "7d":
periodStart.setDate(periodStart.getDate() - 7);
return periodStart;
case "30d":
periodStart.setDate(periodStart.getDate() - 30);
return periodStart;
default:
return null;
}
};
module.exports = {
getModel,
normalizeEmail,
calculateStorageSize,
parseAttachments,
safeRollback,
findEmail,
getPeriodStartDate,
extractEmailAddress,
MAILBOX_MODELS,
};