Files
vexplor/backend-node/src/services/mailAccountFileService.ts
2025-10-16 10:33:21 +09:00

164 lines
4.1 KiB
TypeScript

import fs from "fs/promises";
import path from "path";
import { encryptionService } from "./encryptionService";
export interface MailAccount {
id: string;
name: string;
email: string;
smtpHost: string;
smtpPort: number;
smtpSecure: boolean;
smtpUsername: string;
smtpPassword: string; // 암호화된 비밀번호
dailyLimit: number;
status: "active" | "inactive" | "suspended";
createdAt: string;
updatedAt: string;
}
class MailAccountFileService {
private accountsDir: string;
constructor() {
// 운영 환경에서는 /app/uploads/mail-accounts, 개발 환경에서는 프로젝트 루트
this.accountsDir =
process.env.NODE_ENV === "production"
? "/app/uploads/mail-accounts"
: path.join(process.cwd(), "uploads", "mail-accounts");
this.ensureDirectoryExists();
}
private async ensureDirectoryExists() {
try {
await fs.access(this.accountsDir);
} catch {
await fs.mkdir(this.accountsDir, { recursive: true, mode: 0o755 });
}
}
private getAccountPath(id: string): string {
return path.join(this.accountsDir, `${id}.json`);
}
async getAllAccounts(): Promise<MailAccount[]> {
await this.ensureDirectoryExists();
try {
const files = await fs.readdir(this.accountsDir);
const jsonFiles = files.filter((f) => f.endsWith(".json"));
const accounts = await Promise.all(
jsonFiles.map(async (file) => {
const content = await fs.readFile(
path.join(this.accountsDir, file),
"utf-8"
);
return JSON.parse(content) as MailAccount;
})
);
return accounts.sort(
(a, b) =>
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
);
} catch {
return [];
}
}
async getAccountById(id: string): Promise<MailAccount | null> {
try {
const content = await fs.readFile(this.getAccountPath(id), "utf-8");
return JSON.parse(content);
} catch {
return null;
}
}
async createAccount(
data: Omit<MailAccount, "id" | "createdAt" | "updatedAt">
): Promise<MailAccount> {
const id = `account-${Date.now()}`;
const now = new Date().toISOString();
// 비밀번호 암호화
const encryptedPassword = encryptionService.encrypt(data.smtpPassword);
const account: MailAccount = {
...data,
id,
smtpPassword: encryptedPassword,
createdAt: now,
updatedAt: now,
};
await fs.writeFile(
this.getAccountPath(id),
JSON.stringify(account, null, 2),
"utf-8"
);
return account;
}
async updateAccount(
id: string,
data: Partial<Omit<MailAccount, "id" | "createdAt">>
): Promise<MailAccount | null> {
const existing = await this.getAccountById(id);
if (!existing) {
return null;
}
// 비밀번호가 변경되면 암호화
if (data.smtpPassword && data.smtpPassword !== existing.smtpPassword) {
data.smtpPassword = encryptionService.encrypt(data.smtpPassword);
}
const updated: MailAccount = {
...existing,
...data,
id: existing.id,
createdAt: existing.createdAt,
updatedAt: new Date().toISOString(),
};
await fs.writeFile(
this.getAccountPath(id),
JSON.stringify(updated, null, 2),
"utf-8"
);
return updated;
}
async deleteAccount(id: string): Promise<boolean> {
try {
await fs.unlink(this.getAccountPath(id));
return true;
} catch {
return false;
}
}
async getAccountByEmail(email: string): Promise<MailAccount | null> {
const accounts = await this.getAllAccounts();
return accounts.find((a) => a.email === email) || null;
}
async getActiveAccounts(): Promise<MailAccount[]> {
const accounts = await this.getAllAccounts();
return accounts.filter((a) => a.status === "active");
}
/**
* 비밀번호 복호화
*/
decryptPassword(encryptedPassword: string): string {
return encryptionService.decrypt(encryptedPassword);
}
}
export const mailAccountFileService = new MailAccountFileService();