summaryrefslogtreecommitdiff
path: root/lib/mail/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mail/service.ts')
-rw-r--r--lib/mail/service.ts380
1 files changed, 0 insertions, 380 deletions
diff --git a/lib/mail/service.ts b/lib/mail/service.ts
deleted file mode 100644
index cbd02953..00000000
--- a/lib/mail/service.ts
+++ /dev/null
@@ -1,380 +0,0 @@
-'use server';
-
-import fs from 'fs';
-import path from 'path';
-import handlebars from 'handlebars';
-import i18next from 'i18next';
-import resourcesToBackend from 'i18next-resources-to-backend';
-import { getOptions } from '@/i18n/settings';
-
-// Types
-export interface TemplateFile {
- name: string;
- content: string;
- lastModified: string;
- path: string;
-}
-
-interface UpdateTemplateRequest {
- name: string;
- content: string;
-}
-
-interface TemplateValidationResult {
- isValid: boolean;
- errors: string[];
- warnings: string[];
-}
-
-// Configuration
-const baseDir = path.join(process.cwd(), 'lib', 'mail');
-const templatesDir = path.join(baseDir, 'templates');
-
-let initialized = false;
-
-function getFilePath(name: string): string {
- const fileName = name.endsWith('.hbs') ? name : `${name}.hbs`;
- return path.join(templatesDir, fileName);
-}
-
-// Initialization
-async function initializeAsync(): Promise<void> {
- if (initialized) return;
-
- try {
- // i18next 초기화 (서버 사이드)
- if (!i18next.isInitialized) {
- await i18next
- .use(resourcesToBackend((language: string, namespace: string) =>
- import(`@/i18n/locales/${language}/${namespace}.json`)
- ))
- .init(getOptions());
- }
-
- // Handlebars 헬퍼 등록
- registerHandlebarsHelpers();
- initialized = true;
- } catch (error) {
- console.error('Failed to initialize TemplateService:', error);
- // 초기화 실패해도 헬퍼는 등록
- registerHandlebarsHelpers();
- initialized = true;
- }
-}
-
-async function ensureInitialized(): Promise<void> {
- if (initialized) return;
- await initializeAsync();
-}
-
-function registerHandlebarsHelpers(): void {
- // i18n 번역 헬퍼
- handlebars.registerHelper('t', function(key: string, options: { hash?: Record<string, unknown> }) {
- return i18next.t(key, options.hash || {});
- });
-
- // 날짜 포맷 헬퍼
- handlebars.registerHelper('formatDate', function(date: Date | string, format?: string) {
- if (!date) return '';
- const d = new Date(date);
- if (isNaN(d.getTime())) return '';
-
- if (!format || format === 'date') {
- return d.toISOString().split('T')[0];
- }
-
- if (format === 'datetime') {
- return d.toLocaleString('ko-KR', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- }
-
- return d.toLocaleDateString('ko-KR');
- });
-
- // 숫자 포맷 헬퍼
- handlebars.registerHelper('formatNumber', function(number: number | string) {
- if (typeof number === 'string') {
- number = parseFloat(number);
- }
- if (isNaN(number)) return '';
- return number.toLocaleString('ko-KR');
- });
-
- // 조건부 렌더링 헬퍼
- handlebars.registerHelper('ifEquals', function(this: unknown, arg1: unknown, arg2: unknown, options: { fn: (context: unknown) => string; inverse: (context: unknown) => string }) {
- return (arg1 === arg2) ? options.fn(this) : options.inverse(this);
- });
-
- // 배열 길이 확인 헬퍼
- handlebars.registerHelper('ifArrayLength', function(this: unknown, array: unknown[], length: number, options: { fn: (context: unknown) => string; inverse: (context: unknown) => string }) {
- return (Array.isArray(array) && array.length === length) ? options.fn(this) : options.inverse(this);
- });
-}
-
-// Validation
-function validateTemplate(content: string): TemplateValidationResult {
- const errors: string[] = [];
- const warnings: string[] = [];
-
- try {
- // Handlebars 문법 검사
- handlebars.compile(content);
- } catch (error) {
- if (error instanceof Error) {
- errors.push(`Handlebars 문법 오류: ${error.message}`);
- }
- }
-
- // 일반적인 문제 확인
- if (content.trim().length === 0) {
- errors.push('템플릿 내용이 비어있습니다.');
- }
-
- // 위험한 패턴 확인
- if (content.includes('<script>')) {
- warnings.push('스크립트 태그가 포함되어 있습니다. 보안에 주의하세요.');
- }
-
- return {
- isValid: errors.length === 0,
- errors,
- warnings
- };
-}
-
-// Core Functions
-async function getTemplateList(): Promise<TemplateFile[]> {
- try {
- if (!fs.existsSync(templatesDir)) {
- return [];
- }
-
- const files = fs.readdirSync(templatesDir).filter(file => file.endsWith('.hbs'));
- const allTemplates: TemplateFile[] = [];
-
- for (const file of files) {
- const filePath = path.join(templatesDir, file);
-
- try {
- const stats = fs.statSync(filePath);
- const content = fs.readFileSync(filePath, 'utf8');
-
- allTemplates.push({
- name: file.replace('.hbs', ''),
- content,
- lastModified: stats.mtime.toISOString(),
- path: filePath
- });
- } catch (fileError) {
- console.error(`❌ 파일 읽기 실패: ${filePath}`, fileError);
- }
- }
-
- return allTemplates.sort((a, b) => a.name.localeCompare(b.name));
- } catch (error) {
- console.error('Error getting template list:', error);
- throw new Error('템플릿 목록을 가져오는데 실패했습니다.');
- }
-}
-
-async function getTemplate(name: string): Promise<TemplateFile | null> {
- try {
- const filePath = getFilePath(name);
-
- if (!fs.existsSync(filePath)) {
- return null;
- }
-
- const stats = fs.statSync(filePath);
- const content = fs.readFileSync(filePath, 'utf8');
-
- return {
- name: name.replace('.hbs', ''),
- content,
- lastModified: stats.mtime.toISOString(),
- path: filePath
- };
- } catch (error) {
- console.error(`❌ 템플릿 조회 실패 ${name}:`, error);
- throw new Error(`템플릿 ${name}을 가져오는데 실패했습니다.`);
- }
-}
-
-async function updateTemplate(request: UpdateTemplateRequest): Promise<TemplateFile> {
- try {
- const { name, content } = request;
-
- // 템플릿 유효성 검사
- const validation = validateTemplate(content);
- if (!validation.isValid) {
- throw new Error(`템플릿 유효성 검사 실패: ${validation.errors.join(', ')}`);
- }
-
- const filePath = getFilePath(name);
-
- if (!fs.existsSync(filePath)) {
- throw new Error(`템플릿 ${name}이 존재하지 않습니다.`);
- }
-
- fs.writeFileSync(filePath, content, 'utf8');
-
- const stats = fs.statSync(filePath);
-
- return {
- name: name.replace('.hbs', ''),
- content,
- lastModified: stats.mtime.toISOString(),
- path: filePath
- };
- } catch (error) {
- console.error('Error updating template:', error);
- if (error instanceof Error) {
- throw error;
- }
- throw new Error('템플릿 수정에 실패했습니다.');
- }
-}
-
-async function searchTemplates(query: string): Promise<TemplateFile[]> {
- try {
- const allTemplates = await getTemplateList();
- const lowerQuery = query.toLowerCase();
-
- return allTemplates.filter(template =>
- template.name.toLowerCase().includes(lowerQuery) ||
- template.content.toLowerCase().includes(lowerQuery)
- );
- } catch (error) {
- console.error('Error searching templates:', error);
- throw new Error('템플릿 검색에 실패했습니다.');
- }
-}
-
-async function previewTemplate(
- name: string,
- data?: Record<string, unknown>
-): Promise<string> {
- try {
- // 초기화 대기
- await ensureInitialized();
-
- const template = await getTemplate(name);
- if (!template) {
- throw new Error(`템플릿 ${name}이 존재하지 않습니다.`);
- }
-
- // 템플릿 컴파일
- const compiledTemplate = handlebars.compile(template.content);
- const content = compiledTemplate(data || {});
-
- return content;
- } catch (error) {
- console.error('Error previewing template:', error);
- throw new Error('템플릿 미리보기 생성에 실패했습니다.');
- }
-}
-
-// Server Actions
-export async function getTemplatesAction(search?: string) {
- try {
- let templates;
-
- if (search) {
- templates = await searchTemplates(search);
- } else {
- templates = await getTemplateList();
- }
-
- return {
- success: true,
- data: templates
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : '템플릿 목록을 가져오는데 실패했습니다.'
- };
- }
-}
-
-export async function getTemplateAction(name: string) {
- try {
- const template = await getTemplate(name);
-
- if (!template) {
- return {
- success: false,
- error: '템플릿을 찾을 수 없습니다.'
- };
- }
-
- return {
- success: true,
- data: template
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : '템플릿을 가져오는데 실패했습니다.'
- };
- }
-}
-
-export async function updateTemplateAction(name: string, content: string) {
- try {
- if (!content) {
- return {
- success: false,
- error: '템플릿 내용이 필요합니다.'
- };
- }
-
- const template = await updateTemplate({
- name,
- content
- });
-
- return {
- success: true,
- data: template
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : '템플릿 수정에 실패했습니다.'
- };
- }
-}
-
-export async function previewTemplateAction(
- name: string,
- data?: Record<string, unknown>
-) {
- try {
- if (!name) {
- return {
- success: false,
- error: '템플릿 이름이 필요합니다.'
- };
- }
-
- const result = await previewTemplate(name, data);
-
- return {
- success: true,
- data: {
- html: result
- }
- };
- } catch (error) {
- return {
- success: false,
- error: error instanceof Error ? error.message : '미리보기 생성에 실패했습니다.'
- };
- }
-} \ No newline at end of file