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) configRepo := config.NewRepository(dbCon)
configService := config.NewService(configRepo) configService := config.NewService(configRepo)
if err := configService.EnsureDefaults(); err != nil {
panic(fmt.Errorf("failed to ensure config defaults: %w", err))
}
fileRepo := file.NewRepository(dbCon) fileRepo := file.NewRepository(dbCon)
fileService := file.NewService(fileRepo, "./uploads") fileService := file.NewService(fileRepo, "./uploads")
fileHandler := file.NewHandler(fileService, configService) fileHandler := file.NewHandler(fileService, configService)

View File

@@ -1,13 +1,19 @@
package config package config
import "strconv"
const ( const (
KeyUploadMaxFileSizeBytes = "upload.max_file_size_bytes" KeyUploadMaxFileSizeBytes = "upload.max_file_size_bytes"
KeyUploadMultiMaxFiles = "upload.multi.max_files" KeyUploadMultiMaxFiles = "upload.multi.max_files"
KeyUploadMaxHours = "upload.max_hours" KeyUploadMaxHours = "upload.max_hours"
KeyRateLimitLoginPerMinute = "ratelimit.login.per_minute" KeyRateLimitLoginPerMinute = "ratelimit.login.per_minute"
KeyRateLimitApiPerMinute = "ratelimit.api.per_minute" KeyRateLimitApiPerMinute = "ratelimit.api.per_minute"
KeyRateLimitApiBurst = "ratelimit.api.burst" KeyRateLimitApiBurst = "ratelimit.api.burst"
KeyRateLimitLoginBurst = "ratelimit.login.burst" KeyRateLimitLoginBurst = "ratelimit.login.burst"
KeyUseNtfy = "use_ntfy"
KeyNtfyUrl = "ntfy.url"
KeyNtfyTopic = "ntfy.topic"
) )
// Defaults (used when DB does not have an override) // Defaults (used when DB does not have an override)
@@ -20,4 +26,27 @@ const (
DefaultRateLimitLoginBurst = 10 DefaultRateLimitLoginBurst = 10
DefaultRateLimitApiPerMinute = 60 DefaultRateLimitApiPerMinute = 60
DefaultRateLimitApiBurst = 30 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 package config
import "gorm.io/gorm" import (
"errors"
"gorm.io/gorm"
)
type Repository struct { type Repository struct {
db *gorm.DB db *gorm.DB
@@ -37,3 +41,15 @@ func (r *Repository) List() ([]ConfigEntry, error) {
} }
return entries, nil 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)} 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) { func (s *Service) List() ([]ConfigEntry, error) {
entries, err := s.repo.List() entries, err := s.repo.List()
if err != nil { if err != nil {

View File

@@ -20,6 +20,10 @@ type ConfigPageData struct {
RateLimitLoginBurst int RateLimitLoginBurst int
RateLimitApiPerMinute int RateLimitApiPerMinute int
RateLimitApiBurst int RateLimitApiBurst int
NtfyUse bool
NtfyUrl string
NtfyTopic string
} }
// ConfigPage renders a modular admin config screen. // ConfigPage renders a modular admin config screen.
@@ -28,14 +32,17 @@ func (h *Handler) ConfigPage(c *gin.Context) {
maxBytes := cfg.GetInt64Default(config.KeyUploadMaxFileSizeBytes, config.DefaultUploadMaxFileSizeBytes) maxBytes := cfg.GetInt64Default(config.KeyUploadMaxFileSizeBytes, config.DefaultUploadMaxFileSizeBytes)
data := ConfigPageData{ data := ConfigPageData{
Success: c.Query("saved") == "1", Success: c.Query("saved") == "1",
UploadMaxFileSizeMB: maxBytes / (1024 * 1024), UploadMaxFileSizeMB: maxBytes / (1024 * 1024),
UploadMultiMaxFiles: cfg.GetIntDefault(config.KeyUploadMultiMaxFiles, config.DefaultUploadMultiMaxFiles), UploadMultiMaxFiles: cfg.GetIntDefault(config.KeyUploadMultiMaxFiles, config.DefaultUploadMultiMaxFiles),
UploadMaxHours: cfg.GetIntDefault(config.KeyUploadMaxHours, config.DefaultUploadMaxHours), UploadMaxHours: cfg.GetIntDefault(config.KeyUploadMaxHours, config.DefaultUploadMaxHours),
RateLimitLoginPerMinute: cfg.GetIntDefault(config.KeyRateLimitLoginPerMinute, config.DefaultRateLimitLoginPerMinute), RateLimitLoginPerMinute: cfg.GetIntDefault(config.KeyRateLimitLoginPerMinute, config.DefaultRateLimitLoginPerMinute),
RateLimitLoginBurst: cfg.GetIntDefault(config.KeyRateLimitLoginBurst, config.DefaultRateLimitLoginBurst), RateLimitLoginBurst: cfg.GetIntDefault(config.KeyRateLimitLoginBurst, config.DefaultRateLimitLoginBurst),
RateLimitApiPerMinute: cfg.GetIntDefault(config.KeyRateLimitApiPerMinute, config.DefaultRateLimitApiPerMinute), RateLimitApiPerMinute: cfg.GetIntDefault(config.KeyRateLimitApiPerMinute, config.DefaultRateLimitApiPerMinute),
RateLimitApiBurst: cfg.GetIntDefault(config.KeyRateLimitApiBurst, config.DefaultRateLimitApiBurst), 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) c.HTML(http.StatusOK, "config.html", data)
@@ -113,6 +120,14 @@ func (h *Handler) ConfigSave(c *gin.Context) {
return 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. // Persist.
if err := cfg.SetString(config.KeyUploadMaxFileSizeBytes, strconv.FormatInt(maxMB*1024*1024, 10)); err != nil { if err := cfg.SetString(config.KeyUploadMaxFileSizeBytes, strconv.FormatInt(maxMB*1024*1024, 10)); err != nil {
h.renderConfigError(c, err.Error()) h.renderConfigError(c, err.Error())
@@ -132,16 +147,25 @@ func (h *Handler) ConfigSave(c *gin.Context) {
_ = cfg.SetString(config.KeyRateLimitApiPerMinute, strconv.Itoa(apiPerMin)) _ = cfg.SetString(config.KeyRateLimitApiPerMinute, strconv.Itoa(apiPerMin))
_ = cfg.SetString(config.KeyRateLimitApiBurst, strconv.Itoa(apiBurst)) _ = 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") c.Redirect(http.StatusFound, "/config?saved=1")
} }
func (h *Handler) renderConfigError(c *gin.Context, msg string) { func (h *Handler) renderConfigError(c *gin.Context, msg string) {
maxBytes := h.configService.GetInt64Default(config.KeyUploadMaxFileSizeBytes, config.DefaultUploadMaxFileSizeBytes) maxBytes := h.configService.GetInt64Default(config.KeyUploadMaxFileSizeBytes, config.DefaultUploadMaxFileSizeBytes)
data := ConfigPageData{ data := ConfigPageData{
Error: msg, Error: msg,
UploadMaxFileSizeMB: maxBytes / (1024 * 1024), UploadMaxFileSizeMB: maxBytes / (1024 * 1024),
UploadMultiMaxFiles: h.configService.GetIntDefault(config.KeyUploadMultiMaxFiles, config.DefaultUploadMultiMaxFiles), UploadMultiMaxFiles: h.configService.GetIntDefault(config.KeyUploadMultiMaxFiles, config.DefaultUploadMultiMaxFiles),
UploadMaxHours: h.configService.GetIntDefault(config.KeyUploadMaxHours, config.DefaultUploadMaxHours), UploadMaxHours: h.configService.GetIntDefault(config.KeyUploadMaxHours, config.DefaultUploadMaxHours),
RateLimitLoginPerMinute: h.configService.GetIntDefault(config.KeyRateLimitLoginPerMinute, config.DefaultRateLimitLoginPerMinute), RateLimitLoginPerMinute: h.configService.GetIntDefault(config.KeyRateLimitLoginPerMinute, config.DefaultRateLimitLoginPerMinute),
RateLimitLoginBurst: h.configService.GetIntDefault(config.KeyRateLimitLoginBurst, config.DefaultRateLimitLoginBurst), RateLimitLoginBurst: h.configService.GetIntDefault(config.KeyRateLimitLoginBurst, config.DefaultRateLimitLoginBurst),
RateLimitApiPerMinute: h.configService.GetIntDefault(config.KeyRateLimitApiPerMinute, config.DefaultRateLimitApiPerMinute), RateLimitApiPerMinute: h.configService.GetIntDefault(config.KeyRateLimitApiPerMinute, config.DefaultRateLimitApiPerMinute),

View File

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

View File

@@ -207,6 +207,26 @@
</div> </div>
</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 --> <!-- ACTIONS -->
<div class="flex justify-between items-center border-t-8 border-black pt-4"> <div class="flex justify-between items-center border-t-8 border-black pt-4">
<button type="submit">SAVE</button> <button type="submit">SAVE</button>
@@ -250,6 +270,16 @@
// Listen for changes // Listen for changes
document.addEventListener('input', updateConversions); 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> </script>
</body> </body>