'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 { 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 { if (initialized) return; await initializeAsync(); } function registerHandlebarsHelpers(): void { // i18n 번역 헬퍼 handlebars.registerHelper('t', function(key: string, options: { hash?: Record }) { 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('