Tempalte dir
This commit is contained in:
@@ -107,6 +107,7 @@ var flagsServe = append(
|
|||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-startup-queries", Aliases: []string{"web_push_startup_queries"}, EnvVars: []string{"NTFY_WEB_PUSH_STARTUP_QUERIES"}, Usage: "queries run when the web push database is initialized"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-startup-queries", Aliases: []string{"web_push_startup_queries"}, EnvVars: []string{"NTFY_WEB_PUSH_STARTUP_QUERIES"}, Usage: "queries run when the web push database is initialized"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-duration", Aliases: []string{"web_push_expiry_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryDuration), Usage: "automatically expire unused subscriptions after this time"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-duration", Aliases: []string{"web_push_expiry_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryDuration), Usage: "automatically expire unused subscriptions after this time"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-warning-duration", Aliases: []string{"web_push_expiry_warning_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryWarningDuration), Usage: "send web push warning notification after this time before expiring unused subscriptions"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "web-push-expiry-warning-duration", Aliases: []string{"web_push_expiry_warning_duration"}, EnvVars: []string{"NTFY_WEB_PUSH_EXPIRY_WARNING_DURATION"}, Value: util.FormatDuration(server.DefaultWebPushExpiryWarningDuration), Usage: "send web push warning notification after this time before expiring unused subscriptions"}),
|
||||||
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "template-directory", Aliases: []string{"template_directory"}, EnvVars: []string{"NTFY_TEMPLATE_DIRECTORY"}, Usage: "directory to load named templates from"}),
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdServe = &cli.Command{
|
var cmdServe = &cli.Command{
|
||||||
@@ -205,6 +206,7 @@ func execServe(c *cli.Context) error {
|
|||||||
metricsListenHTTP := c.String("metrics-listen-http")
|
metricsListenHTTP := c.String("metrics-listen-http")
|
||||||
enableMetrics := c.Bool("enable-metrics") || metricsListenHTTP != ""
|
enableMetrics := c.Bool("enable-metrics") || metricsListenHTTP != ""
|
||||||
profileListenHTTP := c.String("profile-listen-http")
|
profileListenHTTP := c.String("profile-listen-http")
|
||||||
|
templateDirectory := c.String("template-directory")
|
||||||
|
|
||||||
// Convert durations
|
// Convert durations
|
||||||
cacheDuration, err := util.ParseDuration(cacheDurationStr)
|
cacheDuration, err := util.ParseDuration(cacheDurationStr)
|
||||||
@@ -461,6 +463,7 @@ func execServe(c *cli.Context) error {
|
|||||||
conf.WebPushStartupQueries = webPushStartupQueries
|
conf.WebPushStartupQueries = webPushStartupQueries
|
||||||
conf.WebPushExpiryDuration = webPushExpiryDuration
|
conf.WebPushExpiryDuration = webPushExpiryDuration
|
||||||
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
|
conf.WebPushExpiryWarningDuration = webPushExpiryWarningDuration
|
||||||
|
conf.TemplateDirectory = templateDirectory
|
||||||
conf.Version = c.App.Version
|
conf.Version = c.App.Version
|
||||||
|
|
||||||
// Set up hot-reloading of config
|
// Set up hot-reloading of config
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ type Config struct {
|
|||||||
WebPushExpiryDuration time.Duration
|
WebPushExpiryDuration time.Duration
|
||||||
WebPushExpiryWarningDuration time.Duration
|
WebPushExpiryWarningDuration time.Duration
|
||||||
Version string // injected by App
|
Version string // injected by App
|
||||||
|
TemplateDirectory string // Directory to load named templates from
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig instantiates a default new server config
|
// NewConfig instantiates a default new server config
|
||||||
@@ -257,5 +258,6 @@ func NewConfig() *Config {
|
|||||||
WebPushEmailAddress: "",
|
WebPushEmailAddress: "",
|
||||||
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
WebPushExpiryDuration: DefaultWebPushExpiryDuration,
|
||||||
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
WebPushExpiryWarningDuration: DefaultWebPushExpiryWarningDuration,
|
||||||
|
TemplateDirectory: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ type Server struct {
|
|||||||
metricsHandler http.Handler // Handles /metrics if enable-metrics set, and listen-metrics-http not set
|
metricsHandler http.Handler // Handles /metrics if enable-metrics set, and listen-metrics-http not set
|
||||||
closeChan chan bool
|
closeChan chan bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
templates map[string]*template.Template // Loaded named templates
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleFunc extends the normal http.HandlerFunc to be able to easily return errors
|
// handleFunc extends the normal http.HandlerFunc to be able to easily return errors
|
||||||
@@ -222,8 +223,16 @@ func New(conf *Config) (*Server, error) {
|
|||||||
messagesHistory: []int64{messages},
|
messagesHistory: []int64{messages},
|
||||||
visitors: make(map[string]*visitor),
|
visitors: make(map[string]*visitor),
|
||||||
stripe: stripe,
|
stripe: stripe,
|
||||||
|
templates: make(map[string]*template.Template),
|
||||||
}
|
}
|
||||||
s.priceCache = util.NewLookupCache(s.fetchStripePrices, conf.StripePriceCacheDuration)
|
s.priceCache = util.NewLookupCache(s.fetchStripePrices, conf.StripePriceCacheDuration)
|
||||||
|
if conf.TemplateDirectory != "" {
|
||||||
|
tmpls, err := loadTemplatesFromDir(conf.TemplateDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load templates from %s: %w", conf.TemplateDirectory, err)
|
||||||
|
}
|
||||||
|
s.templates = tmpls
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1113,10 +1122,10 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedR
|
|||||||
return errHTTPEntityTooLargeJSONBody
|
return errHTTPEntityTooLargeJSONBody
|
||||||
}
|
}
|
||||||
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
|
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
|
||||||
if m.Message, err = replaceTemplate(m.Message, peekedBody); err != nil {
|
if m.Message, err = s.replaceTemplate(m.Message, peekedBody); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.Title, err = replaceTemplate(m.Title, peekedBody); err != nil {
|
if m.Title, err = s.replaceTemplate(m.Title, peekedBody); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(m.Message) > s.config.MessageSizeLimit {
|
if len(m.Message) > s.config.MessageSizeLimit {
|
||||||
@@ -1125,10 +1134,26 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedR
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func replaceTemplate(tpl string, source string) (string, error) {
|
func (s *Server) replaceTemplate(tpl string, source string) (string, error) {
|
||||||
if templateDisallowedRegex.MatchString(tpl) {
|
if templateDisallowedRegex.MatchString(tpl) {
|
||||||
return "", errHTTPBadRequestTemplateDisallowedFunctionCalls
|
return "", errHTTPBadRequestTemplateDisallowedFunctionCalls
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(tpl, "@") {
|
||||||
|
name := strings.TrimPrefix(tpl, "@")
|
||||||
|
t, ok := s.templates[name]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("template '@%s' not found", name)
|
||||||
|
}
|
||||||
|
var data any
|
||||||
|
if err := json.Unmarshal([]byte(source), &data); err != nil {
|
||||||
|
return "", errHTTPBadRequestTemplateMessageNotJSON
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
|
||||||
|
return "", errHTTPBadRequestTemplateExecuteFailed
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
var data any
|
var data any
|
||||||
if err := json.Unmarshal([]byte(source), &data); err != nil {
|
if err := json.Unmarshal([]byte(source), &data); err != nil {
|
||||||
return "", errHTTPBadRequestTemplateMessageNotJSON
|
return "", errHTTPBadRequestTemplateMessageNotJSON
|
||||||
@@ -2061,3 +2086,32 @@ func (s *Server) updateAndWriteStats(messagesCount int64) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadTemplatesFromDir(dir string) (map[string]*template.Template, error) {
|
||||||
|
templates := make(map[string]*template.Template)
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := entry.Name()
|
||||||
|
if !strings.HasSuffix(name, ".tmpl") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := filepath.Join(dir, name)
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read template %s: %w", name, err)
|
||||||
|
}
|
||||||
|
tmpl, err := template.New(name).Funcs(sprig.FuncMap()).Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse template %s: %w", name, err)
|
||||||
|
}
|
||||||
|
base := strings.TrimSuffix(name, ".tmpl")
|
||||||
|
templates[base] = tmpl
|
||||||
|
}
|
||||||
|
return templates, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user