Skip to content

Commit 178acde

Browse files
committed
feat: custom property
1 parent 4de93d1 commit 178acde

File tree

2 files changed

+33
-25
lines changed

2 files changed

+33
-25
lines changed

internal/email/template.go

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,13 @@ import (
1212
)
1313

1414
// TemplateManager manages the loading and rendering of HTML email templates.
15-
// 템플릿 파일은 지정한 baseDir 하위에 존재하며, 내부 플레이스홀더 (예: {{.name}}, {{.group}}, {{.email}})
16-
// 의 치환 기능을 지원합니다.
1715
type TemplateManager struct {
1816
templates map[string]*template.Template
1917
baseDir string
2018
imageDir string
2119
mu sync.RWMutex
2220
}
2321

24-
// NewTemplateManager initializes and returns a new TemplateManager with the provided base directory.
2522
func NewTemplateManager(baseDir string, imageDir string) *TemplateManager {
2623
return &TemplateManager{
2724
templates: make(map[string]*template.Template),
@@ -34,18 +31,13 @@ func (tm *TemplateManager) BaseDir() string {
3431
return tm.baseDir
3532
}
3633

37-
// LoadTemplate loads a template file (relative to baseDir) and caches it under the given name.
38-
// 예: 이름 "default"로 "default.html" 파일을 로드하여 캐시에 저장합니다.
34+
// LoadTemplate loads a template file and caches it under the given name.
3935
func (tm *TemplateManager) LoadTemplate(name, filename string) error {
4036
fullPath := filepath.Join(tm.baseDir, filename)
4137
tmpl, err := template.New(filename).Funcs(template.FuncMap{
42-
// Dummy image function that does nothing
43-
"image": func(imageSrc string) string {
44-
return ""
45-
},
46-
"imageWithSize": func(imageSrc, width, height string) string {
47-
return ""
48-
},
38+
"image": func(imageSrc string) string { return "" },
39+
"imageWithSize": func(imageSrc, width, height string) string { return "" },
40+
"property": func(key string) string { return "" },
4941
}).ParseFiles(fullPath)
5042
if err != nil {
5143
return fmt.Errorf("failed to parse template file %s: %v", fullPath, err)
@@ -57,7 +49,6 @@ func (tm *TemplateManager) LoadTemplate(name, filename string) error {
5749
}
5850

5951
// RenderTemplate executes the cached template identified by name using the provided data.
60-
// 데이터는 예를 들어 map[string]interface{} 형태로 전달할 수 있습니다.
6152
func (tm *TemplateManager) RenderTemplate(name string, data interface{}) (string, []email.Attachment, error) {
6253
tm.mu.RLock()
6354
tmpl, exists := tm.templates[name]
@@ -67,7 +58,7 @@ func (tm *TemplateManager) RenderTemplate(name string, data interface{}) (string
6758
}
6859
var buf bytes.Buffer
6960
var attachments []email.Attachment
70-
tmpl.Funcs(template.FuncMap{
61+
tmpl = tmpl.Funcs(template.FuncMap{
7162
"image": func(imageSrc string) template.HTML {
7263
imagePath := filepath.Join(tm.imageDir, imageSrc)
7364
if _, err := os.Stat(imagePath); err != nil {
@@ -110,14 +101,24 @@ func (tm *TemplateManager) RenderTemplate(name string, data interface{}) (string
110101
})
111102
return template.HTML(fmt.Sprintf("<img src=\"cid:%s\" alt=\"%s\" width=\"%s\" height=\"%s\">", imageSrc, imageSrc, width, height))
112103
},
104+
"property": func(key string) string {
105+
if dataMap, ok := data.(map[string]interface{}); ok {
106+
if custom, ok := dataMap["custom"].(map[string]string); ok {
107+
if val, exists := custom[key]; exists {
108+
return val
109+
}
110+
}
111+
}
112+
return ""
113+
},
113114
})
115+
114116
if err := tmpl.Execute(&buf, data); err != nil {
115117
return "", nil, fmt.Errorf("failed to execute template %s: %v", name, err)
116118
}
117119
return buf.String(), attachments, nil
118120
}
119121

120-
// ListTemplates returns a slice of the names of the currently loaded templates.
121122
func (tm *TemplateManager) ListTemplates() []string {
122123
tm.mu.RLock()
123124
defer tm.mu.RUnlock()
@@ -128,8 +129,6 @@ func (tm *TemplateManager) ListTemplates() []string {
128129
return names
129130
}
130131

131-
// ExportedTemplates returns a shallow copy of the internal template map.
132-
// 외부에서는 이 복사본을 읽기 전용으로 활용할 수 있습니다.
133132
func (tm *TemplateManager) ExportedTemplates() map[string]*template.Template {
134133
tm.mu.RLock()
135134
defer tm.mu.RUnlock()
@@ -140,15 +139,12 @@ func (tm *TemplateManager) ExportedTemplates() map[string]*template.Template {
140139
return templatesCopy
141140
}
142141

143-
// DeleteTemplate removes the template identified by name from the cache.
144142
func (tm *TemplateManager) DeleteTemplate(name string) {
145143
tm.mu.Lock()
146144
defer tm.mu.Unlock()
147145
delete(tm.templates, name)
148146
}
149147

150-
// Templates returns the internal template map.
151-
// 이 메서드는 내부에서만 읽기 전용으로 사용합니다.
152148
func (tm *TemplateManager) Templates() map[string]*template.Template {
153149
return tm.ExportedTemplates()
154150
}

internal/web/handlers.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ func (h *APIHandler) PreviewTemplateHandler(w http.ResponseWriter, r *http.Reque
203203
return template.HTML(fmt.Sprintf("<img src=\"%s\" width=\"%s\" height=\"%s\">",
204204
html.EscapeString("/api/images/"+imageSrc), html.EscapeString(width), html.EscapeString(height)))
205205
},
206+
"property": func(key string) template.HTML {
207+
return template.HTML(fmt.Sprintf("<code>PROPERTY(%s)</code>", key))
208+
},
206209
}).Parse(reqData.Content)
207210
if err != nil {
208211
http.Error(w, fmt.Sprintf("템플릿 파싱 실패: %v", err), http.StatusBadRequest)
@@ -220,6 +223,13 @@ func (h *APIHandler) PreviewTemplateHandler(w http.ResponseWriter, r *http.Reque
220223
return template.HTML(fmt.Sprintf("<img src=\"%s\" alt=\"\" style=\"max-width: 100%%; height: auto;\">",
221224
html.EscapeString("/api/images/"+imageSrc)))
222225
},
226+
"imageWithSize": func(imageSrc, width, height string) template.HTML {
227+
return template.HTML(fmt.Sprintf("<img src=\"%s\" width=\"%s\" height=\"%s\">",
228+
html.EscapeString("/api/images/"+imageSrc), html.EscapeString(width), html.EscapeString(height)))
229+
},
230+
"property": func(key string) template.HTML {
231+
return template.HTML(fmt.Sprintf("<code>PROPERTY(%s)</code>", key))
232+
},
223233
}).Parse(string(contentBytes))
224234
if err != nil {
225235
http.Error(w, fmt.Sprintf("템플릿 파싱 실패: %v", err), http.StatusInternalServerError)
@@ -277,8 +287,9 @@ func (h *APIHandler) EmailHandler(w http.ResponseWriter, r *http.Request) {
277287
}
278288

279289
type RecipientInfo struct {
280-
Name string `json:"name"`
281-
Email string `json:"email"`
290+
Name string `json:"name"`
291+
Email string `json:"email"`
292+
Custom map[string]string `json:"custom,omitempty"`
282293
}
283294

284295
var reqData struct {
@@ -299,9 +310,10 @@ func (h *APIHandler) EmailHandler(w http.ResponseWriter, r *http.Request) {
299310
go func(recipients []RecipientInfo) {
300311
for _, rec := range recipients {
301312
data := map[string]interface{}{
302-
"name": rec.Name,
303-
"email": rec.Email,
304-
"year": time.Now().Year(),
313+
"name": rec.Name,
314+
"email": rec.Email,
315+
"year": time.Now().Year(),
316+
"custom": rec.Custom,
305317
}
306318
body, attachments, err := h.TemplateManager.RenderTemplate(reqData.Template, data)
307319
if err != nil {

0 commit comments

Comments
 (0)