diff options
Diffstat (limited to 'lib/knox-api/mail/knox-mail.ts')
| -rw-r--r-- | lib/knox-api/mail/knox-mail.ts | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/lib/knox-api/mail/knox-mail.ts b/lib/knox-api/mail/knox-mail.ts new file mode 100644 index 00000000..48ba6c16 --- /dev/null +++ b/lib/knox-api/mail/knox-mail.ts @@ -0,0 +1,312 @@ +'use server'; + +// Knox Mail API TypeScript Library for Next.js Server Actions +// Based on Knox Mail API Guide v2.0 + +export interface KnoxMailConfig { + baseUrl: string; + systemId: string; + userId: string; +} + +// 공통 타입 정의 +export interface KnoxMailResponse<T = unknown> { + result: 'success' | 'error'; + mailId?: string; + data?: T; + error?: { + code: string; + message: string; + }; +} + +// 발신인 정보 +export interface Sender { + emailAddress: string; +} + +// 수신인 정보 +export interface Recipient { + emailAddress: string; + recipientType: 'TO' | 'CC' | 'BCC'; +} + +// 일반 메일 발신 요청 +export interface SendMailRequest { + subject: string; + contents: string; + contentType: 'TEXT' | 'HTML' | 'MIME'; + docSecuType: 'PERSONAL' | 'OFFICIAL'; + sender: Sender; + recipients: Recipient[]; + attachments?: File[]; + reservedTime?: string; // yyyy-MM-dd HH:mm 형식 +} + +// 대외비 메일 DRM 옵션 +export interface DrmOption { + validDays: string; + useCount: string; + canView: string; + canPrint: string; + notifyExternalUser: string; + notifyInternalUser: string; +} + +// 대외비 메일 발신 요청 +export interface SendSecuMailRequest extends Omit<SendMailRequest, 'docSecuType'> { + docSecuType: 'CONFIDENTIAL' | 'CONFIDENTIAL_STRICT'; + drmOption: DrmOption; +} + +// 메일 발신 취소 요청 +export interface CancelMailRequest { + recipients: string[]; +} + +// 메일 별 수신상태 조회 요청 +export interface DeliveryStatusCountRequest { + mailIds: string[]; +} + +// 수신상태 카운트 응답 +export interface DeliveryStatusCount { + mailId: string; + totalStatusCount: number; + sendingCount: number; + openCount: number; + unopenCount: number; + etcCount: number; + sendCancelCount: number; + sendCancelReqCount: number; + sendBlockCount: number; + sendFailCount: number; + drmProcessCount: number; + drmFailCount: number; + reserveCount: number; + reserveCancelCount: number; + unknownCount: number; +} + +// 수신인 별 수신상태 응답 +export interface RecipientDeliveryStatus { + name: string; + position: string; + enName: string; + enPosition: string; + company: string; + department: string; + enDepartment: string; + email: string; + userID: string; + recipientType: 'TO' | 'CC' | 'BCC'; + updateTime: { + month: number; + year: number; + dayOfMonth: number; + hourOfDay: number; + minute: number; + second: number; + }; + status: string; + needOpenNotify: boolean; + isDLAddr: boolean; +} + +// 내부 유틸리티 함수들 +function getHeaders(systemId: string): Record<string, string> { + return { + 'System-ID': systemId, + 'Content-Type': 'application/json', + }; +} + +async function makeRequest<T>( + config: KnoxMailConfig, + endpoint: string, + method: 'GET' | 'POST', + data?: unknown, + isMultipart = false +): Promise<KnoxMailResponse<T>> { + const url = `${config.baseUrl}${endpoint}?userId=${config.userId}`; + + const headers = getHeaders(config.systemId); + if (isMultipart) { + delete headers['Content-Type']; // multipart/form-data는 브라우저가 자동으로 설정 + } + + try { + const response = await fetch(url, { + method, + headers, + body: data ? (isMultipart ? data as FormData : JSON.stringify(data)) : undefined, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + return { + result: 'error', + error: { + code: response.status.toString(), + message: errorData.message || response.statusText, + }, + }; + } + + const result = await response.json(); + return { + result: 'success', + ...result, + }; + } catch (error) { + return { + result: 'error', + error: { + code: 'NETWORK_ERROR', + message: error instanceof Error ? error.message : 'Unknown error', + }, + }; + } +} + +function createFormData(mailData: Record<string, unknown>, attachments?: File[]): FormData { + const formData = new FormData(); + + // 메일 데이터를 JSON으로 추가 + formData.append('mail', JSON.stringify(mailData)); + + // 첨부파일이 있으면 추가 + if (attachments && attachments.length > 0) { + attachments.forEach(file => { + formData.append('attachments', file); + }); + } + + return formData; +} + +// Next.js 서버 액션 함수들 + +/** + * 일반 메일 발신 서버 액션 + * @param config Knox Mail API 설정 + * @param request 메일 발신 요청 데이터 + * @returns 발신 결과 (mailId 포함) + */ +export async function sendMail( + config: KnoxMailConfig, + request: SendMailRequest +): Promise<KnoxMailResponse<{ mailId: string }>> { + const { attachments, ...mailData } = request; + + if (attachments && attachments.length > 0) { + const formData = createFormData(mailData, attachments); + return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/send', 'POST', formData, true); + } else { + return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/send', 'POST', mailData); + } +} + +/** + * 대외비 메일 발신 서버 액션 + * @param config Knox Mail API 설정 + * @param request 대외비 메일 발신 요청 데이터 + * @returns 발신 결과 (mailId 포함) + */ +export async function sendSecuMail( + config: KnoxMailConfig, + request: SendSecuMailRequest +): Promise<KnoxMailResponse<{ mailId: string }>> { + const { attachments, ...mailData } = request; + + if (attachments && attachments.length > 0) { + const formData = createFormData(mailData, attachments); + return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/secu-send', 'POST', formData, true); + } else { + return makeRequest<{ mailId: string }>(config, '/mail/api/v2.0/mails/secu-send', 'POST', mailData); + } +} + +/** + * 메일 발신 취소 서버 액션 + * @param config Knox Mail API 설정 + * @param mailId 취소할 메일 ID + * @param request 취소 요청 데이터 (수신인 목록) + * @returns 취소 결과 + */ +export async function cancelMail( + config: KnoxMailConfig, + mailId: string, + request: CancelMailRequest +): Promise<KnoxMailResponse> { + return makeRequest(config, `/mail/api/v2.0/mails/${mailId}/cancel`, 'POST', request); +} + +/** + * 메일 별 수신상태 조회 서버 액션 + * @param config Knox Mail API 설정 + * @param request 조회할 메일 ID 목록 + * @returns 메일별 수신상태 카운트 목록 + */ +export async function getDeliveryStatusCount( + config: KnoxMailConfig, + request: DeliveryStatusCountRequest +): Promise<KnoxMailResponse<DeliveryStatusCount[]>> { + return makeRequest<DeliveryStatusCount[]>(config, '/mail/api/v2.0/mails/deliverystatus/count', 'POST', request); +} + +/** + * 수신인 별 수신상태 조회 서버 액션 + * @param config Knox Mail API 설정 + * @param mailId 조회할 메일 ID + * @returns 수신인별 수신상태 목록 + */ +export async function getRecipientDeliveryStatus( + config: KnoxMailConfig, + mailId: string +): Promise<KnoxMailResponse<RecipientDeliveryStatus[]>> { + return makeRequest<RecipientDeliveryStatus[]>(config, `/mail/api/v2.0/mails/${mailId}/deliverystatus`, 'GET'); +} + +// 편의 함수들 +export async function createKnoxMailConfig( + baseUrl: string, + systemId: string, + userId: string +): Promise<KnoxMailConfig> { + return { + baseUrl, + systemId, + userId, + }; +} + +// 에러 코드 상수 +export const KNOX_MAIL_ERROR_CODES = { + ML1001: '필수값 중 입력되지 않은 값이 있습니다.', + ML1002: '사용자 정보가 존재하지 않습니다.', + ML1003: '요청하신 데이터가 존재하지 않습니다.', + ML1101: '본문 (content) 값이 입력되지 않았습니다.', + ML1102: '첨부 파일 처리 중 에러가 발생했습니다.', + ML1104: '일반 메일 발신 중 docSecuType 값이 유효하지 않습니다.', + ML1107: '대외비/극비 보안 옵션 값이 유효하지 않습니다.', + ML1108: '대외비/극비 메일 contentType 값이 유효하지 않습니다.', + ML1109: '대외비/극비 메일 docSecuType 값이 유효하지 않습니다.', + ML1110: '극비 메일일 경우에는 내부 수신인에게만 메일 발신이 가능합니다.', + ML1111: '대외비/극비 메일에서 허용되지 않는 첨부 파일의 확장자가 있습니다.', + ML1112: '메일 제목 크기가 300 bytes를 초과하였습니다.', + ML1114: '메일 발신 API에서 허용되는 content 크기를 초과하였습니다.', + ML1115: '일반 메일 contentType 값이 유효하지 않습니다.', + ML1117: '메일 발신 API에서 발신가능한 최대 수신인을 초과하였습니다.', + ML1118: '수신자의 수신 타입이 잘못 설정되었습니다.', + CO400: '잘못된 요청입니다.', +} as const; + +// 타입 가드 함수들 +export function isKnoxMailError(response: KnoxMailResponse): response is KnoxMailResponse & { error: { code: string; message: string } } { + return response.result === 'error' && !!response.error; +} + +export function isKnoxMailSuccess<T>(response: KnoxMailResponse<T>): response is KnoxMailResponse<T> & { result: 'success' } { + return response.result === 'success'; +} |
