Merge branch 'lhj'

This commit is contained in:
leeheejin
2025-10-13 15:19:59 +09:00
57 changed files with 4804 additions and 4106 deletions

View File

@@ -12,12 +12,12 @@ const getApiBaseUrl = (): string => {
const currentHost = window.location.hostname;
const currentPort = window.location.port;
// 로컬 개발환경: localhost:9771 또는 localhost:3000 → localhost:8080
// 🎯 로컬 개발환경: Next.js 프록시 사용 (대용량 요청 안정성)
if (
(currentHost === "localhost" || currentHost === "127.0.0.1") &&
(currentPort === "9771" || currentPort === "3000")
) {
return "http://localhost:8080/api";
return "/api"; // 프록시 사용
}
}

View File

@@ -70,12 +70,76 @@ export interface UpdateMailTemplateDto extends Partial<CreateMailTemplateDto> {}
export interface SendMailDto {
accountId: string;
templateId?: string;
to: string[]; // 수신자 이메일 배열
to: string[]; // 받는 사람
cc?: string[]; // 참조 (Carbon Copy)
bcc?: string[]; // 숨은참조 (Blind Carbon Copy)
subject: string;
variables?: Record<string, string>; // 템플릿 변수 치환
customHtml?: string; // 템플릿 없이 직접 HTML 작성 시
}
// ============================================
// 발송 이력 타입
// ============================================
export interface AttachmentInfo {
filename: string;
originalName: string;
size: number;
path: string;
mimetype: string;
}
export interface SentMailHistory {
id: string;
accountId: string;
accountName: string;
accountEmail: string;
to: string[];
cc?: string[];
bcc?: string[];
subject: string;
htmlContent: string;
templateId?: string;
templateName?: string;
attachments?: AttachmentInfo[];
sentAt: string;
status: 'success' | 'failed';
messageId?: string;
errorMessage?: string;
accepted?: string[];
rejected?: string[];
}
export interface SentMailListQuery {
page?: number;
limit?: number;
searchTerm?: string;
status?: 'success' | 'failed' | 'all';
accountId?: string;
startDate?: string;
endDate?: string;
sortBy?: 'sentAt' | 'subject';
sortOrder?: 'asc' | 'desc';
}
export interface SentMailListResponse {
items: SentMailHistory[];
total: number;
page: number;
limit: number;
totalPages: number;
}
export interface MailStatistics {
totalSent: number;
successCount: number;
failedCount: number;
todayCount: number;
thisMonthCount: number;
successRate: number;
}
export interface MailSendResult {
success: boolean;
messageId?: string;
@@ -96,7 +160,7 @@ async function fetchApi<T>(
try {
const response = await apiClient({
url: `/mail${endpoint}`,
url: endpoint, // `/mail` 접두사 제거 (apiClient는 이미 /api를 포함)
method,
data,
});
@@ -124,14 +188,14 @@ async function fetchApi<T>(
* 전체 메일 계정 목록 조회
*/
export async function getMailAccounts(): Promise<MailAccount[]> {
return fetchApi<MailAccount[]>('/accounts');
return fetchApi<MailAccount[]>('/mail/accounts');
}
/**
* 특정 메일 계정 조회
*/
export async function getMailAccount(id: string): Promise<MailAccount> {
return fetchApi<MailAccount>(`/accounts/${id}`);
return fetchApi<MailAccount>(`/mail/accounts/${id}`);
}
/**
@@ -140,7 +204,7 @@ export async function getMailAccount(id: string): Promise<MailAccount> {
export async function createMailAccount(
data: CreateMailAccountDto
): Promise<MailAccount> {
return fetchApi<MailAccount>('/accounts', {
return fetchApi<MailAccount>('/mail/accounts', {
method: 'POST',
data,
});
@@ -153,7 +217,7 @@ export async function updateMailAccount(
id: string,
data: UpdateMailAccountDto
): Promise<MailAccount> {
return fetchApi<MailAccount>(`/accounts/${id}`, {
return fetchApi<MailAccount>(`/mail/accounts/${id}`, {
method: 'PUT',
data,
});
@@ -163,7 +227,7 @@ export async function updateMailAccount(
* 메일 계정 삭제
*/
export async function deleteMailAccount(id: string): Promise<{ success: boolean }> {
return fetchApi<{ success: boolean }>(`/accounts/${id}`, {
return fetchApi<{ success: boolean }>(`/mail/accounts/${id}`, {
method: 'DELETE',
});
}
@@ -172,7 +236,7 @@ export async function deleteMailAccount(id: string): Promise<{ success: boolean
* SMTP 연결 테스트
*/
export async function testMailAccountConnection(id: string): Promise<{ success: boolean; message: string }> {
return fetchApi<{ success: boolean; message: string }>(`/accounts/${id}/test-connection`, {
return fetchApi<{ success: boolean; message: string }>(`/mail/accounts/${id}/test-connection`, {
method: 'POST',
});
}
@@ -185,7 +249,7 @@ export async function testMailConnection(id: string): Promise<{
message: string;
}> {
return fetchApi<{ success: boolean; message: string }>(
`/accounts/${id}/test-connection`,
`/mail/accounts/${id}/test-connection`,
{
method: 'POST',
}
@@ -200,14 +264,14 @@ export async function testMailConnection(id: string): Promise<{
* 전체 메일 템플릿 목록 조회
*/
export async function getMailTemplates(): Promise<MailTemplate[]> {
return fetchApi<MailTemplate[]>('/templates-file');
return fetchApi<MailTemplate[]>('/mail/templates-file');
}
/**
* 특정 메일 템플릿 조회
*/
export async function getMailTemplate(id: string): Promise<MailTemplate> {
return fetchApi<MailTemplate>(`/templates-file/${id}`);
return fetchApi<MailTemplate>(`/mail/templates-file/${id}`);
}
/**
@@ -216,7 +280,7 @@ export async function getMailTemplate(id: string): Promise<MailTemplate> {
export async function createMailTemplate(
data: CreateMailTemplateDto
): Promise<MailTemplate> {
return fetchApi<MailTemplate>('/templates-file', {
return fetchApi<MailTemplate>('/mail/templates-file', {
method: 'POST',
data,
});
@@ -229,7 +293,7 @@ export async function updateMailTemplate(
id: string,
data: UpdateMailTemplateDto
): Promise<MailTemplate> {
return fetchApi<MailTemplate>(`/templates-file/${id}`, {
return fetchApi<MailTemplate>(`/mail/templates-file/${id}`, {
method: 'PUT',
data,
});
@@ -239,7 +303,7 @@ export async function updateMailTemplate(
* 메일 템플릿 삭제
*/
export async function deleteMailTemplate(id: string): Promise<{ success: boolean }> {
return fetchApi<{ success: boolean }>(`/templates-file/${id}`, {
return fetchApi<{ success: boolean }>(`/mail/templates-file/${id}`, {
method: 'DELETE',
});
}
@@ -251,7 +315,7 @@ export async function previewMailTemplate(
id: string,
sampleData?: Record<string, string>
): Promise<{ html: string }> {
return fetchApi<{ html: string }>(`/templates-file/${id}/preview`, {
return fetchApi<{ html: string }>(`/mail/templates-file/${id}/preview`, {
method: 'POST',
data: { sampleData },
});
@@ -265,7 +329,7 @@ export async function previewMailTemplate(
* 메일 발송 (단건 또는 소규모 발송)
*/
export async function sendMail(data: SendMailDto): Promise<MailSendResult> {
return fetchApi<MailSendResult>('/send/simple', {
return fetchApi<MailSendResult>('/mail/send/simple', {
method: 'POST',
data,
});
@@ -407,6 +471,15 @@ export async function getReceivedMails(
return fetchApi<ReceivedMail[]>(`/mail/receive/${accountId}?limit=${limit}`);
}
/**
* 오늘 수신한 메일 수 조회 (통계)
*/
export async function getTodayReceivedCount(accountId?: string): Promise<number> {
const params = accountId ? `?accountId=${accountId}` : '';
const response = await fetchApi<{ count: number }>(`/mail/receive/today-count${params}`);
return response.count;
}
/**
* 메일 상세 조회
*/
@@ -439,3 +512,52 @@ export async function testImapConnection(
method: 'POST',
});
}
// ============================================
// 발송 이력 API
// ============================================
/**
* 발송 이력 목록 조회
*/
export async function getSentMailList(
query: SentMailListQuery = {}
): Promise<SentMailListResponse> {
const params = new URLSearchParams();
if (query.page) params.append('page', query.page.toString());
if (query.limit) params.append('limit', query.limit.toString());
if (query.searchTerm) params.append('searchTerm', query.searchTerm);
if (query.status && query.status !== 'all') params.append('status', query.status);
if (query.accountId) params.append('accountId', query.accountId);
if (query.startDate) params.append('startDate', query.startDate);
if (query.endDate) params.append('endDate', query.endDate);
if (query.sortBy) params.append('sortBy', query.sortBy);
if (query.sortOrder) params.append('sortOrder', query.sortOrder);
return fetchApi(`/mail/sent?${params.toString()}`);
}
/**
* 특정 발송 이력 상세 조회
*/
export async function getSentMailById(id: string): Promise<SentMailHistory> {
return fetchApi(`/mail/sent/${id}`);
}
/**
* 발송 이력 삭제
*/
export async function deleteSentMail(id: string): Promise<{ success: boolean; message: string }> {
return fetchApi(`/mail/sent/${id}`, {
method: 'DELETE',
});
}
/**
* 메일 발송 통계 조회
*/
export async function getMailStatistics(accountId?: string): Promise<MailStatistics> {
const params = accountId ? `?accountId=${accountId}` : '';
return fetchApi(`/mail/sent/statistics${params}`);
}