Skip to content

Commit

Permalink
website: start implementing the /admin/songs page
Browse files Browse the repository at this point in the history
  • Loading branch information
Wessie committed Feb 26, 2024
1 parent 39afd82 commit 0c0dda7
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 6 deletions.
17 changes: 17 additions & 0 deletions templates/default/admin-songs.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{define "content"}}
<section class="section">
<div class="container pb-4">
<form action="/admin/songs" class="control is-flex" hx-boost="true" hx-push-url="true" hx-target="#songs-content" hx-select="#songs-content">
<input class="input" type="text" name="q" placeholder="Search" value="{{.Query}}" hx-get="/admin/songs" hx-target="#songs-content" hx-select="#songs-content" hx-trigger="keyup changed delay:500ms">
<button class="button is-info" type="submit">Search</button>
</form>
</div>
<div id="songs-content">
{{template "pagination" .Page}}
{{range .Forms}}
{{template "form_admin_songs" .}}
{{end}}
{{template "pagination" .Page}}
</div>
</section>
{{end}}
2 changes: 1 addition & 1 deletion templates/default/partials/admin-navbar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<a class="navbar-item" href="/admin">Home</a>
{{if .User.UserPermissions.Has "news"}}<a class="navbar-item" href="/admin/news">News</a>{{end}}
{{if .User.UserPermissions.Has "pending_view"}}<a class="navbar-item" href="/admin/pending">Pending</a>{{end}}
{{if .User.UserPermissions.Has "database_view"}}<a class="navbar-item" href="/admin/database">Song Database</a>{{end}}
{{if .User.UserPermissions.Has "database_view"}}<a class="navbar-item" href="/admin/songs">Song Database</a>{{end}}
{{if .User.UserPermissions.Has "admin"}}<a class="navbar-item" href="/admin/users">Users</a>{{end}}
<a class="navbar-item" href="/admin/profile">Profile</a>
</div>
Expand Down
102 changes: 102 additions & 0 deletions templates/default/partials/form_admin_songs.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{{define "form_admin_songs"}}
<div class="songs-entry notification {{if .Song.NeedReplacement}}is-warning{{end}}">
<form method="post" hx-post="/admin/songs" hx-swap="outerHTML swap:1s">
<input class="is-hidden" type="number" name="id" value="{{.Song.TrackID}}">
<div class="columns is-gapless">
<div class="column">
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">Artist</label>
</div>
<div class="field-body">
<input class="input" name="artist" type="text" value="{{.Song.Artist}}" {{if not .HasEdit}}disabled{{end}}>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">Title</label>
</div>
<div class="field-body">
<input class="input" name="title" type="text" value="{{.Song.Title}}" {{if not .HasEdit}}disabled{{end}}>
</div>
</div>
</div>
<div class="column">
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">Album</label>
</div>
<div class="field-body">
<input class="input" name="album" type="text" value="{{.Song.Album}}" {{if not .HasEdit}}disabled{{end}}>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">Tags</label>
</div>
<div class="field-body">
<input class="input" name="tags" type="text" value="{{.Song.Tags}}" {{if not .HasEdit}}disabled{{end}}>
</div>
</div>
</div>
</div>
<div class="columns is-desktop is-variable is-1">
<div class="column is-narrow">
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">ID:</label>
</div>
<div class="field-body">
<span class="mt-2">{{.Song.TrackID}}</span>
</div>
</div>
</div>
<div class="column">
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">Acceptor</label>
</div>
<div class="field-body">
<span class="mt-2">{{.Song.Acceptor}} ({{.Song.LastEditor}})</span>
</div>
</div>
</div>
<div class="column">
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">LP/LR</label>
</div>
<div class="field-body">
<span class="mt-2">{{.Song.LastPlayed | Since}} / {{.Song.LastRequested | Since}}</span>
</div>
</div>
</div>
<div class="column is-narrow">
<div class="field is-horizontal">
<div class="field-label">
<label class="label mt-2">Priority</label>
</div>
<div class="field-body">
<span class="mt-2">{{.Song.Priority}} ({{.Song.RequestCount}})</span>
</div>
</div>
</div>
<div class="column is-flex is-justify-content-end">
<div class="buttons">
<button class="button is-success">Download</button>
<button class="button is-info">Play</button>
{{if .HasEdit}}
<button class="button" type="submit" name="action" value="save">Save</button>
{{if .Song.NeedReplacement}}
<button class="button is-white" type="submit" name="action" value="replacement">Unmark Repl</button>
{{else}}
<button class="button is-warning" type="submit" name="action" value="replacement">Mark Repl</button>
{{end}}
{{end}}
{{if .HasDelete}}<button class="button is-danger" type="submit" name="action" value="delete">Delete</button>{{end}}
</div>
</div>
</div>
</form>
</div>
{{end}}
20 changes: 17 additions & 3 deletions website/admin/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func NewState(
cfg config.Config,
dp *daypass.Daypass,
storage radio.StorageService,
search radio.SearchService,
siteTmpl *templates.Site,
exec templates.Executor,
sessionManager *scs.SessionManager,
Expand All @@ -30,6 +31,7 @@ func NewState(
Config: cfg,
Daypass: dp,
Storage: storage,
Search: search,
Templates: siteTmpl,
TemplateExecutor: exec,
SessionManager: sessionManager,
Expand All @@ -43,6 +45,7 @@ type State struct {

Daypass *daypass.Daypass
Storage radio.StorageService
Search radio.SearchService
Templates *templates.Site
TemplateExecutor templates.Executor
SessionManager *scs.SessionManager
Expand All @@ -52,13 +55,24 @@ type State struct {

func Route(ctx context.Context, s State) func(chi.Router) {
return func(r chi.Router) {
// the login middleware will require atleast the active permission
r = r.With(s.Authentication.LoginMiddleware)
r.HandleFunc("/", s.GetHome)
r.Get("/profile", s.GetProfile)
r.Post("/profile", s.PostProfile)
r.Get("/pending", vmiddleware.RequirePermission(radio.PermPendingView, s.GetPending))
r.Post("/pending", vmiddleware.RequirePermission(radio.PermPendingEdit, s.PostPending))
r.Get("/pending-song/{SubmissionID:[0-9]+}", vmiddleware.RequirePermission(radio.PermPendingView, s.GetPendingSong))
r.Get("/pending",
vmiddleware.RequirePermission(radio.PermPendingView, s.GetPending))
r.Post("/pending",
vmiddleware.RequirePermission(radio.PermPendingEdit, s.PostPending))
r.Get("/pending-song/{SubmissionID:[0-9]+}",
vmiddleware.RequirePermission(radio.PermPendingView, s.GetPendingSong))
r.Get("/songs",
vmiddleware.RequirePermission(radio.PermDatabaseView, s.GetSongs))
r.Post("/songs",
vmiddleware.RequirePermission(radio.PermDatabaseEdit, s.PostSongs))
r.Delete("/songs",
vmiddleware.RequirePermission(radio.PermDatabaseDelete, s.DeleteSongs))

// debug handlers, might not be needed later
r.HandleFunc("/streamer/start", vmiddleware.RequirePermission(radio.PermAdmin, s.StartStreamer))
r.HandleFunc("/streamer/stop", vmiddleware.RequirePermission(radio.PermAdmin, s.StopStreamer))
Expand Down
97 changes: 97 additions & 0 deletions website/admin/songs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package admin

import (
"net/http"

radio "github.com/R-a-dio/valkyrie"
"github.com/R-a-dio/valkyrie/errors"
"github.com/R-a-dio/valkyrie/website/middleware"
"github.com/R-a-dio/valkyrie/website/shared"
"github.com/rs/zerolog/hlog"
)

const songsPageSize = 20

type SongsInput struct {
middleware.Input

Forms []SongsForm
Query string
Page *shared.Pagination
}

func (SongsInput) TemplateBundle() string {
return "admin-songs"
}

type SongsForm struct {
HasDelete bool
HasEdit bool
Song radio.Song
}

func (SongsForm) TemplateName() string {
return "form_admin_songs"
}

func (SongsForm) TemplateBundle() string {
return "admin-songs"
}

func NewSongsInput(s radio.SearchService, r *http.Request) (*SongsInput, error) {
const op errors.Op = "website/admin.NewSongInput"
ctx := r.Context()

page, offset, err := shared.PageAndOffset(r, songsPageSize)
if err != nil {
return nil, errors.E(op, err)
}

query := r.FormValue("q")
searchResult, err := s.Search(ctx, query, songsPageSize, offset)
if err != nil {
return nil, errors.E(op, err)
}

// generate the input we can so far, since we need some data from it
input := &SongsInput{
Input: middleware.InputFromContext(ctx),
Query: query,
Page: shared.NewPagination(
page, shared.PageCount(int64(searchResult.TotalHits), songsPageSize),
r.URL,
),
}

forms := make([]SongsForm, len(searchResult.Songs))
for i := range searchResult.Songs {
forms[i].Song = searchResult.Songs[i]
forms[i].HasDelete = input.User.UserPermissions.Has(radio.PermDatabaseDelete)
forms[i].HasEdit = input.User.UserPermissions.Has(radio.PermDatabaseEdit)
}

input.Forms = forms
return input, nil
}

func (s *State) GetSongs(w http.ResponseWriter, r *http.Request) {
input, err := NewSongsInput(s.Search, r)
if err != nil {
hlog.FromRequest(r).Error().Err(err).Msg("input creation failure")
return
}

err = s.TemplateExecutor.Execute(w, r, input)
if err != nil {
hlog.FromRequest(r).Error().Err(err).Msg("template failure")
return
}
}

func (s *State) PostSongs(w http.ResponseWriter, r *http.Request) {

}

func (s *State) DeleteSongs(w http.ResponseWriter, r *http.Request) {

}
1 change: 1 addition & 0 deletions website/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func Execute(ctx context.Context, cfg config.Config) error {
cfg,
dpass,
storage,
searchService,
siteTemplates,
executor,
sessionManager,
Expand Down
4 changes: 2 additions & 2 deletions website/public/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ func NewSearchInput(s radio.SearchService, rs radio.RequestStorage, r *http.Requ
query := r.FormValue("q")
searchResult, err := s.Search(ctx, query, searchPageSize, offset)
if err != nil {
return nil, err
return nil, errors.E(op, err)
}

// TODO(wessie): check if this is the right identifier
identifier := r.RemoteAddr
lastRequest, err := rs.LastRequest(identifier)
if err != nil {
return nil, err
return nil, errors.E(op, err)
}

cd, ok := radio.CalculateCooldown(requestDelay, lastRequest)
Expand Down
23 changes: 23 additions & 0 deletions website/shared/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package shared

import (
"html/template"
"net/http"
"net/url"
"strconv"

"github.com/R-a-dio/valkyrie/errors"
)

func PageCount(total, size int64) int64 {
Expand All @@ -14,6 +17,26 @@ func PageCount(total, size int64) int64 {
return full
}

func PageAndOffset(r *http.Request, pageSize int64) (int64, int64, error) {
var page int64 = 1
{
rawPage := r.FormValue("page")
if rawPage == "" {
return page, 0, nil
}
parsedPage, err := strconv.ParseInt(rawPage, 10, 0)
if err != nil {
return page, 0, errors.E(err, errors.InvalidForm)
}
page = parsedPage
}
var offset = (page - 1) * pageSize
if offset < 0 {
offset = 0
}
return page, offset, nil
}

func NewPagination(currentPage, totalPages int64, uri *url.URL) *Pagination {
return &Pagination{
Nr: currentPage,
Expand Down

0 comments on commit 0c0dda7

Please sign in to comment.