diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98e6ef6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..6d36b41 --- /dev/null +++ b/Justfile @@ -0,0 +1,2 @@ +run: + go run src/*.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..54e9558 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module beretta + +go 1.22.7 + +require github.com/mattn/go-sqlite3 v1.14.23 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..32531fa --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= +github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/src/db.go b/src/db.go new file mode 100644 index 0000000..dab5118 --- /dev/null +++ b/src/db.go @@ -0,0 +1,49 @@ +package main + +import ( + _ "embed" + _ "github.com/mattn/go-sqlite3" + "database/sql" +) + +//go:embed init.sql +var initSQL string + +type Db interface { + // CheckAndStore checks if the tag is already stored in the database and stores it if it's not. + CheckAndStore(repo, tag string) (bool, error) +} + +type SQLiteDb struct { + DB *sql.DB +} + +func (s SQLiteDb) CheckAndStore(repo, tag string) (bool, error) { + var count int + err := s.DB.QueryRow("SELECT COUNT(*) FROM tags WHERE repo = ? AND tag = ?", repo, tag).Scan(&count) + if err != nil { + return false, err + } + if count > 0 { + return false, nil + } + _, err = s.DB.Exec("INSERT INTO tags (repo, tag) VALUES (?, ?)", repo, tag) + if err != nil { + return false, err + } + return true, nil +} + +// NewSQLiteDb creates a new SQLite database connection. +// The database is created if it doesn't exist. +func NewSQLiteDb(file string) (SQLiteDb, error) { + db, err := sql.Open("sqlite3", file) + if err != nil { + return SQLiteDb{}, err + } + _, err = db.Exec(initSQL) + if err != nil { + return SQLiteDb{}, err + } + return SQLiteDb{DB: db}, nil +} diff --git a/src/init.sql b/src/init.sql new file mode 100644 index 0000000..1519c31 --- /dev/null +++ b/src/init.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + repo TEXT NOT NULL, + tag TEXT NOT NULL +); diff --git a/src/main.go b/src/main.go index c91ac47..296c72c 100644 --- a/src/main.go +++ b/src/main.go @@ -7,7 +7,17 @@ import ( func main() { // Load configuration - config := loadConfig("config.json") + config, err := loadConfig("config.json") + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + + // Create database + db, err := NewSQLiteDb("tags.db") + if err != nil { + log.Fatalf("Failed to create database: %v", err) + } + _ = db notifier := NtfyNotifier{Topic: config.NtfyTopic} @@ -22,9 +32,20 @@ func main() { if tag.Name == "" { log.Printf("No tags found for %s", repo.Repo) continue + _ = notifier } - if notifier.Notify(repo, tag.Name) { - log.Printf("Notified for %s/%s: %s", repo.Owner, repo.Repo, tag.Name) + + // Check and store tag + stored, err := db.CheckAndStore(repo.Repo, tag.Name) + if err != nil { + log.Printf("Failed to store tag for %s: %v", repo.Repo, err) + continue + } + if stored { + log.Printf("New tag stored for %s: %s", repo.Repo, tag.Name) + if notifier.Notify(repo, tag.Name) { + log.Printf("Notified for %s/%s: %s", repo.Owner, repo.Repo, tag.Name) + } } } time.Sleep(time.Duration(config.Interval) * time.Second) diff --git a/src/repo.go b/src/repo.go index 45ef804..a9d8c6c 100644 --- a/src/repo.go +++ b/src/repo.go @@ -19,7 +19,7 @@ type RepoConfig struct { func (r RepoConfig) GetLatestTag() (Tag, error) { url := fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", r.Owner, r.Repo) - fmt.Printf(url) + resp, err := http.Get(url) if err != nil { log.Printf("Failed to fetch releases for %s/%s: %v", r.Owner, r.Repo, err) diff --git a/src/types.go b/src/types.go index 53e5fe9..42fefe7 100644 --- a/src/types.go +++ b/src/types.go @@ -16,20 +16,25 @@ type Config struct { NtfyTopic string `json:"ntfy_topic"` } -func loadConfig(file string) Config { +func loadConfig(file string) (Config, error) { + var config Config fh, err := os.OpenFile(file, os.O_RDONLY, 0644) + if err != nil { log.Fatalf("Failed to open config file: %v", err) + return Config{}, err } data, err := io.ReadAll(fh) if err != nil { log.Fatalf("Failed to read config file: %v", err) + return Config{}, err } - var config Config if err := json.Unmarshal(data, &config); err != nil { log.Fatalf("Failed to parse config file: %v", err) + return Config{}, err } - return config + + return config, nil } type Notifier interface {