diff --git a/src/modules/auth/login.go b/src/modules/auth/login.go
index e1651e2..e653c9d 100644
--- a/src/modules/auth/login.go
+++ b/src/modules/auth/login.go
@@ -11,6 +11,12 @@ import (
// Renders the loginPage form
func loginPage(c echo.Context) error {
+ // if the request has the required cookies, redirect to /
+ _, err := c.Cookie("session-token")
+ _, err2 := c.Cookie("navidrome-url")
+ if err == nil && err2 == nil {
+ return c.Redirect(http.StatusFound, "/")
+ }
return utils.RenderTempl(c, http.StatusOK, LoginTempl())
}
@@ -36,6 +42,7 @@ func loginFragment(c echo.Context) error {
cookie1.Path = "/"
cookie1.HttpOnly = true
cookie1.Secure = true
+ cookie1.SameSite = http.SameSiteStrictMode
c.SetCookie(cookie1)
cookie2 := new(http.Cookie)
@@ -45,6 +52,7 @@ func loginFragment(c echo.Context) error {
cookie2.Path = "/"
cookie2.HttpOnly = true
cookie2.Secure = true
+ cookie2.SameSite = http.SameSiteStrictMode
c.SetCookie(cookie2)
return c.HTML(http.StatusOK, "
Logged in, redirecting...
")
diff --git a/src/modules/covers/covers.go b/src/modules/covers/covers.go
new file mode 100644
index 0000000..5ff5610
--- /dev/null
+++ b/src/modules/covers/covers.go
@@ -0,0 +1,31 @@
+package covers
+
+import (
+ "acide/src/utils"
+ "log"
+ "net/http"
+
+ "github.com/labstack/echo"
+)
+
+func Setup(g *echo.Group) {
+ log.Print("Setting up the covers module")
+
+ g.Use(utils.Authed)
+
+ g.GET("/:id", getCover)
+}
+
+func getCover(c echo.Context) error {
+ token, server := utils.Credentials(c)
+ albumId := c.Param("id")
+
+ coverBytes, err := loadCover(token, server, albumId)
+ if err != nil {
+ return err
+ }
+
+ c.Response().Header().Set("Cache-Control", "max-age=604800")
+
+ return c.Blob(http.StatusOK, "image/png", coverBytes)
+}
diff --git a/src/modules/covers/covers.service.go b/src/modules/covers/covers.service.go
new file mode 100644
index 0000000..3d28187
--- /dev/null
+++ b/src/modules/covers/covers.service.go
@@ -0,0 +1,31 @@
+package covers
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/go-resty/resty/v2"
+)
+
+func loadCover(token, server, albumId string) ([]byte, error) {
+
+ response, err := resty.New().R().
+ SetHeader("x-nd-authorization", fmt.Sprintf("Bearer %s", token)).
+ Get(fmt.Sprintf(
+ "%s/rest/getCoverArt.view?id=%s&u=%s&s=12e7f3&t=%s&v=1.13.0&c=wmusic&size=300",
+ server,
+ albumId,
+ "fernando",
+ "d7bbe92d7da363aa202ae16136887adc",
+ ))
+
+ if err != nil {
+ return nil, err
+ }
+
+ if !response.IsSuccess() {
+ return nil, errors.New("Error fetching image from server")
+ }
+
+ return response.Body(), nil
+}
diff --git a/src/modules/index/index.go b/src/modules/index/index.go
index 2534e8f..918cf5f 100644
--- a/src/modules/index/index.go
+++ b/src/modules/index/index.go
@@ -2,28 +2,28 @@ package index
import (
"acide/src/utils"
+ "fmt"
"log"
"net/http"
"github.com/labstack/echo"
)
-// Registers all the routes that this module (auth) provides
func SetupRoutes(g *echo.Group) {
log.Print("Setting up the index module")
+ g.Use(utils.Authed)
+
// To include custom rendering logic:
g.GET("/", indexPage)
}
func indexPage(c echo.Context) error {
- // If the required cookies are set, redirect to home
- _, err1 := c.Cookie("session-token")
- _, err2 := c.Cookie("navidrome-url")
-
- if err1 != nil || err2 != nil {
- return c.Redirect(http.StatusFound, "/auth/")
+ sessionToken, navidromeUrl := utils.Credentials(c)
+ albums, err := getRandomAlbums(sessionToken, navidromeUrl, 10)
+ if err != nil {
+ return c.HTML(http.StatusBadRequest, fmt.Sprintf("%s", err))
}
- return utils.RenderTempl(c, http.StatusOK, IndexTempl())
+ return utils.RenderTempl(c, http.StatusOK, IndexTempl(albums))
}
diff --git a/src/modules/index/index.service.go b/src/modules/index/index.service.go
new file mode 100644
index 0000000..add34e7
--- /dev/null
+++ b/src/modules/index/index.service.go
@@ -0,0 +1,33 @@
+package index
+
+import (
+ "acide/src/utils"
+ "errors"
+ "fmt"
+
+ "github.com/go-resty/resty/v2"
+)
+
+// Gets `amount` random albums from the server
+func getRandomAlbums(token, server string, amount int) ([]utils.Album, error) {
+ var albums []utils.Album
+ var error utils.NavError
+
+ client := resty.New()
+
+ response, err := client.R().
+ SetHeader("x-nd-authorization", fmt.Sprintf("Bearer %s", token)).
+ SetResult(&albums).
+ SetError(&error).
+ Get(fmt.Sprintf("%s/api/album?_end=%d&_order=DESC&_sort=random&_start=0", server, amount))
+
+ if err != nil {
+ return nil, err
+ }
+
+ if !response.IsSuccess() {
+ return nil, errors.New(fmt.Sprintf("Error getting albums: %s", error.Error))
+ }
+
+ return albums, nil
+}
diff --git a/src/modules/index/index.templ b/src/modules/index/index.templ
index bdad796..709fcf6 100644
--- a/src/modules/index/index.templ
+++ b/src/modules/index/index.templ
@@ -1,11 +1,35 @@
package index
-import "acide/src/utils"
+import (
+ "acide/src/utils"
+ "fmt"
+)
-templ IndexTempl() {
+templ IndexTempl(albums []utils.Album) {
@utils.SkeletonTempl() {
- Home page :D
+
+
+ for _, album := range albums {
+ @albumCard(album)
+ }
+
}
}
+
+templ albumCard(album utils.Album) {
+
+
+
+
+ { album.Name }
+
+
+ { album.Artist }
+
+
+
+}
diff --git a/src/modules/index/index_templ.go b/src/modules/index/index_templ.go
index 1536b36..fbf98e5 100644
--- a/src/modules/index/index_templ.go
+++ b/src/modules/index/index_templ.go
@@ -8,9 +8,12 @@ package index
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
-import "acide/src/utils"
+import (
+ "acide/src/utils"
+ "fmt"
+)
-func IndexTempl() templ.Component {
+func IndexTempl(albums []utils.Album) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -43,7 +46,17 @@ func IndexTempl() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Home page :D
")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, album := range albums {
+ templ_7745c5c3_Err = albumCard(album).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -57,4 +70,72 @@ func IndexTempl() templ.Component {
})
}
+func albumCard(album utils.Album) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(album.Name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/modules/index/index.templ`, Line: 28, Col: 16}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(album.Artist)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/modules/index/index.templ`, Line: 31, Col: 18}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
var _ = templruntime.GeneratedTemplate
diff --git a/src/routes.go b/src/routes.go
index 859dbbd..50b3bab 100644
--- a/src/routes.go
+++ b/src/routes.go
@@ -2,6 +2,7 @@ package src
import (
"acide/src/modules/auth"
+ "acide/src/modules/covers"
"acide/src/modules/index"
"net/http"
"os"
@@ -27,6 +28,7 @@ func (s *Server) RegisterRoutes() http.Handler {
// NOTE: Register subroutes here
index.SetupRoutes(e.Group(""))
auth.SetupRoutes(e.Group("/auth"))
+ covers.Setup(e.Group("/covers"))
return e
}
diff --git a/src/utils/types.go b/src/utils/types.go
index b0939a5..f0bafc8 100644
--- a/src/utils/types.go
+++ b/src/utils/types.go
@@ -1,5 +1,7 @@
package utils
+import "time"
+
// The result of a Login
type AuthSuccess struct {
Id string `json:"id"`
@@ -10,3 +12,47 @@ type AuthSuccess struct {
Token string `json:"token"`
Username string `json:"username"`
}
+
+// An album as defined by the server
+type Album struct {
+ PlayCount int `json:"playCount"`
+ PlayDate time.Time `json:"playDate"`
+ Rating int `json:"rating"`
+ Starred bool `json:"starred"`
+ StarredAt time.Time `json:"starredAt"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ EmbedArtPath string `json:"embedArtPath"`
+ ArtistID string `json:"artistId"`
+ Artist string `json:"artist"`
+ AlbumArtistID string `json:"albumArtistId"`
+ AlbumArtist string `json:"albumArtist"`
+ AllArtistIds string `json:"allArtistIds"`
+ MaxYear int `json:"maxYear"`
+ MinYear int `json:"minYear"`
+ Compilation bool `json:"compilation"`
+ SongCount int `json:"songCount"`
+ Duration float64 `json:"duration"`
+ Size int `json:"size"`
+ Genre string `json:"genre"`
+ Genres []struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ } `json:"genres"`
+ FullText string `json:"fullText"`
+ OrderAlbumName string `json:"orderAlbumName"`
+ OrderAlbumArtistName string `json:"orderAlbumArtistName"`
+ ImageFiles string `json:"imageFiles"`
+ Paths string `json:"paths"`
+ SmallImageURL string `json:"smallImageUrl"`
+ MediumImageURL string `json:"mediumImageUrl"`
+ LargeImageURL string `json:"largeImageUrl"`
+ ExternalURL string `json:"externalUrl"`
+ ExternalInfoUpdatedAt time.Time `json:"externalInfoUpdatedAt"`
+ CreatedAt time.Time `json:"createdAt"`
+ UpdatedAt time.Time `json:"updatedAt"`
+}
+
+type NavError struct {
+ Error string `json:"error"`
+}
diff --git a/src/utils/utils.go b/src/utils/utils.go
index 3922fb0..b5ad2c2 100644
--- a/src/utils/utils.go
+++ b/src/utils/utils.go
@@ -4,11 +4,34 @@ import (
"bytes"
"errors"
"log"
+ "net/http"
"github.com/a-h/templ"
"github.com/labstack/echo"
)
+// Middleware that allows only requests with the `session-token` and `navidrome-url` cookies set
+func Authed(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ _, err := c.Cookie("session-token")
+ if err != nil {
+ c.Redirect(http.StatusFound, "/auth/")
+ return nil
+ }
+ _, err = c.Cookie("navidrome-url")
+ if err != nil {
+ c.Redirect(http.StatusFound, "/auth/")
+ return nil
+ }
+
+ if err := next(c); err != nil {
+ c.Error(err)
+ }
+
+ return nil
+ }
+}
+
// Renders a template and sends it with a custom http status code
func RenderTempl(c echo.Context, status int, cmp templ.Component) error {
var buff bytes.Buffer
@@ -19,3 +42,20 @@ func RenderTempl(c echo.Context, status int, cmp templ.Component) error {
return c.HTML(status, buff.String())
}
+
+// Returns the `sessionToken` and `navidromeUrl` cookies.
+// This function must be called by a route protected by the Auth
+// middleware, otherwise it will panic
+func Credentials(c echo.Context) (string, string) {
+ sessionToken, err := c.Cookie("session-token")
+ if err != nil {
+ panic("Error getting credentials from cookie: session-token was not set")
+ }
+
+ navidromeUrl, err := c.Cookie("navidrome-url")
+ if err != nil {
+ panic("Error getting credentials from cookie: navidrome-url was not set")
+ }
+
+ return sessionToken.Value, navidromeUrl.Value
+}