/** * 백엔드 공통 헬퍼 함수 * 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 형태 모두 지원 * @param {string} emailStr - 이메일 문자열 * @returns {string} 순수 이메일 주소 */ const extractEmailAddress = (emailStr) => { if (!emailStr) return ""; // 형태에서 추출 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} { 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, };