160 lines
3.8 KiB
TypeScript
160 lines
3.8 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() {
|
||
|
|
this.accountsDir = 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 });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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();
|
||
|
|
|