backend logic refactor :3 redo the file structure to be pretty internal/ and cmd/ move some types to db/ redo structure - i made it more go-idiomatic =^w^= - created a Repository type - made LoadMusic and GetSpending into methods not funcs - moved http HandleFuncs to anonymous funcs - added cmd to readme to do stuff funzies
Julian julian.sz.orlowski@gmail.com
Sat, 13 Sep 2025 12:20:02 +0200
8 files changed,
276 insertions(+),
136 deletions(-)
A
cmd/server/main.go
@@ -0,0 +1,46 @@
+package main + +import ( + "crispy-website/internal/db" + "crispy-website/internal/repo" + "fmt" + "log" + "net/http" + "text/template" +) + +func main() { + repository, err := repo.NewRepository("walletdrain.db") + if err != nil { + panic("failed to open db") + } + + fs := http.FileServer(http.Dir("static")) + + http.Handle("/static/", http.StripPrefix("/static/", fs)) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "templates/index.html") + }) + + http.HandleFunc("/music", func(w http.ResponseWriter, r *http.Request) { + results := repository.LoadMusic() + spending := repository.GetSpending() + tmpl := template.Must(template.ParseFiles("templates/music.html")) + + data := db.MusicData{Releases: results, Spending: spending} + + err := tmpl.Execute(w, data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) + + http.HandleFunc("/.well-known/discord", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("dh=b81129b4e9cc388c5ab63919550316fc3ca5ebe4")) + }) + + fmt.Println("Running on :8080") + log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil)) +}
D
index.go
@@ -1,29 +0,0 @@
-package main - -import ( - "log" - "net/http" -) - - -func viewHandler(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "templates/index.html") -} - -func plainTextHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.Write([]byte("dh=b81129b4e9cc388c5ab63919550316fc3ca5ebe4")) -} - -func main() { - fs := http.FileServer(http.Dir("static")) - http.Handle("/static/", http.StripPrefix("/static/", fs)) - - http.HandleFunc("/", viewHandler) - http.HandleFunc("/music", musicHandler) - - http.HandleFunc("/.well-known/discord", plainTextHandler) - - log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil)) -} -
A
internal/db/sql.go
@@ -0,0 +1,23 @@
+package db + +import ( + "database/sql" + + _ "github.com/mattn/go-sqlite3" +) + +type MusicData struct { + Releases []MusicItem + Spending float32 +} + +type MusicItem struct { + Id int + External_ids sql.NullString + Name string + Artist string + Price float64 + Seller sql.NullString + Note string + Purchase_date sql.NullString +}
A
internal/repo/sql.go
@@ -0,0 +1,65 @@
+package repo + +import ( + "crispy-website/internal/db" + "database/sql" + "log" + + _ "github.com/mattn/go-sqlite3" +) + +type Repository struct { + DB *sql.DB +} + +func NewRepository(dbName string) (*Repository, error) { + db, err := sql.Open("sqlite3", dbName) + if err != nil { + panic("couldn't open db") + } + + return &Repository{DB: db}, nil +} + +func (r *Repository) Close() error { + return r.DB.Close() +} + +func (r *Repository) LoadMusic() []db.MusicItem { + rows, err := r.DB.Query("SELECT * FROM music ORDER BY purchase_date DESC") + if err != nil { + panic(err) + } + defer rows.Close() + + var results []db.MusicItem + for rows.Next() { + var result db.MusicItem + err := rows.Scan( + &result.Id, + &result.External_ids, + &result.Name, + &result.Artist, + &result.Price, + &result.Seller, + &result.Note, + &result.Purchase_date) + if err != nil { + log.Print(err) + } + results = append(results, result) + } + return results +} + +func (r *Repository) GetSpending() (value float32) { + err := r.DB.QueryRow("SELECT ROUND(SUM(price), 2) FROM music").Scan(&value) + + if err == sql.ErrNoRows { + log.Println("No rows found") + } else if err != nil { + log.Fatal(err) + } + + return value +}
A
internal/test.go.test
@@ -0,0 +1,135 @@
+package main + +import ( + "database/sql" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +type musicItem struct { + id int + external_ids sql.NullString + Name string + Artist string + price float64 + seller sql.NullString + Note sql.NullString + purchase_date sql.NullString +} + +func main() { + // Connect to the SQLite database + db, err := sql.Open("sqlite3", "walletdrain.db") + if err != nil { + fmt.Printf("Error connecting to database: %v\n", err) + return + } + defer db.Close() + + // Query to get the first 10 music items + rows, err := db.Query("SELECT id, external_ids, Name, Artist, price, seller, Note, purchase_date FROM music WHERE artist='Glitch Cat'") + if err != nil { + fmt.Printf("Error querying database: %v\n", err) + return + } + defer rows.Close() + + // Create HTTP client and set user agent + client := &http.Client{} + ua := "MusicCoverArtFetcher/1.0 (your-email@example.com)" + + fmt.Println("ID\tARTIST\tRELEASE\tCOVER ART URL (250px)") + fmt.Println("-----------------------------------------------") + + // Process each row from the database + for rows.Next() { + var item musicItem + + if err := rows.Scan(&item.id, &item.external_ids, &item.Name, &item.Artist, + &item.price, &item.seller, &item.Note, &item.purchase_date); err != nil { + fmt.Printf("Error scanning row: %v\n", err) + continue + } + + // Search MusicBrainz for the release + query := url.QueryEscape(fmt.Sprintf("artist:\"%s\" AND release:\"%s\"", item.Artist, item.Name)) + req, _ := http.NewRequest("GET", "https://musicbrainz.org/ws/2/release?query="+query+"&fmt=json", nil) + req.Header.Set("User-Agent", ua) + + resp, err := client.Do(req) + if err != nil || resp.StatusCode != http.StatusOK { + fmt.Printf("%d\t%s\t%s\tError searching MusicBrainz\n", item.id, item.Artist, item.Name) + continue + } + + var mbResp struct { + Releases []struct { + ID string `json:"id"` + } `json:"releases"` + } + + body, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + + if err := json.Unmarshal(body, &mbResp); err != nil || len(mbResp.Releases) == 0 { + fmt.Printf("%d\t%s\t%s\tNo match found\n", item.id, item.Artist, item.Name) + continue + } + + // Get cover art using the MusicBrainz ID + req, _ = http.NewRequest("GET", "https://coverartarchive.org/release/"+mbResp.Releases[0].ID, nil) + req.Header.Set("User-Agent", ua) + + resp, err = client.Do(req) + if err != nil || resp.StatusCode != http.StatusOK { + fmt.Printf("%d\t%s\t%s\tNo cover art available\n", item.id, item.Artist, item.Name) + continue + } + + var caResp struct { + Images []struct { + Thumbnails struct { + Img250 string `json:"250"` + } `json:"thumbnails"` + Front bool `json:"front"` + } `json:"images"` + } + + body, _ = ioutil.ReadAll(resp.Body) + resp.Body.Close() + + if err := json.Unmarshal(body, &caResp); err != nil { + fmt.Printf("%d\t%s\t%s\tError parsing cover art data\n", item.id, item.Artist, item.Name) + continue + } + + // Find and display the front cover URL + coverURL := "" + for _, img := range caResp.Images { + if img.Front { + coverURL = img.Thumbnails.Img250 + break + } + } + + if coverURL == "" { + fmt.Printf("%d\t%s\t%s\tNo front cover found\n", item.id, item.Artist, item.Name) + } else { + fmt.Printf("%d\t%s\t%s\t%s\n", item.id, item.Artist, item.Name, coverURL) + } + + // Respect API rate limits + time.Sleep(1 * time.Second) + } + + // Check for errors from iterating over rows + if err = rows.Err(); err != nil { + fmt.Printf("Error iterating over rows: %v\n", err) + } +}
D
music.go
@@ -1,25 +0,0 @@
-package main - -import ( - "net/http" - "text/template" -) - -type musicData struct { - Releases []musicItem - Spending float32 -} - -func musicHandler(w http.ResponseWriter, r *http.Request) { - results := loadMusic() - spending := getSpending() - tmpl := template.Must(template.ParseFiles("templates/music.html")) - - data := musicData{Releases: results, Spending: spending} - - err := tmpl.Execute(w, data) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - -}
D
sql.go
@@ -1,82 +0,0 @@
-package main - -import ( - "database/sql" - "log" - - _ "github.com/mattn/go-sqlite3" -) - -type musicItem struct { - id int - external_ids sql.NullString - Name string - Artist string - price float64 - seller sql.NullString - Note string - purchase_date sql.NullString -} - -func openDB(DBName string) (*sql.DB, error) { - db, err := sql.Open("sqlite3", DBName+".db") - if err != nil { - panic("couldn't open db") - } - - return db, nil -} - -func loadMusic() []musicItem { - db, err := openDB("walletdrain") - if err != nil { - panic("couldn't open db") - } - defer db.Close() - - query := "SELECT * FROM music ORDER BY purchase_date DESC" - - rows, err := db.Query(query) - if err != nil { - panic(err) - } - defer rows.Close() - - var results []musicItem - for rows.Next() { - var result musicItem - err := rows.Scan( - &result.id, - &result.external_ids, - &result.Name, - &result.Artist, - &result.price, - &result.seller, - &result.Note, - &result.purchase_date) - if err != nil { - log.Print(err) - } - results = append(results, result) - } - return results -} - -func getSpending() (value float32) { - db, err := openDB("walletdrain") - if err != nil { - panic("couldn't open db") - } - defer db.Close() - - query := "SELECT ROUND(SUM(price), 2) FROM music" - - err = db.QueryRow(query).Scan(&value) - if err == sql.ErrNoRows { - log.Println("No rows found") - } else if err != nil { - log.Fatal(err) - } - - return value -}