204 lines
6 KiB
JavaScript
204 lines
6 KiB
JavaScript
/**
|
|
* 백엔드 공통 헬퍼 함수
|
|
* 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,
|
|
};
|