feat: Load albums and covers
This commit is contained in:
parent
abba2bfd03
commit
100d3e9d99
@ -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, "<div _=\"init js window.location.href = '/'\">Logged in, redirecting...</div>")
|
||||
|
31
src/modules/covers/covers.go
Normal file
31
src/modules/covers/covers.go
Normal file
@ -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)
|
||||
}
|
31
src/modules/covers/covers.service.go
Normal file
31
src/modules/covers/covers.service.go
Normal file
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
33
src/modules/index/index.service.go
Normal file
33
src/modules/index/index.service.go
Normal file
@ -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
|
||||
}
|
@ -1,11 +1,35 @@
|
||||
package index
|
||||
|
||||
import "acide/src/utils"
|
||||
import (
|
||||
"acide/src/utils"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
templ IndexTempl() {
|
||||
templ IndexTempl(albums []utils.Album) {
|
||||
@utils.SkeletonTempl() {
|
||||
<div>
|
||||
Home page :D
|
||||
<nav class="text-blue-500 font-bold text-xl py-2 border-b border-blue-500">
|
||||
music to go
|
||||
</nav>
|
||||
<div class="overflow-x-scroll whitespace-nowrap py-2">
|
||||
for _, album := range albums {
|
||||
@albumCard(album)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ albumCard(album utils.Album) {
|
||||
<div class="inline-block p-1 mx-1 rounded bg-zinc-200 w-32">
|
||||
<div class="h-30 relative">
|
||||
<img src={ fmt.Sprintf("/covers/%s", album.ID) }/>
|
||||
<div class="overflow-hidden overflow-ellipsis">
|
||||
{ album.Name }
|
||||
</div>
|
||||
<div class="overflow-hidden overflow-ellipsis text-sm">
|
||||
{ album.Artist }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -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("<div>Home page :D</div>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><nav class=\"text-blue-500 font-bold text-xl py-2 border-b border-blue-500\">music to go</nav><div class=\"overflow-x-scroll whitespace-nowrap py-2\">")
|
||||
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("</div></div>")
|
||||
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("<div class=\"inline-block p-1 mx-1 rounded bg-zinc-200 w-32\"><div class=\"h-30 relative\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/covers/%s", album.ID))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/modules/index/index.templ`, Line: 26, Col: 49}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"overflow-hidden overflow-ellipsis\">")
|
||||
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("</div><div class=\"overflow-hidden overflow-ellipsis text-sm\">")
|
||||
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("</div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user