Improve config file structure by combining multiple recipients into lists (breaking)

This commit is contained in:
ChaoticByte 2025-06-12 00:00:52 +02:00
parent ef24503214
commit 295ceec3de
No known key found for this signature in database
4 changed files with 67 additions and 46 deletions

View file

@ -34,27 +34,28 @@ Example:
```json ```json
{ {
"api_fetch_interval": 600, "api_fetch_interval": 600,
"datafile": "data.json",
"enabled_api_endpoints": [ "enabled_api_endpoints": [
"bay", "bay",
"bund" "bund"
], ],
"datafile": "data.json",
"loglevel": 2, "loglevel": 2,
"recipients": [ "lists": [
{ {
"address": "guenther@example.org", "name": "Example List",
"include": [ "recipients": ["someone@example.org"],
{"classification": "kritisch"}, "filter": [
{"title_contains": "jQuery"} {"classification": "hoch", "title_contains": "Microsoft"},
{"classification": "kritisch"}
] ]
} }
], ],
"smtp": { "smtp": {
"from": "from@example.org", "from": "user@localhost",
"host": "example.org", "host": "127.0.0.1",
"port": 587, "port": 587,
"user": "from@example.org", "user": "user@localhost",
"password": "SiEhAbEnMiChInSgEsIcHtGeFiLmTdAsDüRfEnSiEnIcHt" "password": "change me :)"
}, },
"template": { "template": {
"subject": "", "subject": "",

View file

@ -11,7 +11,7 @@ type Config struct {
EnabledApiEndpoints []string `json:"enabled_api_endpoints"` EnabledApiEndpoints []string `json:"enabled_api_endpoints"`
PersistentDataFilePath string `json:"datafile"` PersistentDataFilePath string `json:"datafile"`
LogLevel int `json:"loglevel"` LogLevel int `json:"loglevel"`
Recipients []Recipient `json:"recipients"` Lists *[]NotifyList `json:"lists"`
SmtpConfiguration SmtpSettings `json:"smtp"` SmtpConfiguration SmtpSettings `json:"smtp"`
Template MailTemplateConfig `json:"template"` Template MailTemplateConfig `json:"template"`
} }
@ -23,12 +23,16 @@ func NewConfig() Config {
EnabledApiEndpoints: []string{"bay", "bund"}, EnabledApiEndpoints: []string{"bay", "bund"},
PersistentDataFilePath: "data.json", PersistentDataFilePath: "data.json",
LogLevel: 2, LogLevel: 2,
Recipients: []Recipient{}, Lists: &[]NotifyList{
{ Name: "Example List",
Recipients: []string{},
Filter: []Filter{},},
},
SmtpConfiguration: SmtpSettings{ SmtpConfiguration: SmtpSettings{
From: "from@example.org", From: "user@localhost",
User: "from@example.org", User: "user@localhost",
Password: "SiEhAbEnMiChInSgEsIcHtGeFiLmTdAsDüRfEnSiEnIcHt", Password: "change me :)",
ServerHost: "example.org", ServerHost: "127.0.0.1",
ServerPort: 587}, ServerPort: 587},
Template: MailTemplateConfig{ Template: MailTemplateConfig{
SubjectTemplate: "", SubjectTemplate: "",
@ -39,18 +43,20 @@ func NewConfig() Config {
} }
func checkConfig(config Config) { func checkConfig(config Config) {
if len(config.Recipients) < 1 { if len(*config.Lists) < 1 {
logger.error("Configuration is incomplete") logger.error("Configuration is incomplete")
panic(errors.New("no recipients are configured")) panic(errors.New("no lists are configured"))
} }
for _, r := range config.Recipients { for _, l := range *config.Lists {
if !mailAddressIsValid(r.Address) { if len(l.Filter) < 1 {
logger.error("Configuration is incomplete")
panic(errors.New("list " + l.Name + " has no filter defined - at least [{'any': true/false}] should be configured"))
}
for _, r := range l.Recipients {
if !mailAddressIsValid(r) {
logger.error("Configuration includes invalid data") logger.error("Configuration includes invalid data")
panic(errors.New("'" + r.Address + "' is not a valid e-mail address")) panic(errors.New("'" + r + "' is not a valid e-mail address"))
} }
if len(r.Filters) < 1 {
logger.error("Configuration is incomplete")
panic(errors.New("recipient " + r.Address + " has no filter defined - at least {'any': true/false} should be configured"))
} }
} }
if !mailAddressIsValid(config.SmtpConfiguration.From) { if !mailAddressIsValid(config.SmtpConfiguration.From) {

21
mail.go
View file

@ -23,21 +23,22 @@ func (c MailContent) serializeValidMail(from string, to string) []byte {
// and I'm too lazy to encode ä into =E4 and so on // and I'm too lazy to encode ä into =E4 and so on
subjectEncoded := base64.StdEncoding.EncodeToString([]byte(c.Subject)) subjectEncoded := base64.StdEncoding.EncodeToString([]byte(c.Subject))
bodyEncoded := base64.StdEncoding.EncodeToString([]byte(c.Body)) bodyEncoded := base64.StdEncoding.EncodeToString([]byte(c.Body))
data := []byte(fmt.Sprintf( data := fmt.Appendf(nil,
"Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\nFrom: %v%vTo: %v%vSubject: =?utf-8?b?%v?=%v%v%v", "Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\nFrom: %v%vTo: %v%vSubject: =?utf-8?b?%v?=%v%v%v",
from, MAIL_LINE_SEP, from, MAIL_LINE_SEP,
to, MAIL_LINE_SEP, to, MAIL_LINE_SEP,
subjectEncoded, MAIL_LINE_SEP, subjectEncoded, MAIL_LINE_SEP,
MAIL_LINE_SEP, MAIL_LINE_SEP,
bodyEncoded)) bodyEncoded)
// done, I guess // done, I guess
return data return data
} }
type Recipient struct { type NotifyList struct {
Address string `json:"address"` Name string `json:"name"`
Recipients []string `json:"recipients"`
// Must be a configured filter id // Must be a configured filter id
Filters []Filter `json:"include"` Filter []Filter `json:"filter"`
} }
type SmtpSettings struct { type SmtpSettings struct {
@ -48,8 +49,8 @@ type SmtpSettings struct {
Password string `json:"password"` Password string `json:"password"`
} }
func (r Recipient) sendNotices(notices []WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error { func sendNotices(recipient string, notices []WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error {
logger.debug("Generating and sending mails to " + r.Address + " ...") logger.debug("Generating and sending mails for recipient " + recipient + " ...")
cacheHits := 0 cacheHits := 0
cacheMisses := 0 cacheMisses := 0
mails := [][]byte{} mails := [][]byte{}
@ -67,7 +68,7 @@ func (r Recipient) sendNotices(notices []WidNotice, template MailTemplate, auth
logger.error(err) logger.error(err)
} }
// serialize mail // serialize mail
data = mailContent.serializeValidMail(smtpConfig.From, r.Address) data = mailContent.serializeValidMail(smtpConfig.From, recipient)
// add to cache // add to cache
(*cache)[n.Uuid] = data (*cache)[n.Uuid] = data
} }
@ -77,11 +78,11 @@ func (r Recipient) sendNotices(notices []WidNotice, template MailTemplate, auth
err := sendMails( err := sendMails(
smtpConfig, smtpConfig,
auth, auth,
r.Address, recipient,
mails, mails,
) )
if err != nil { return err } if err != nil { return err }
logger.debug("Successfully sent all mails to " + r.Address) logger.debug("Successfully sent all mails to " + recipient)
return nil return nil
} }

35
main.go
View file

@ -120,22 +120,35 @@ func main() {
logger.debug(fmt.Sprintf("Got %v new notices", len(newNotices))) logger.debug(fmt.Sprintf("Got %v new notices", len(newNotices)))
if len(newNotices) > 0 { if len(newNotices) > 0 {
logger.info("Sending email notifications ...") logger.info("Sending email notifications ...")
// mail recipient : pointer to slice of wid notices to be sent
noticesToBeSent := map[string][]WidNotice{}
recipientsNotified := 0 recipientsNotified := 0
var err error var err error
for _, r := range config.Recipients { for _, l := range *config.Lists {
// Filter notices for this recipient // Filter notices for this list
filteredNotices := []WidNotice{} for _, f := range l.Filter {
for _, f := range r.Filters {
for _, n := range f.filter(newNotices) { for _, n := range f.filter(newNotices) {
if !noticeSliceContains(filteredNotices, n) { for _, r := range l.Recipients {
filteredNotices = append(filteredNotices, n) if !noticeSliceContains(noticesToBeSent[r], n) {
noticesToBeSent[r] = append(noticesToBeSent[r], n)
} }
} }
} }
slices.Reverse(filteredNotices) }
logger.debug(fmt.Sprintf("Including %v of %v notices for recipient %v", len(filteredNotices), len(newNotices), r.Address)) }
// Send notices for r, notices := range noticesToBeSent {
err = r.sendNotices(filteredNotices, mailTemplate, mailAuth, config.SmtpConfiguration, &cache) // sort by publish date
slices.SortFunc(notices, func(a WidNotice, b WidNotice) int {
if a.Published == b.Published {
return 0
} else if a.Published.After(b.Published) {
return 1
} else {
return -1
}
})
// send
err = sendNotices(r, notices, mailTemplate, mailAuth, config.SmtpConfiguration, &cache)
if err != nil { if err != nil {
logger.error(err) logger.error(err)
} else { } else {
@ -149,7 +162,7 @@ func main() {
persistent.data.(PersistentData).LastPublished[id] = t persistent.data.(PersistentData).LastPublished[id] = t
persistent.save() persistent.save()
} }
logger.info(fmt.Sprintf("Email notifications sent to %v of %v recipients", recipientsNotified, len(config.Recipients))) logger.info(fmt.Sprintf("Email notifications sent to %v of %v recipients", recipientsNotified, len(noticesToBeSent)))
} }
} }
dt := int(time.Now().UnixMilli() - t1) dt := int(time.Now().UnixMilli() - t1)