Add ntfy config settings

This commit is contained in:
2026-03-26 13:54:29 +01:00
parent 3116d53b65
commit fc85e859e0
7 changed files with 129 additions and 16 deletions

View File

@@ -73,6 +73,10 @@ func main() {
configRepo := config.NewRepository(dbCon)
configService := config.NewService(configRepo)
if err := configService.EnsureDefaults(); err != nil {
panic(fmt.Errorf("failed to ensure config defaults: %w", err))
}
fileRepo := file.NewRepository(dbCon)
fileService := file.NewService(fileRepo, "./uploads")
fileHandler := file.NewHandler(fileService, configService)

View File

@@ -1,5 +1,7 @@
package config
import "strconv"
const (
KeyUploadMaxFileSizeBytes = "upload.max_file_size_bytes"
KeyUploadMultiMaxFiles = "upload.multi.max_files"
@@ -8,6 +10,10 @@ const (
KeyRateLimitApiPerMinute = "ratelimit.api.per_minute"
KeyRateLimitApiBurst = "ratelimit.api.burst"
KeyRateLimitLoginBurst = "ratelimit.login.burst"
KeyUseNtfy = "use_ntfy"
KeyNtfyUrl = "ntfy.url"
KeyNtfyTopic = "ntfy.topic"
)
// Defaults (used when DB does not have an override)
@@ -20,4 +26,27 @@ const (
DefaultRateLimitLoginBurst = 10
DefaultRateLimitApiPerMinute = 60
DefaultRateLimitApiBurst = 30
DefaultUseNtfy = 0
DefaultNtfyUrl = ""
DefaultNtfyTopic = ""
)
// DefaultKeyValues returns a map of config keys to their default string values, for use when initializing the database.
// Code duplication be dammed
func DefaultKeyValues() map[string]string {
return map[string]string{
KeyUploadMaxFileSizeBytes: strconv.FormatInt(DefaultUploadMaxFileSizeBytes, 10),
KeyUploadMultiMaxFiles: strconv.Itoa(DefaultUploadMultiMaxFiles),
KeyUploadMaxHours: strconv.Itoa(DefaultUploadMaxHours),
KeyRateLimitLoginPerMinute: strconv.Itoa(DefaultRateLimitLoginPerMinute),
KeyRateLimitLoginBurst: strconv.Itoa(DefaultRateLimitLoginBurst),
KeyRateLimitApiPerMinute: strconv.Itoa(DefaultRateLimitApiPerMinute),
KeyRateLimitApiBurst: strconv.Itoa(DefaultRateLimitApiBurst),
KeyUseNtfy: strconv.Itoa(DefaultUseNtfy),
KeyNtfyUrl: DefaultNtfyUrl,
KeyNtfyTopic: DefaultNtfyTopic,
}
}

View File

@@ -1,6 +1,10 @@
package config
import "gorm.io/gorm"
import (
"errors"
"gorm.io/gorm"
)
type Repository struct {
db *gorm.DB
@@ -37,3 +41,15 @@ func (r *Repository) List() ([]ConfigEntry, error) {
}
return entries, nil
}
func (r *Repository) CreateIfMissing(key, value string) error {
var e ConfigEntry
err := r.db.First(&e, "key = ?", key).Error
if err == nil {
return nil
}
if errors.Is(err, gorm.ErrRecordNotFound) {
return r.db.Create(&ConfigEntry{Key: key, Value: value}).Error
}
return err
}

View File

@@ -22,6 +22,15 @@ func NewService(r *Repository) *Service {
return &Service{repo: r, cache: make(map[string]string)}
}
func (s *Service) EnsureDefaults() error {
for k, v := range DefaultKeyValues() {
if err := s.repo.CreateIfMissing(k, v); err != nil {
return err
}
}
return nil
}
func (s *Service) List() ([]ConfigEntry, error) {
entries, err := s.repo.List()
if err != nil {

View File

@@ -20,6 +20,10 @@ type ConfigPageData struct {
RateLimitLoginBurst int
RateLimitApiPerMinute int
RateLimitApiBurst int
NtfyUse bool
NtfyUrl string
NtfyTopic string
}
// ConfigPage renders a modular admin config screen.
@@ -36,6 +40,9 @@ func (h *Handler) ConfigPage(c *gin.Context) {
RateLimitLoginBurst: cfg.GetIntDefault(config.KeyRateLimitLoginBurst, config.DefaultRateLimitLoginBurst),
RateLimitApiPerMinute: cfg.GetIntDefault(config.KeyRateLimitApiPerMinute, config.DefaultRateLimitApiPerMinute),
RateLimitApiBurst: cfg.GetIntDefault(config.KeyRateLimitApiBurst, config.DefaultRateLimitApiBurst),
NtfyUse: cfg.GetIntDefault(config.KeyUseNtfy, 0) != 0,
NtfyUrl: cfg.GetStringDefault(config.KeyNtfyUrl, config.DefaultNtfyUrl),
NtfyTopic: cfg.GetStringDefault(config.KeyNtfyTopic, config.DefaultNtfyTopic),
}
c.HTML(http.StatusOK, "config.html", data)
@@ -113,6 +120,14 @@ func (h *Handler) ConfigSave(c *gin.Context) {
return
}
useNTFY, err := strconv.ParseBool(c.PostForm("ntfy_use"))
if err != nil {
h.renderConfigError(c, "invalid ntfy use value")
return
}
ntfyUrl := c.PostForm("ntfy_url")
ntfyTopic := c.PostForm("ntfy_topic")
// Persist.
if err := cfg.SetString(config.KeyUploadMaxFileSizeBytes, strconv.FormatInt(maxMB*1024*1024, 10)); err != nil {
h.renderConfigError(c, err.Error())
@@ -132,6 +147,15 @@ func (h *Handler) ConfigSave(c *gin.Context) {
_ = cfg.SetString(config.KeyRateLimitApiPerMinute, strconv.Itoa(apiPerMin))
_ = cfg.SetString(config.KeyRateLimitApiBurst, strconv.Itoa(apiBurst))
// shitty ah fix
actualBool := 0
if useNTFY {
actualBool = 1
}
_ = cfg.SetString(config.KeyUseNtfy, strconv.Itoa(actualBool))
_ = cfg.SetString(config.KeyNtfyUrl, ntfyUrl)
_ = cfg.SetString(config.KeyNtfyTopic, ntfyTopic)
c.Redirect(http.StatusFound, "/config?saved=1")
}

View File

@@ -19,6 +19,7 @@ type ConfigService interface {
GetIntDefault(key string, def int) int
GetInt64Default(key string, def int64) int64
SetString(key, value string) error
GetStringDefault(key string, value string) string
}
func NewHandler(fileService *file.Service, cfg ConfigService) *Handler {

View File

@@ -207,6 +207,26 @@
</div>
</div>
<div class="box mb-6">
<div class="section-title">NTFY_Settings</div>
<div class="row">
<label>Use_NTFY</label>
<input type="checkbox" id="ntfy_use_seen" {{if .NtfyUse}}checked{{end}}>
<input type="hidden" name="ntfy_use" id="ntfy_use_hidden" value="{{if .NtfyUse}}true{{else}}false{{end}}">
</div>
<div class="row">
<label>NTFY_Url</label>
<input type="text" name="ntfy_url" value="{{.NtfyUrl}}">
</div>
<div class="row">
<label>NTFY_Topic</label>
<input type="text" name="ntfy_topic" value="{{.NtfyTopic}}">
</div>
</div>
<!-- ACTIONS -->
<div class="flex justify-between items-center border-t-8 border-black pt-4">
<button type="submit">SAVE</button>
@@ -250,6 +270,16 @@
// Listen for changes
document.addEventListener('input', updateConversions);
const checkbox = document.getElementById('ntfy_use_seen');
const hidden = document.getElementById('ntfy_use_hidden');
if (checkbox && hidden) {
// Update hidden input whenever checkbox changes
checkbox.addEventListener('change', () => {
hidden.value = checkbox.checked ? "true" : "false";
});
}
</script>
</body>