메일 관리 작업 저장용 커밋

This commit is contained in:
leeheejin
2025-10-01 16:15:53 +09:00
parent 2a8841c6dc
commit 0209be8fd6
65 changed files with 8636 additions and 2145 deletions

View File

@@ -0,0 +1,201 @@
import { Request, Response } from 'express';
import { mailAccountFileService } from '../services/mailAccountFileService';
export class MailAccountFileController {
async getAllAccounts(req: Request, res: Response) {
try {
const accounts = await mailAccountFileService.getAllAccounts();
// 비밀번호는 반환하지 않음
const safeAccounts = accounts.map(({ smtpPassword, ...account }) => account);
return res.json({
success: true,
data: safeAccounts,
total: safeAccounts.length,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '계정 조회 실패',
error: err.message,
});
}
}
async getAccountById(req: Request, res: Response) {
try {
const { id } = req.params;
const account = await mailAccountFileService.getAccountById(id);
if (!account) {
return res.status(404).json({
success: false,
message: '계정을 찾을 수 없습니다.',
});
}
// 비밀번호는 마스킹 처리
const { smtpPassword, ...safeAccount } = account;
return res.json({
success: true,
data: {
...safeAccount,
smtpPassword: '••••••••', // 마스킹
},
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '계정 조회 실패',
error: err.message,
});
}
}
async createAccount(req: Request, res: Response) {
try {
const {
name,
email,
smtpHost,
smtpPort,
smtpSecure,
smtpUsername,
smtpPassword,
dailyLimit,
status,
} = req.body;
if (!name || !email || !smtpHost || !smtpPort || !smtpUsername || !smtpPassword) {
return res.status(400).json({
success: false,
message: '필수 필드가 누락되었습니다.',
});
}
// 이메일 중복 확인
const existingAccount = await mailAccountFileService.getAccountByEmail(email);
if (existingAccount) {
return res.status(400).json({
success: false,
message: '이미 등록된 이메일입니다.',
});
}
const account = await mailAccountFileService.createAccount({
name,
email,
smtpHost,
smtpPort,
smtpSecure: smtpSecure || false,
smtpUsername,
smtpPassword,
dailyLimit: dailyLimit || 1000,
status: status || 'active',
});
// 비밀번호 제외하고 반환
const { smtpPassword: _, ...safeAccount } = account;
return res.status(201).json({
success: true,
data: safeAccount,
message: '메일 계정이 생성되었습니다.',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '계정 생성 실패',
error: err.message,
});
}
}
async updateAccount(req: Request, res: Response) {
try {
const { id } = req.params;
const updates = req.body;
const account = await mailAccountFileService.updateAccount(id, updates);
if (!account) {
return res.status(404).json({
success: false,
message: '계정을 찾을 수 없습니다.',
});
}
// 비밀번호 제외하고 반환
const { smtpPassword: _, ...safeAccount } = account;
return res.json({
success: true,
data: safeAccount,
message: '계정이 수정되었습니다.',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '계정 수정 실패',
error: err.message,
});
}
}
async deleteAccount(req: Request, res: Response) {
try {
const { id } = req.params;
const success = await mailAccountFileService.deleteAccount(id);
if (!success) {
return res.status(404).json({
success: false,
message: '계정을 찾을 수 없습니다.',
});
}
return res.json({
success: true,
message: '계정이 삭제되었습니다.',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '계정 삭제 실패',
error: err.message,
});
}
}
async testConnection(req: Request, res: Response) {
try {
const { id } = req.params;
// TODO: 실제 SMTP 연결 테스트 구현
// const account = await mailAccountFileService.getAccountById(id);
// nodemailer로 연결 테스트
return res.json({
success: true,
message: '연결 테스트 성공 (미구현)',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '연결 테스트 실패',
error: err.message,
});
}
}
}
export const mailAccountFileController = new MailAccountFileController();

View File

@@ -0,0 +1,213 @@
import { Request, Response } from 'express';
import { mailQueryService, QueryParameter } from '../services/mailQueryService';
export class MailQueryController {
// 쿼리에서 파라미터 감지
async detectParameters(req: Request, res: Response) {
try {
const { sql } = req.body;
if (!sql) {
return res.status(400).json({
success: false,
message: 'SQL 쿼리가 필요합니다.',
});
}
const parameters = mailQueryService.detectParameters(sql);
return res.json({
success: true,
data: parameters,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '파라미터 감지 실패',
error: err.message,
});
}
}
// 쿼리 테스트 실행
async testQuery(req: Request, res: Response) {
try {
const { sql, parameters } = req.body;
if (!sql) {
return res.status(400).json({
success: false,
message: 'SQL 쿼리가 필요합니다.',
});
}
const result = await mailQueryService.testQuery(
sql,
parameters || []
);
return res.json({
success: result.success,
data: result,
message: result.success
? '쿼리 테스트 성공'
: '쿼리 테스트 실패',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '쿼리 테스트 실패',
error: err.message,
});
}
}
// 쿼리 실행
async executeQuery(req: Request, res: Response) {
try {
const { sql, parameters } = req.body;
if (!sql) {
return res.status(400).json({
success: false,
message: 'SQL 쿼리가 필요합니다.',
});
}
const result = await mailQueryService.executeQuery(
sql,
parameters || []
);
return res.json({
success: result.success,
data: result,
message: result.success
? '쿼리 실행 성공'
: '쿼리 실행 실패',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '쿼리 실행 실패',
error: err.message,
});
}
}
// 템플릿 변수 추출
async extractVariables(req: Request, res: Response) {
try {
const { template } = req.body;
if (!template) {
return res.status(400).json({
success: false,
message: '템플릿이 필요합니다.',
});
}
const variables = mailQueryService.extractVariables(template);
return res.json({
success: true,
data: variables,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '변수 추출 실패',
error: err.message,
});
}
}
// 변수 매핑 검증
async validateMapping(req: Request, res: Response) {
try {
const { templateVariables, queryFields } = req.body;
if (!templateVariables || !queryFields) {
return res.status(400).json({
success: false,
message: '템플릿 변수와 쿼리 필드가 필요합니다.',
});
}
const validation = mailQueryService.validateVariableMapping(
templateVariables,
queryFields
);
return res.json({
success: true,
data: validation,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '변수 매핑 검증 실패',
error: err.message,
});
}
}
// 대량 메일 데이터 처리
async processMailData(req: Request, res: Response) {
try {
const { templateHtml, templateSubject, sql, parameters } = req.body;
if (!templateHtml || !templateSubject || !sql) {
return res.status(400).json({
success: false,
message: '템플릿, 제목, SQL 쿼리가 모두 필요합니다.',
});
}
// 쿼리 실행
const queryResult = await mailQueryService.executeQuery(
sql,
parameters || []
);
if (!queryResult.success) {
return res.status(400).json({
success: false,
message: '쿼리 실행 실패',
error: queryResult.error,
});
}
// 메일 데이터 처리
const mailData = await mailQueryService.processMailData(
templateHtml,
templateSubject,
queryResult
);
return res.json({
success: true,
data: {
totalRecipients: mailData.length,
mailData: mailData.slice(0, 5), // 미리보기용 5개만
},
message: `${mailData.length}명의 수신자에게 발송 준비 완료`,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '메일 데이터 처리 실패',
error: err.message,
});
}
}
}
export const mailQueryController = new MailQueryController();

View File

@@ -0,0 +1,96 @@
import { Request, Response } from 'express';
import { mailSendSimpleService } from '../services/mailSendSimpleService';
export class MailSendSimpleController {
/**
* 메일 발송 (단건 또는 소규모)
*/
async sendMail(req: Request, res: Response) {
try {
const { accountId, templateId, to, subject, variables, customHtml } = req.body;
// 필수 파라미터 검증
if (!accountId || !to || !Array.isArray(to) || to.length === 0) {
return res.status(400).json({
success: false,
message: '계정 ID와 수신자 이메일이 필요합니다.',
});
}
if (!subject) {
return res.status(400).json({
success: false,
message: '메일 제목이 필요합니다.',
});
}
// 템플릿 또는 커스텀 HTML 중 하나는 있어야 함
if (!templateId && !customHtml) {
return res.status(400).json({
success: false,
message: '템플릿 또는 메일 내용이 필요합니다.',
});
}
// 메일 발송
const result = await mailSendSimpleService.sendMail({
accountId,
templateId,
to,
subject,
variables,
customHtml,
});
if (result.success) {
return res.json({
success: true,
data: result,
message: '메일이 발송되었습니다.',
});
} else {
return res.status(500).json({
success: false,
message: result.error || '메일 발송 실패',
});
}
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '메일 발송 중 오류가 발생했습니다.',
error: err.message,
});
}
}
/**
* SMTP 연결 테스트
*/
async testConnection(req: Request, res: Response) {
try {
const { accountId } = req.body;
if (!accountId) {
return res.status(400).json({
success: false,
message: '계정 ID가 필요합니다.',
});
}
const result = await mailSendSimpleService.testConnection(accountId);
return res.json(result);
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '연결 테스트 실패',
error: err.message,
});
}
}
}
export const mailSendSimpleController = new MailSendSimpleController();

View File

@@ -0,0 +1,258 @@
import { Request, Response } from 'express';
import { mailTemplateFileService } from '../services/mailTemplateFileService';
import { mailQueryService } from '../services/mailQueryService';
export class MailTemplateFileController {
// 모든 템플릿 조회
async getAllTemplates(req: Request, res: Response) {
try {
const { category, search } = req.query;
let templates;
if (search) {
templates = await mailTemplateFileService.searchTemplates(search as string);
} else if (category) {
templates = await mailTemplateFileService.getTemplatesByCategory(category as string);
} else {
templates = await mailTemplateFileService.getAllTemplates();
}
return res.json({
success: true,
data: templates,
total: templates.length,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '템플릿 조회 실패',
error: err.message,
});
}
}
// 특정 템플릿 조회
async getTemplateById(req: Request, res: Response) {
try {
const { id } = req.params;
const template = await mailTemplateFileService.getTemplateById(id);
if (!template) {
return res.status(404).json({
success: false,
message: '템플릿을 찾을 수 없습니다.',
});
}
return res.json({
success: true,
data: template,
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '템플릿 조회 실패',
error: err.message,
});
}
}
// 템플릿 생성
async createTemplate(req: Request, res: Response) {
try {
const { name, subject, components, queryConfig, recipientConfig, category } = req.body;
if (!name || !subject || !Array.isArray(components)) {
return res.status(400).json({
success: false,
message: '템플릿 이름, 제목, 컴포넌트가 필요합니다.',
});
}
const template = await mailTemplateFileService.createTemplate({
name,
subject,
components,
queryConfig,
recipientConfig,
category,
});
return res.status(201).json({
success: true,
data: template,
message: '템플릿이 생성되었습니다.',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '템플릿 생성 실패',
error: err.message,
});
}
}
// 템플릿 수정
async updateTemplate(req: Request, res: Response) {
try {
const { id } = req.params;
const updates = req.body;
const template = await mailTemplateFileService.updateTemplate(id, updates);
if (!template) {
return res.status(404).json({
success: false,
message: '템플릿을 찾을 수 없습니다.',
});
}
return res.json({
success: true,
data: template,
message: '템플릿이 수정되었습니다.',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '템플릿 수정 실패',
error: err.message,
});
}
}
// 템플릿 삭제
async deleteTemplate(req: Request, res: Response) {
try {
const { id } = req.params;
const success = await mailTemplateFileService.deleteTemplate(id);
if (!success) {
return res.status(404).json({
success: false,
message: '템플릿을 찾을 수 없습니다.',
});
}
return res.json({
success: true,
message: '템플릿이 삭제되었습니다.',
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '템플릿 삭제 실패',
error: err.message,
});
}
}
// 템플릿 미리보기 (HTML 렌더링)
async previewTemplate(req: Request, res: Response) {
try {
const { id } = req.params;
const { sampleData } = req.body;
const template = await mailTemplateFileService.getTemplateById(id);
if (!template) {
return res.status(404).json({
success: false,
message: '템플릿을 찾을 수 없습니다.',
});
}
// HTML 렌더링
let html = mailTemplateFileService.renderTemplateToHtml(template.components);
let subject = template.subject;
// 샘플 데이터가 있으면 변수 치환
if (sampleData) {
html = mailQueryService.replaceVariables(html, sampleData);
subject = mailQueryService.replaceVariables(subject, sampleData);
}
return res.json({
success: true,
data: {
subject,
html,
sampleData,
},
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '미리보기 생성 실패',
error: err.message,
});
}
}
// 템플릿 + 쿼리 통합 미리보기
async previewWithQuery(req: Request, res: Response) {
try {
const { id } = req.params;
const { queryId, parameters } = req.body;
const template = await mailTemplateFileService.getTemplateById(id);
if (!template) {
return res.status(404).json({
success: false,
message: '템플릿을 찾을 수 없습니다.',
});
}
// 쿼리 실행
const query = template.queryConfig?.queries.find(q => q.id === queryId);
if (!query) {
return res.status(404).json({
success: false,
message: '쿼리를 찾을 수 없습니다.',
});
}
const queryResult = await mailQueryService.executeQuery(query.sql, parameters || []);
if (!queryResult.success || !queryResult.data || queryResult.data.length === 0) {
return res.status(400).json({
success: false,
message: '쿼리 결과가 없습니다.',
error: queryResult.error,
});
}
// 첫 번째 행으로 미리보기
const sampleData = queryResult.data[0];
let html = mailTemplateFileService.renderTemplateToHtml(template.components);
let subject = template.subject;
html = mailQueryService.replaceVariables(html, sampleData);
subject = mailQueryService.replaceVariables(subject, sampleData);
return res.json({
success: true,
data: {
subject,
html,
sampleData,
totalRecipients: queryResult.data.length,
},
});
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
success: false,
message: '쿼리 미리보기 실패',
error: err.message,
});
}
}
}
export const mailTemplateFileController = new MailTemplateFileController();