ERP-node/backend-node/src/services/mailSentHistoryService.ts

323 lines
8.2 KiB
TypeScript
Raw Normal View History

/**
* ( )
*/
2025-10-13 16:04:13 +09:00
import fs from "fs";
import path from "path";
import { v4 as uuidv4 } from "uuid";
import {
SentMailHistory,
SentMailListQuery,
SentMailListResponse,
AttachmentInfo,
2025-10-13 16:04:13 +09:00
} from "../types/mailSentHistory";
2025-10-13 16:04:13 +09:00
// 운영 환경에서는 /app/data/mail-sent, 개발 환경에서는 프로젝트 루트의 data/mail-sent 사용
const SENT_MAIL_DIR =
process.env.NODE_ENV === "production"
? "/app/data/mail-sent"
: path.join(process.cwd(), "data", "mail-sent");
class MailSentHistoryService {
constructor() {
2025-10-13 16:04:13 +09:00
// 디렉토리 생성 (없으면) - try-catch로 권한 에러 방지
try {
if (!fs.existsSync(SENT_MAIL_DIR)) {
fs.mkdirSync(SENT_MAIL_DIR, { recursive: true });
}
} catch (error) {
console.error("메일 발송 이력 디렉토리 생성 실패:", error);
// 디렉토리가 이미 존재하거나 권한이 없어도 서비스는 계속 실행
// 실제 파일 쓰기 시점에 에러 처리
}
}
/**
*
*/
2025-10-13 16:04:13 +09:00
async saveSentMail(
data: Omit<SentMailHistory, "id" | "sentAt">
): Promise<SentMailHistory> {
const history: SentMailHistory = {
id: uuidv4(),
sentAt: new Date().toISOString(),
...data,
};
2025-10-13 16:04:13 +09:00
try {
// 디렉토리가 없으면 다시 시도
if (!fs.existsSync(SENT_MAIL_DIR)) {
fs.mkdirSync(SENT_MAIL_DIR, { recursive: true });
}
const filePath = path.join(SENT_MAIL_DIR, `${history.id}.json`);
fs.writeFileSync(filePath, JSON.stringify(history, null, 2), "utf-8");
console.log("발송 이력 저장:", history.id);
} catch (error) {
console.error("발송 이력 저장 실패:", error);
// 파일 저장 실패해도 history 객체는 반환 (메일 발송은 성공했으므로)
}
return history;
}
/**
* (, )
*/
2025-10-13 16:04:13 +09:00
async getSentMailList(
query: SentMailListQuery
): Promise<SentMailListResponse> {
const {
page = 1,
limit = 20,
2025-10-13 16:04:13 +09:00
searchTerm = "",
status = "all",
accountId,
startDate,
endDate,
2025-10-13 16:04:13 +09:00
sortBy = "sentAt",
sortOrder = "desc",
} = query;
// 모든 발송 이력 파일 읽기
let allHistory: SentMailHistory[] = [];
2025-10-13 16:04:13 +09:00
try {
// 디렉토리가 없으면 빈 배열 반환
if (!fs.existsSync(SENT_MAIL_DIR)) {
console.warn("메일 발송 이력 디렉토리가 없습니다:", SENT_MAIL_DIR);
return {
items: [],
total: 0,
page,
limit,
totalPages: 0,
};
}
const files = fs
.readdirSync(SENT_MAIL_DIR)
.filter((f) => f.endsWith(".json"));
for (const file of files) {
try {
const filePath = path.join(SENT_MAIL_DIR, file);
const content = fs.readFileSync(filePath, "utf-8");
const history: SentMailHistory = JSON.parse(content);
allHistory.push(history);
} catch (error) {
console.error(`발송 이력 파일 읽기 실패: ${file}`, error);
}
}
2025-10-13 16:04:13 +09:00
} catch (error) {
console.error("메일 발송 이력 조회 실패:", error);
return {
items: [],
total: 0,
page,
limit,
totalPages: 0,
};
}
// 필터링
let filtered = allHistory;
// 상태 필터
2025-10-13 16:04:13 +09:00
if (status !== "all") {
filtered = filtered.filter((h) => h.status === status);
}
// 계정 필터
if (accountId) {
filtered = filtered.filter((h) => h.accountId === accountId);
}
// 날짜 필터
if (startDate) {
filtered = filtered.filter((h) => h.sentAt >= startDate);
}
if (endDate) {
filtered = filtered.filter((h) => h.sentAt <= endDate);
}
// 검색어 필터 (제목, 받는사람)
if (searchTerm) {
const term = searchTerm.toLowerCase();
filtered = filtered.filter(
(h) =>
h.subject.toLowerCase().includes(term) ||
h.to.some((email) => email.toLowerCase().includes(term)) ||
(h.cc && h.cc.some((email) => email.toLowerCase().includes(term)))
);
}
// 정렬
filtered.sort((a, b) => {
let aVal: any = a[sortBy];
let bVal: any = b[sortBy];
2025-10-13 16:04:13 +09:00
if (sortBy === "sentAt") {
aVal = new Date(aVal).getTime();
bVal = new Date(bVal).getTime();
} else {
2025-10-13 16:04:13 +09:00
aVal = aVal ? aVal.toLowerCase() : "";
bVal = bVal ? bVal.toLowerCase() : "";
}
2025-10-13 16:04:13 +09:00
if (sortOrder === "asc") {
return aVal > bVal ? 1 : -1;
} else {
return aVal < bVal ? 1 : -1;
}
});
// 페이징
const total = filtered.length;
const totalPages = Math.ceil(total / limit);
const start = (page - 1) * limit;
const end = start + limit;
const items = filtered.slice(start, end);
return {
items,
total,
page,
limit,
totalPages,
};
}
/**
*
*/
async getSentMailById(id: string): Promise<SentMailHistory | null> {
const filePath = path.join(SENT_MAIL_DIR, `${id}.json`);
if (!fs.existsSync(filePath)) {
return null;
}
try {
2025-10-13 16:04:13 +09:00
const content = fs.readFileSync(filePath, "utf-8");
return JSON.parse(content) as SentMailHistory;
} catch (error) {
2025-10-13 16:04:13 +09:00
console.error("발송 이력 읽기 실패:", error);
return null;
}
}
/**
*
*/
async deleteSentMail(id: string): Promise<boolean> {
const filePath = path.join(SENT_MAIL_DIR, `${id}.json`);
if (!fs.existsSync(filePath)) {
return false;
}
try {
fs.unlinkSync(filePath);
2025-10-13 16:04:13 +09:00
console.log("🗑️ 발송 이력 삭제:", id);
return true;
} catch (error) {
2025-10-13 16:04:13 +09:00
console.error("발송 이력 삭제 실패:", error);
return false;
}
}
/**
*
*/
async getStatistics(accountId?: string): Promise<{
totalSent: number;
successCount: number;
failedCount: number;
todayCount: number;
thisMonthCount: number;
successRate: number;
}> {
let allHistory: SentMailHistory[] = [];
2025-10-13 16:04:13 +09:00
try {
// 디렉토리가 없으면 빈 통계 반환
if (!fs.existsSync(SENT_MAIL_DIR)) {
return {
totalSent: 0,
successCount: 0,
failedCount: 0,
todayCount: 0,
thisMonthCount: 0,
successRate: 0,
};
}
const files = fs
.readdirSync(SENT_MAIL_DIR)
.filter((f) => f.endsWith(".json"));
for (const file of files) {
try {
const filePath = path.join(SENT_MAIL_DIR, file);
const content = fs.readFileSync(filePath, "utf-8");
const history: SentMailHistory = JSON.parse(content);
// 계정 필터
if (!accountId || history.accountId === accountId) {
allHistory.push(history);
}
} catch (error) {
console.error(`발송 이력 파일 읽기 실패: ${file}`, error);
}
}
2025-10-13 16:04:13 +09:00
} catch (error) {
console.error("통계 조회 실패:", error);
return {
totalSent: 0,
successCount: 0,
failedCount: 0,
todayCount: 0,
thisMonthCount: 0,
successRate: 0,
};
}
const now = new Date();
2025-10-13 16:04:13 +09:00
const todayStart = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate()
).toISOString();
const monthStart = new Date(
now.getFullYear(),
now.getMonth(),
1
).toISOString();
const totalSent = allHistory.length;
2025-10-13 16:04:13 +09:00
const successCount = allHistory.filter(
(h) => h.status === "success"
).length;
const failedCount = allHistory.filter((h) => h.status === "failed").length;
const todayCount = allHistory.filter((h) => h.sentAt >= todayStart).length;
2025-10-13 16:04:13 +09:00
const thisMonthCount = allHistory.filter(
(h) => h.sentAt >= monthStart
).length;
const successRate =
totalSent > 0 ? Math.round((successCount / totalSent) * 100) : 0;
return {
totalSent,
successCount,
failedCount,
todayCount,
thisMonthCount,
successRate,
};
}
}
export const mailSentHistoryService = new MailSentHistoryService();