Skip to content

Commit

Permalink
notifier working, missing executor
Browse files Browse the repository at this point in the history
  • Loading branch information
gweebg committed Feb 2, 2024
1 parent c60dc19 commit 5b25329
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 65 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@

1. Database should store data relative to each time the address has changed.
2. Database records should contain:
- Datetime of change (unix time)
- Previous address (string)
- New address (string)
- Address version (v4 | v6, string)
- Source of change (ipify, myip, etc., string)
- Id (uint)
- Datetime of change (unix time)
- Previous address (string)
- New address (string)
- Address version (v4 | v6, string)
- Source of change (ipify, myip, etc., string)
- Id (uint)
3. Database should use sqlite3


### Application

1. Enable API
2. Normal
3. Daemon
8 changes: 3 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,10 @@ func main() {
db := database.GetDatabase()

err := db.AutoMigrate(&database.AddressEntry{})
utils.Check(err, "could not run AutoMigrate")
utils.Check(err, "could not run database AutoMigrate")

w := watcher.NewWatcher()

log.Printf("started watcher")
go w.Watch()

select {}
log.Printf("started watcher loop")
w.Watch()
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ require (
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@ golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
10 changes: 6 additions & 4 deletions internal/watcher/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import (
"github.com/gweebg/ipwatcher/internal/config"
)

func RequestAddress(version string) (string, error) {
func RequestAddress(version string) (string, string, error) {

conf := config.GetConfig()
sources := conf.Get("sources").([]config.Source)

address := ""
fromSource := ""

const forceSource = "service.force_source"
for _, source := range sources {
Expand Down Expand Up @@ -48,18 +49,19 @@ func RequestAddress(version string) (string, error) {
continue
}

fromSource = url
log.Printf("valid address from source '%v'\n", source.Name)
break
}

// if address is still empty after querying the urls then, user needs to try others
if address == "" {
return address, errors.New(
"none of the specified sources returned a valid address or 'force_source' name missmatch",
return address, "", errors.New(
"none of the specified sources returned a valid address or 'force_source' name mismatch",
)
}

return address, nil
return address, fromSource, nil
}

func parseResponse(response *http.Response, source config.Source) string {
Expand Down
186 changes: 186 additions & 0 deletions internal/watcher/notifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package watcher

import (
"context"
"fmt"
"github.com/gweebg/ipwatcher/internal/config"
"gopkg.in/gomail.v2"
"log"
"time"
)

// Recipient represents a email recipient defined as per the configuration file
type Recipient struct {
// Name of the recipient
Name string `mapstructure:"name"`
// Address is the email address of the recipient
Address string `mapstructure:"address"`
}

// Notifier allows for email mass notification
type Notifier struct {
// From is the address to send from, obtained from the configuration file at 'watcher.smtp.*'
From string
// Recipients is the slice containing the recipients information such as email and name
Recipients []Recipient

// emailDialer represents the *gomail.Dialer object responsible by sending the email messages
emailDialer *gomail.Dialer

// doneCh is a channel that indicates when the email set is sent
doneCh chan struct{}
}

func NewNotifier() *Notifier {

c := config.GetConfig()

dialer := gomail.NewDialer(
c.GetString("watcher.smtp.smtp_server"),
c.GetInt("watcher.smtp.smtp_port"),
c.GetString("watcher.smtp.username"),
c.GetString("watcher.smtp.password"),
)

var recipients []Recipient
err := c.UnmarshalKey("watcher.smtp.recipients", &recipients)
if err != nil {
log.Fatalf("invalid 'watcher.smtp.recipients' configuration: %v\n", err.Error())
}

return &Notifier{
From: c.GetString("watcher.smtp.from_address"),
Recipients: recipients,
emailDialer: dialer,
}
}

func (n *Notifier) NotifyMail(ctx context.Context) error {

s, err := n.emailDialer.Dial()
if err != nil {
return err
}

m := gomail.NewMessage()
for _, r := range n.Recipients {

ctx = context.WithValue(ctx, "name", r.Name)

m.SetHeader("From", n.From)
m.SetAddressHeader("To", r.Address, r.Name)
m.SetHeader("Subject", "Update on your public address!")
m.SetBody("text/html", generateMailBody(ctx))

if err := gomail.Send(s, m); err != nil {
log.Printf("could not send email to %q: %v", r.Address, err)
}
m.Reset()
}

err = s.Close()
if err != nil {
return err
}

return nil
}

type bodyGenerator func(context.Context) string

func generateMailBody(ctx context.Context) string {

patterns := map[string]bodyGenerator{
"on_change": generateOnChange,
"on_match": generateOnMatch,
"on_error": generateOnError,
}

event := ctx.Value("event").(string)
generator, _ := patterns[event]

return generator(ctx)
}

func generateOnChange(ctx context.Context) string {

name := ctx.Value("name").(string)

previousAddress := ctx.Value("previous_address").(string)
currentAddress := ctx.Value("current_address").(string)

timestamp := ctx.Value("timestamp").(time.Time)
source := ctx.Value("source").(string)

// todo: make email template dynamic by allowing its definition on the configuration file
return fmt.Sprintf(`<html>
<head>
<title>Watcher Report</title>
</head>
<body style="font-family: Arial, sans-serif;">
<div style="background-color: #f0f0f0; padding: 20px;">
<h1 style="color: #333;">Your Public IP Address Has Updated</h1>
<p style="font-size: 16px;">Hello <strong>%s</strong>, your public IP address has been changed. Here are the details:</p>
<ul style="font-size: 16px;">
<li><strong>Previous Address:</strong> %s</li>
<li><strong>Current Address:</strong> %s</li>
<li><strong>Updated at:</strong> %s</li>
<li><strong>Information Source:</strong> %s</li>
</ul>
</div>
</body>
</html>`,
name, previousAddress, currentAddress, timestamp.Format("2006-01-02 15:04:05"), source)

}

func generateOnMatch(ctx context.Context) string {

name := ctx.Value("name").(string)
timestamp := ctx.Value("timestamp").(time.Time)
source := ctx.Value("source").(string)

return fmt.Sprintf(`<html>
<head>
<title>Watcher Report</title>
</head>
<body style="font-family: Arial, sans-serif;">
<div style="background-color: #f0f0f0; padding: 20px;">
<h1 style="color: #333;">Your Public IP Address Has <strong>Not</strong> Changed</h1>
<p style="font-size: 16px;">Hello <strong>%s</strong>, your public IP address is still the same. Here are the details:</p>
<ul style="font-size: 16px;">
<li><strong>At:</strong> %s</li>
<li><strong>Information Source:</strong> %s</li>
</ul>
</div>
</body>
</html>`,
name, timestamp.Format("2006-01-02 15:04:05"), source)

}

func generateOnError(ctx context.Context) string {

name := ctx.Value("name").(string)

timestamp := ctx.Value("timestamp").(time.Time)
err := ctx.Value("error").(error)

return fmt.Sprintf(`<html>
<head>
<title>Watcher Report</title>
</head>
<body style="font-family: Arial, sans-serif;">
<div style="background-color: #f0f0f0; padding: 20px;">
<h1 style="color: #333;">Watcher Error</h1>
<p style="font-size: 16px;">Hello <strong>%s</strong>, an error occured while watching your address. Here are the details:</p>
<ul style="font-size: 16px;">
<li><strong>At:</strong> %s</li>
<li><strong>Error:</strong> %s</li>
</ul>
</div>
</body>
</html>`,
name, timestamp.Format("2006-01-02 15:04:05"), err.Error())

}
Loading

0 comments on commit 5b25329

Please sign in to comment.