diff --git a/go.mod b/go.mod index 13654a8..dbe2581 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( ) require ( + github.com/allegro/bigcache/v3 v3.0.2 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect ) diff --git a/go.sum b/go.sum index 8dbac26..c4d4f20 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= +github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= diff --git a/lyrics.go b/lyrics.go index bff928f..e5a9d7a 100644 --- a/lyrics.go +++ b/lyrics.go @@ -49,6 +49,11 @@ func (s *song) parse(doc *goquery.Document) { func lyricsHandler(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] + if data, err := getCache(id); err == nil { + render(w, data) + return + } + url := fmt.Sprintf("https://genius.com/%s-lyrics", id) resp, err := http.Get(url) if err != nil { @@ -78,4 +83,5 @@ func lyricsHandler(w http.ResponseWriter, r *http.Request) { } t.Execute(w, s) + setCache(id, s) } diff --git a/main.go b/main.go index 85db4c1..35f8155 100644 --- a/main.go +++ b/main.go @@ -2,32 +2,27 @@ package main import ( "fmt" - "log" "net" "net/http" "os" "strconv" "time" + "github.com/allegro/bigcache/v3" "github.com/gorilla/mux" ) -func fatal(err any) { - log.Fatalf("[ERR] %s\n", err) -} - -func info(s string) { - log.Printf("[INFO] %s\n", s) -} - -func write(w http.ResponseWriter, status int, data []byte) { - w.WriteHeader(status) - w.Write(data) -} - func main() { + c, err := bigcache.NewBigCache(bigcache.DefaultConfig(time.Hour * 2)) + if err != nil { + fatal("can't initialize caching") + } + cache = c + r := mux.NewRouter() + r.Use(securityHeaders) + r.HandleFunc("/{id}-lyrics", lyricsHandler) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) diff --git a/static/script.js b/static/script.js index 9749a75..ee507ae 100644 --- a/static/script.js +++ b/static/script.js @@ -1,4 +1,4 @@ -document.querySelectorAll("#lyrics > a").forEach(item => { +document.querySelectorAll("#lyrics a").forEach(item => { item.addEventListener("click", getAnnotation) }) diff --git a/static/style.css b/static/style.css index a57b963..98a50a4 100644 --- a/static/style.css +++ b/static/style.css @@ -45,10 +45,6 @@ body { } #lyrics { - position: absolute; - left: 50%; - transform: translateX(-50%); - padding: 1rem 0; color: #171717; line-height: 2.5rem; } @@ -58,6 +54,10 @@ body { color: inherit; } +#lyrics a:hover { + background-color: #ccc; +} + #nav { font-size: 2.5rem; text-align: center; @@ -76,24 +76,30 @@ a { #metadata { display: flex; flex-direction: column; - align-items: center; gap: 0.5rem; - padding: 1rem; + text-align: center; } #metadata h1 { - font-size: 2.2rem; + font-size: 2rem; color: #171717; } #metadata h2 { - font-size: 1.5rem; + font-size: 1.4rem; color: #1E1E1E; - text-transform: uppercase; font-weight: 500; } #metadata > img { width: 20rem; border-radius: 3px; + box-shadow: 0 1px 1px #ddd; +} + +#container { + display: flex; + padding: 2rem; + gap: 5rem; + justify-content: center; } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..eab3cbb --- /dev/null +++ b/utils.go @@ -0,0 +1,73 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + "path" + "text/template" + + "github.com/allegro/bigcache/v3" +) + +var cache *bigcache.BigCache + +func setCache(key string, entry any) error { + data, err := json.Marshal(&entry) + if err != nil { + return err + } + + return cache.Set(key, data) +} + +func getCache(key string) (any, error) { + data, err := cache.Get(key) + if err != nil { + return nil, err + } + + var decoded any + + if err = json.Unmarshal(data, &decoded); err != nil { + return nil, err + } + + return decoded, nil +} + +func fatal(err any) { + log.Fatalf("[ERR] %s\n", err) +} + +func info(s string) { + log.Printf("[INFO] %s\n", s) +} + +func write(w http.ResponseWriter, status int, data []byte) { + w.WriteHeader(status) + w.Write(data) +} + +func securityHeaders(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + csp := "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' images.genius.com; object-src 'none'" + w.Header().Add("content-security-policy", csp) + w.Header().Add("referrer-policy", "no-referrer") + w.Header().Add("x-content-type-options", "nosniff") + next.ServeHTTP(w, r) + }) +} + +func render(w http.ResponseWriter, data any) { + t, err := template.ParseFiles(path.Join("views/lyrics.tmpl")) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if err = t.Execute(w, data); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } +} diff --git a/views/lyrics.tmpl b/views/lyrics.tmpl index 8589e58..6dc3f6e 100644 --- a/views/lyrics.tmpl +++ b/views/lyrics.tmpl @@ -8,11 +8,13 @@