home to your local SPACEGIRL 💫 arimelody.space
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add artists/tracks pages; more components; css cleanup

+497 -353
+40 -8
admin/artisthttp.go
··· 12 12 13 13 func serveArtists(app *model.AppState) http.Handler { 14 14 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 + session := r.Context().Value("session").(*model.Session) 16 + 15 17 slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/artists")[1:], "/") 16 - id := slices[0] 17 - artist, err := controller.GetArtist(app.DB, id) 18 + artistID := slices[0] 19 + 20 + if len(artistID) > 0 { 21 + serveArtist(app, artistID).ServeHTTP(w, r) 22 + return 23 + } 24 + 25 + artists, err := controller.GetAllArtists(app.DB) 26 + if err != nil { 27 + fmt.Printf("WARN: Failed to fetch artists: %s\n", err) 28 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 29 + return 30 + } 31 + 32 + type ArtistsResponse struct { 33 + adminPageData 34 + Artists []*model.Artist 35 + } 36 + 37 + err = templates.ArtistsTemplate.Execute(w, ArtistsResponse{ 38 + adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 39 + Artists: artists, 40 + }) 41 + if err != nil { 42 + fmt.Printf("WARN: Failed to serve admin artists page: %s\n", err) 43 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 44 + } 45 + }) 46 + } 47 + 48 + func serveArtist(app *model.AppState, artistID string) http.Handler { 49 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 50 + session := r.Context().Value("session").(*model.Session) 51 + 52 + artist, err := controller.GetArtist(app.DB, artistID) 18 53 if err != nil { 19 54 if artist == nil { 20 55 http.NotFound(w, r) 21 56 return 22 57 } 23 - fmt.Printf("Error rendering admin artist page for %s: %s\n", id, err) 58 + fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err) 24 59 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 25 60 return 26 61 } 27 62 28 63 credits, err := controller.GetArtistCredits(app.DB, artist.ID, true) 29 64 if err != nil { 30 - fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 65 + fmt.Printf("WARN: Failed to serve admin artist page for %s: %s\n", artistID, err) 31 66 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 32 67 return 33 68 } ··· 37 72 Artist *model.Artist 38 73 Credits []*model.Credit 39 74 } 40 - 41 - session := r.Context().Value("session").(*model.Session) 42 75 43 76 err = templates.EditArtistTemplate.Execute(w, ArtistResponse{ 44 77 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, ··· 46 79 Credits: credits, 47 80 }) 48 81 if err != nil { 49 - fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 82 + fmt.Printf("WARN: Failed to serve admin artist page for %s: %s\n", artistID, err) 50 83 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 51 84 } 52 85 }) 53 86 } 54 -
+19 -1
admin/http.go
··· 53 53 54 54 mux.Handle("/releases", requireAccount(serveReleases(app))) 55 55 mux.Handle("/releases/", requireAccount(serveReleases(app))) 56 + mux.Handle("/artists", requireAccount(serveArtists(app))) 56 57 mux.Handle("/artists/", requireAccount(serveArtists(app))) 58 + mux.Handle("/tracks", requireAccount(serveTracks(app))) 57 59 mux.Handle("/tracks/", requireAccount(serveTracks(app))) 58 60 59 61 mux.Handle("/static/", staticHandler()) ··· 79 81 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 80 82 return 81 83 } 82 - releaseCount, err := controller.GetReleasesCount(app.DB, false) 84 + releaseCount, err := controller.GetReleaseCount(app.DB, false) 83 85 if err != nil { 84 86 fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %s\n", err) 85 87 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) ··· 89 91 artists, err := controller.GetAllArtists(app.DB) 90 92 if err != nil { 91 93 fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err) 94 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 95 + return 96 + } 97 + artistCount, err := controller.GetArtistCount(app.DB) 98 + if err != nil { 99 + fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %s\n", err) 92 100 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 93 101 return 94 102 } ··· 99 107 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 100 108 return 101 109 } 110 + trackCount, err := controller.GetTrackCount(app.DB) 111 + if err != nil { 112 + fmt.Fprintf(os.Stderr, "WARN: Failed to pull track count: %s\n", err) 113 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 114 + return 115 + } 102 116 103 117 type IndexData struct { 104 118 adminPageData 105 119 Releases []*model.Release 106 120 ReleaseCount int 107 121 Artists []*model.Artist 122 + ArtistCount int 108 123 Tracks []*model.Track 124 + TrackCount int 109 125 } 110 126 111 127 err = templates.IndexTemplate.Execute(w, IndexData{ ··· 113 129 Releases: releases, 114 130 ReleaseCount: releaseCount, 115 131 Artists: artists, 132 + ArtistCount: artistCount, 116 133 Tracks: tracks, 134 + TrackCount: trackCount, 117 135 }) 118 136 if err != nil { 119 137 fmt.Fprintf(os.Stderr, "WARN: Failed to render admin index: %s\n", err)
+22 -18
admin/releasehttp.go
··· 13 13 14 14 func serveReleases(app *model.AppState) http.Handler { 15 15 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 + session := r.Context().Value("session").(*model.Session) 17 + 16 18 slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/releases")[1:], "/") 17 19 releaseID := slices[0] 18 20 ··· 26 28 return 27 29 } 28 30 29 - session := r.Context().Value("session").(*model.Session) 30 - 31 31 type ReleasesData struct { 32 32 adminPageData 33 33 Releases []*model.Release ··· 35 35 36 36 releases, err := controller.GetAllReleases(app.DB, false, 0, true) 37 37 if err != nil { 38 - fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err) 38 + fmt.Fprintf(os.Stderr, "WARN: Failed to fetch releases: %s\n", err) 39 39 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 40 40 return 41 41 } ··· 48 48 Releases: releases, 49 49 }) 50 50 if err != nil { 51 - fmt.Fprintf(os.Stderr, "WARN: Failed to render releases page: %s\n", err) 51 + fmt.Fprintf(os.Stderr, "WARN: Failed to serve releases page: %s\n", err) 52 52 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 53 53 return 54 54 } ··· 65 65 http.NotFound(w, r) 66 66 return 67 67 } 68 - fmt.Printf("WARN: Failed to pull full release data for %s: %s\n", releaseID, err) 68 + fmt.Printf("WARN: Failed to fetch full release data for %s: %s\n", releaseID, err) 69 69 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 70 70 return 71 71 } ··· 103 103 Release *model.Release 104 104 } 105 105 106 + for i, track := range release.Tracks { 107 + track.Number = i + 1 108 + } 109 + 106 110 err = templates.EditReleaseTemplate.Execute(w, ReleaseResponse{ 107 111 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 108 112 Release: release, 109 113 }) 110 114 if err != nil { 111 - fmt.Printf("Error rendering admin release page for %s: %s\n", release.ID, err) 115 + fmt.Printf("WARN: Failed to serve admin release page for %s: %s\n", release.ID, err) 112 116 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 113 117 } 114 118 }) ··· 119 123 w.Header().Set("Content-Type", "text/html") 120 124 err := templates.EditCreditsTemplate.Execute(w, release) 121 125 if err != nil { 122 - fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err) 126 + fmt.Printf("WARN: Failed to serve edit credits component for %s: %s\n", release.ID, err) 123 127 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 124 128 } 125 129 }) ··· 129 133 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 130 134 artists, err := controller.GetArtistsNotOnRelease(app.DB, release.ID) 131 135 if err != nil { 132 - fmt.Printf("WARN: Failed to pull artists not on %s: %s\n", release.ID, err) 136 + fmt.Printf("WARN: Failed to fetch artists not on %s: %s\n", release.ID, err) 133 137 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 134 138 return 135 139 } ··· 145 149 Artists: artists, 146 150 }) 147 151 if err != nil { 148 - fmt.Printf("Error rendering add credits component for %s: %s\n", release.ID, err) 152 + fmt.Printf("WARN: Failed to serve add credits component for %s: %s\n", release.ID, err) 149 153 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 150 154 } 151 155 }) ··· 156 160 artistID := strings.Split(r.URL.Path, "/")[3] 157 161 artist, err := controller.GetArtist(app.DB, artistID) 158 162 if err != nil { 159 - fmt.Printf("WARN: Failed to pull artists %s: %s\n", artistID, err) 163 + fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err) 160 164 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 161 165 return 162 166 } ··· 168 172 w.Header().Set("Content-Type", "text/html") 169 173 err = templates.NewCreditTemplate.Execute(w, artist) 170 174 if err != nil { 171 - fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err) 175 + fmt.Printf("WARN: Failed to serve new credit component for %s: %s\n", artist.ID, err) 172 176 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 173 177 } 174 178 }) ··· 177 181 func serveEditLinks(release *model.Release) http.Handler { 178 182 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 179 183 w.Header().Set("Content-Type", "text/html") 180 - err := templates.EditCreditsTemplate.Execute(w, release) 184 + err := templates.EditLinksTemplate.Execute(w, release) 181 185 if err != nil { 182 - fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err) 186 + fmt.Printf("WARN: Failed to serve edit links component for %s: %s\n", release.ID, err) 183 187 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 184 188 } 185 189 }) ··· 199 203 Add: func(a, b int) int { return a + b }, 200 204 }) 201 205 if err != nil { 202 - fmt.Printf("Error rendering edit tracks component for %s: %s\n", release.ID, err) 206 + fmt.Printf("WARN: Failed to serve edit tracks component for %s: %s\n", release.ID, err) 203 207 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 204 208 } 205 209 }) ··· 209 213 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 210 214 tracks, err := controller.GetTracksNotOnRelease(app.DB, release.ID) 211 215 if err != nil { 212 - fmt.Printf("WARN: Failed to pull tracks not on %s: %s\n", release.ID, err) 216 + fmt.Printf("WARN: Failed to fetch tracks not on %s: %s\n", release.ID, err) 213 217 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 214 218 return 215 219 } ··· 225 229 Tracks: tracks, 226 230 }) 227 231 if err != nil { 228 - fmt.Printf("Error rendering add tracks component for %s: %s\n", release.ID, err) 232 + fmt.Printf("WARN: Failed to add tracks component for %s: %s\n", release.ID, err) 229 233 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 230 234 } 231 235 }) ··· 236 240 trackID := strings.Split(r.URL.Path, "/")[3] 237 241 track, err := controller.GetTrack(app.DB, trackID) 238 242 if err != nil { 239 - fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err) 243 + fmt.Printf("WARN: Failed to fetch track %s: %s\n", trackID, err) 240 244 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 241 245 return 242 246 } ··· 248 252 w.Header().Set("Content-Type", "text/html") 249 253 err = templates.NewTrackTemplate.Execute(w, track) 250 254 if err != nil { 251 - fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err) 255 + fmt.Printf("WARN: Failed to serve new track component for %s: %s\n", track.ID, err) 252 256 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 253 257 } 254 258 })
+17 -13
admin/static/admin.css
··· 95 95 } 96 96 97 97 header { 98 + display: flex; 99 + justify-content: space-between; 100 + align-items: center; 101 + } 102 + nav { 98 103 position: fixed; 104 + top: 0; 99 105 left: 0; 100 - height: 100vh; 106 + width: 180px; 107 + height: calc(100vh - 2em); 108 + margin: 0; 109 + padding: 1em 0; 101 110 display: flex; 102 111 flex-direction: column; 103 - width: 180px; 112 + justify-content: left; 113 + 104 114 background-color: var(--bg-1); 105 115 box-shadow: var(--shadow-md); 106 - 107 116 transition: background .1s ease-out, color .1s ease-out; 108 - } 109 - nav { 110 - height: 100%; 111 - margin: 1em 0; 112 - display: flex; 113 - flex-direction: column; 114 - justify-content: left; 117 + 115 118 user-select: none; 116 119 } 117 120 nav .icon { ··· 134 137 color: var(--fg-2); 135 138 line-height: 2em; 136 139 font-weight: 500; 137 - transition: color .1s, background-color .1s; 140 + transition: color .1s ease-out, background-color .1s ease-out; 138 141 } 139 142 .nav-item:hover { 140 - background: var(--bg-2); 143 + color: var(--bg-2); 144 + background-color: var(--fg-2); 141 145 text-decoration: none; 142 146 } 143 147 .nav-item.active { 144 148 border-left: 4px solid var(--fg-2); 145 149 } 146 150 .nav-item.active a { 147 - padding-left: calc(1em - 4px); 151 + padding-left: calc(1em - 3.5px); 148 152 } 149 153 nav a { 150 154 padding: .2em 1em;
+23
admin/static/artists.css
··· 1 + .artist { 2 + padding: .5em; 3 + 4 + color: var(--fg-3); 5 + background: var(--bg-2); 6 + box-shadow: var(--shadow-md); 7 + border-radius: 16px; 8 + text-align: center; 9 + 10 + cursor: pointer; 11 + transition: background .1s ease-out, color .1s ease-out; 12 + } 13 + 14 + .artist:hover { 15 + background: var(--bg-1); 16 + text-decoration: hover; 17 + } 18 + 19 + .artist .artist-avatar { 20 + width: 100%; 21 + object-fit: cover; 22 + border-radius: 8px; 23 + }
+7
admin/static/artists.js
··· 1 + import { hijackClickEvent } from "./admin.js"; 2 + 3 + document.addEventListener("readystatechange", () => { 4 + document.querySelectorAll(".artists-group .artist").forEach(el => { 5 + hijackClickEvent(el, el.querySelector("a.artist-name")) 6 + }); 7 + });
+5 -64
admin/static/edit-release.css
··· 133 133 dialog { 134 134 width: min(720px, calc(100% - 2em)); 135 135 padding: 2em; 136 - border: 1px solid #101010; 136 + border: none; 137 137 border-radius: 16px; 138 138 color: var(--fg-0); 139 - background: var(--bg-0); 139 + background-color: var(--bg-0); 140 + box-shadow: var(--shadow-lg); 141 + 142 + transition: color .1s ease-out, background-color .1s ease-out; 140 143 } 141 144 142 145 dialog header { ··· 431 434 } 432 435 #editlinks td input[type="text"]:focus { 433 436 outline: 1px solid #808080; 434 - } 435 - 436 - /* 437 - * RELEASE TRACKS 438 - */ 439 - 440 - #tracks .track { 441 - margin-bottom: 1em; 442 - padding: 1em; 443 - display: flex; 444 - flex-direction: column; 445 - gap: .5em; 446 - 447 - border-radius: 16px; 448 - background: var(--bg-2); 449 - box-shadow: var(--shadow-md); 450 - 451 - transition: background .1s ease-out, color .1s ease-out; 452 - } 453 - 454 - #tracks .track h3, 455 - #tracks .track p { 456 - margin: 0; 457 - } 458 - 459 - #tracks h2.track-title { 460 - margin: 0; 461 - display: flex; 462 - gap: .5em; 463 - } 464 - 465 - #tracks h2.track-title .track-number { 466 - opacity: .5; 467 - } 468 - 469 - #tracks a:hover { 470 - text-decoration: underline; 471 - } 472 - 473 - #tracks .track-album { 474 - margin-left: auto; 475 - font-style: italic; 476 - font-size: .75em; 477 - opacity: .5; 478 - } 479 - 480 - #tracks .track-album.empty { 481 - color: #ff2020; 482 - opacity: 1; 483 - } 484 - 485 - #tracks .track-description { 486 - font-style: italic; 487 - } 488 - 489 - #tracks .track-lyrics { 490 - max-height: 10em; 491 - overflow-y: scroll; 492 - } 493 - 494 - #tracks .track .empty { 495 - opacity: 0.75; 496 437 } 497 438 498 439 #edittracks ul {
-84
admin/static/index.css
··· 1 - @import url("/admin/static/release-list-item.css"); 2 - 3 - .artist { 4 - padding: .5em; 5 - 6 - color: var(--fg-3); 7 - background: var(--bg-2); 8 - box-shadow: var(--shadow-md); 9 - border-radius: 16px; 10 - text-align: center; 11 - 12 - cursor: pointer; 13 - transition: background .1s ease-out, color .1s ease-out; 14 - } 15 - 16 - .artist:hover { 17 - background: var(--bg-1); 18 - text-decoration: hover; 19 - } 20 - 21 - .artist-avatar { 22 - width: 100%; 23 - object-fit: cover; 24 - border-radius: 8px; 25 - } 26 - 27 - .track { 28 - margin-bottom: 1em; 29 - padding: 1em; 30 - display: flex; 31 - flex-direction: column; 32 - gap: .5em; 33 - 34 - border-radius: 8px; 35 - background: #f8f8f8f8; 36 - border: 1px solid #808080; 37 - 38 - transition: background .1s ease-out, color .1s ease-out; 39 - } 40 - 41 - .track p { 42 - margin: 0; 43 - } 44 - 45 - .card h2.track-title { 46 - margin: 0; 47 - display: flex; 48 - flex-direction: row; 49 - justify-content: space-between; 50 - } 51 - 52 - .track-id { 53 - width: fit-content; 54 - font-family: "Monaspace Argon", monospace; 55 - font-size: .8em; 56 - font-style: italic; 57 - line-height: 1em; 58 - user-select: all; 59 - } 60 - 61 - .track-album { 62 - margin-left: auto; 63 - font-style: italic; 64 - font-size: .75em; 65 - opacity: .5; 66 - } 67 - 68 - .track-album.empty { 69 - color: #ff2020; 70 - opacity: 1; 71 - } 72 - 73 - .track-description { 74 - font-style: italic; 75 - } 76 - 77 - .track-lyrics { 78 - max-height: 10em; 79 - overflow-y: scroll; 80 - } 81 - 82 - .track .empty { 83 - opacity: 0.75; 84 - }
-8
admin/static/index.js
··· 1 - import { hijackClickEvent } from "./admin.js"; 2 - 3 1 const newReleaseBtn = document.getElementById("create-release"); 4 2 const newArtistBtn = document.getElementById("create-artist"); 5 3 const newTrackBtn = document.getElementById("create-track"); ··· 74 72 console.error(err); 75 73 }); 76 74 }); 77 - 78 - document.addEventListener("readystatechange", () => { 79 - document.querySelectorAll("#artists .artist").forEach(el => { 80 - hijackClickEvent(el, el.querySelector("a.artist-name")) 81 - }); 82 - });
+7 -7
admin/static/release-list-item.css admin/static/releases.css
··· 17 17 margin: 0; 18 18 } 19 19 20 - .release-artwork { 20 + .release .release-artwork { 21 21 margin: auto 0; 22 22 width: 96px; 23 23 ··· 29 29 box-shadow: var(--shadow-sm); 30 30 } 31 31 32 - .release-artwork img { 32 + .release .release-artwork img { 33 33 width: 100%; 34 34 aspect-ratio: 1; 35 35 } 36 36 37 - .release-title small { 37 + .release .release-title small { 38 38 opacity: .75; 39 39 } 40 40 41 - .release-links { 41 + .release .release-links { 42 42 margin: .5em 0; 43 43 padding: 0; 44 44 display: flex; ··· 48 48 gap: .5em; 49 49 } 50 50 51 - .release-actions { 51 + .release .release-actions { 52 52 margin-top: .5em; 53 53 user-select: none; 54 54 color: var(--fg-3); 55 55 } 56 56 57 - .release-actions a { 57 + .release .release-actions a { 58 58 margin-right: .3em; 59 59 padding: .3em .5em; 60 60 display: inline-block; ··· 66 66 transition: color .1s ease-out, background .1s ease-out; 67 67 } 68 68 69 - .release-actions a:hover { 69 + .release .release-actions a:hover { 70 70 background: var(--bg-0); 71 71 color: var(--fg-3); 72 72 text-decoration: none;
+127
admin/static/tracks.css
··· 1 + #tracks h2.track-title { 2 + margin: 0; 3 + display: flex; 4 + gap: .5em; 5 + } 6 + 7 + #tracks .track { 8 + margin-bottom: 1em; 9 + padding: 1em; 10 + display: flex; 11 + flex-direction: column; 12 + gap: .5em; 13 + 14 + border-radius: 16px; 15 + background: var(--bg-2); 16 + box-shadow: var(--shadow-md); 17 + 18 + transition: background .1s ease-out, color .1s ease-out; 19 + } 20 + 21 + #tracks .track h3, 22 + #tracks .track p { 23 + margin: 0; 24 + } 25 + 26 + #tracks h2.track-title { 27 + margin: 0; 28 + display: flex; 29 + gap: .5em; 30 + } 31 + 32 + #tracks h2.track-title .track-number { 33 + opacity: .5; 34 + } 35 + 36 + #tracks a:hover { 37 + text-decoration: underline; 38 + } 39 + 40 + #tracks .track-album { 41 + margin-left: auto; 42 + font-style: italic; 43 + font-size: .75em; 44 + opacity: .5; 45 + } 46 + 47 + #tracks .track-album.empty { 48 + color: #ff2020; 49 + opacity: 1; 50 + } 51 + 52 + #tracks .track-description { 53 + font-style: italic; 54 + } 55 + 56 + #tracks .track-lyrics { 57 + max-height: 10em; 58 + overflow-y: scroll; 59 + } 60 + 61 + #tracks .track .empty { 62 + opacity: 0.75; 63 + } 64 + 65 + 66 + .card h2.track-title { 67 + margin: 0; 68 + display: flex; 69 + flex-direction: row; 70 + /* 71 + justify-content: space-between; 72 + */ 73 + } 74 + 75 + /* 76 + .track { 77 + margin-bottom: 1em; 78 + padding: 1em; 79 + display: flex; 80 + flex-direction: column; 81 + gap: .5em; 82 + 83 + border-radius: 8px; 84 + background-color: var(--bg-2); 85 + box-shadow: var(--shadow-md); 86 + 87 + transition: color .1s ease-out, background-color .1s ease-out; 88 + } 89 + 90 + .track p { 91 + margin: 0; 92 + } 93 + 94 + .track-id { 95 + width: fit-content; 96 + font-family: "Monaspace Argon", monospace; 97 + font-size: .8em; 98 + font-style: italic; 99 + line-height: 1em; 100 + user-select: all; 101 + } 102 + 103 + .track-album { 104 + margin-left: auto; 105 + font-style: italic; 106 + font-size: .75em; 107 + opacity: .5; 108 + } 109 + 110 + .track-album.empty { 111 + color: #ff2020; 112 + opacity: 1; 113 + } 114 + 115 + .track-description { 116 + font-style: italic; 117 + } 118 + 119 + .track-lyrics { 120 + max-height: 10em; 121 + overflow-y: scroll; 122 + } 123 + 124 + .track .empty { 125 + opacity: 0.75; 126 + } 127 + */
+9 -11
admin/templates/html/artists.html
··· 1 1 {{define "head"}} 2 2 <title>Artists - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon"> 4 - <link rel="stylesheet" href="/admin/static/index.css"> 4 + <link rel="stylesheet" href="/admin/static/admin.css"> 5 + <link rel="stylesheet" href="/admin/static/artists.css"> 5 6 {{end}} 6 7 7 8 {{define "content"}} 8 9 <main> 9 - <h1>Artists</h1> 10 - 11 - <div class="card-header"> 12 - <h2><a href="/admin/artists/">Artists</a></h2> 10 + <header> 11 + <h1><a href="/admin/artists/">Artists</a></h2> 13 12 <a class="button new" id="create-artist">Create New</a> 14 - </div> 13 + </header> 14 + 15 15 {{if .Artists}} 16 16 <div class="artists-group"> 17 - {{range $Artist := .Artists}} 18 - <div class="artist"> 19 - <img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar"> 20 - <a href="/admin/artists/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a> 21 - </div> 17 + {{range .Artists}} 18 + {{block "artist" .}}{{end}} 22 19 {{end}} 23 20 </div> 24 21 {{else}} ··· 27 24 </main> 28 25 29 26 <script type="module" src="/admin/static/admin.js"></script> 27 + <script type="module" src="/admin/static/artists.js"></script> 30 28 {{end}}
+6
admin/templates/html/components/artist/artist.html
··· 1 + {{define "artist"}} 2 + <div class="artist"> 3 + <img src="{{.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar"> 4 + <a href="/admin/artists/{{.ID}}" class="artist-name">{{.Name}}</a> 5 + </div> 6 + {{end}}
admin/templates/html/components/credits/addcredit.html admin/templates/html/components/credit/addcredit.html
admin/templates/html/components/credits/editcredits.html admin/templates/html/components/credit/editcredits.html
admin/templates/html/components/credits/newcredit.html admin/templates/html/components/credit/newcredit.html
admin/templates/html/components/links/editlinks.html admin/templates/html/components/link/editlinks.html
admin/templates/html/components/release/release-list-item.html admin/templates/html/components/release/release.html
+24
admin/templates/html/components/track/track.html
··· 1 + {{define "track"}} 2 + <div class="track" data-id="{{.ID}}"> 3 + <h2 class="track-title"> 4 + {{if .Number}} 5 + <span class="track-number">{{.Number}}</span> 6 + {{end}} 7 + <a href="/admin/tracks/{{.ID}}">{{.Title}}</a> 8 + </h2> 9 + 10 + <h3>Description</h3> 11 + {{if .Description}} 12 + <p class="track-description">{{.GetDescriptionHTML}}</p> 13 + {{else}} 14 + <p class="track-description empty">No description provided.</p> 15 + {{end}} 16 + 17 + <h3>Lyrics</h3> 18 + {{if .Lyrics}} 19 + <p class="track-lyrics">{{.GetLyricsHTML}}</p> 20 + {{else}} 21 + <p class="track-lyrics empty">There are no lyrics.</p> 22 + {{end}} 23 + </div> 24 + {{end}}
admin/templates/html/components/tracks/addtrack.html admin/templates/html/components/track/addtrack.html
admin/templates/html/components/tracks/edittracks.html admin/templates/html/components/track/edittracks.html
admin/templates/html/components/tracks/newtrack.html admin/templates/html/components/track/newtrack.html
+1
admin/templates/html/edit-artist.html
··· 2 2 <title>Editing {{.Artist.Name}} - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="{{.Artist.GetAvatar}}" type="image/x-icon"> 4 4 <link rel="stylesheet" href="/admin/static/edit-artist.css"> 5 + <link rel="stylesheet" href="/admin/static/artists.css"> 5 6 {{end}} 6 7 7 8 {{define "content"}}
+3 -20
admin/templates/html/edit-release.html
··· 2 2 <title>Editing {{.Release.Title}} - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="{{.Release.GetArtwork}}" type="image/x-icon"> 4 4 <link rel="stylesheet" href="/admin/static/edit-release.css"> 5 + <link rel="stylesheet" href="/admin/static/releases.css"> 6 + <link rel="stylesheet" href="/admin/static/tracks.css"> 5 7 {{end}} 6 8 7 9 {{define "content"}} ··· 154 156 >Edit</a> 155 157 </div> 156 158 {{range $i, $track := .Release.Tracks}} 157 - <div class="track" data-id="{{$track.ID}}"> 158 - <h2 class="track-title"> 159 - <span class="track-number">{{.Add $i 1}}</span> 160 - <a href="/admin/tracks/{{$track.ID}}">{{$track.Title}}</a> 161 - </h2> 162 - 163 - <h3>Description</h3> 164 - {{if $track.Description}} 165 - <p class="track-description">{{$track.GetDescriptionHTML}}</p> 166 - {{else}} 167 - <p class="track-description empty">No description provided.</p> 168 - {{end}} 169 - 170 - <h3>Lyrics</h3> 171 - {{if $track.Lyrics}} 172 - <p class="track-lyrics">{{$track.GetLyricsHTML}}</p> 173 - {{else}} 174 - <p class="track-lyrics empty">There are no lyrics.</p> 175 - {{end}} 176 - </div> 159 + {{block "track" .}}{{end}} 177 160 {{end}} 178 161 </div> 179 162
+2
admin/templates/html/edit-track.html
··· 2 2 <title>Editing Track - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon"> 4 4 <link rel="stylesheet" href="/admin/static/edit-track.css"> 5 + <link rel="stylesheet" href="/admin/static/tracks.css"> 6 + <link rel="stylesheet" href="/admin/static/releases.css"> 5 7 {{end}} 6 8 7 9 {{define "content"}}
+13 -28
admin/templates/html/index.html
··· 1 1 {{define "head"}} 2 2 <title>Admin - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon"> 4 - <link rel="stylesheet" href="/admin/static/index.css"> 4 + <link rel="stylesheet" href="/admin/static/admin.css"> 5 + <link rel="stylesheet" href="/admin/static/releases.css"> 6 + <link rel="stylesheet" href="/admin/static/artists.css"> 7 + <link rel="stylesheet" href="/admin/static/tracks.css"> 5 8 {{end}} 6 9 7 10 {{define "content"}} ··· 14 17 <h2><a href="/admin/releases/">Releases</a> <small>({{.ReleaseCount}} total)</small></h2> 15 18 <a class="button new" id="create-release">Create New</a> 16 19 </div> 20 + {{if .Artists}} 17 21 {{range .Releases}} 18 22 {{block "release" .}}{{end}} 19 23 {{end}} 20 - {{if not .Releases}} 24 + {{else}} 21 25 <p>There are no releases.</p> 22 26 {{end}} 23 27 </div> 24 28 25 29 <div class="card" id="artists"> 26 30 <div class="card-header"> 27 - <h2><a href="/admin/artists/">Artists</a></h2> 31 + <h2><a href="/admin/artists/">Artists</a> <small>({{.ArtistCount}} total)</small></h2> 28 32 <a class="button new" id="create-artist">Create New</a> 29 33 </div> 30 34 {{if .Artists}} 31 35 <div class="artists-group"> 32 - {{range $Artist := .Artists}} 33 - <div class="artist"> 34 - <img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar"> 35 - <a href="/admin/artists/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a> 36 - </div> 36 + {{range .Artists}} 37 + {{block "artist" .}}{{end}} 37 38 {{end}} 38 39 </div> 39 40 {{else}} ··· 43 44 44 45 <div class="card" id="tracks"> 45 46 <div class="card-header"> 46 - <h2><a href="/admin/tracks/">Tracks</a></h2> 47 + <h2><a href="/admin/tracks/">Tracks</a> <small>({{.TrackCount}} total)</small></h2> 47 48 <a class="button new" id="create-track">Create New</a> 48 49 </div> 49 50 <p><em>"Orphaned" tracks that have not yet been bound to a release.</em></p> 50 51 <br> 51 - {{range $Track := .Tracks}} 52 - <div class="track"> 53 - <h2 class="track-title"> 54 - <a href="/admin/tracks/{{$Track.ID}}">{{$Track.Title}}</a> 55 - </h2> 56 - {{if $Track.Description}} 57 - <p class="track-description">{{$Track.GetDescriptionHTML}}</p> 58 - {{else}} 59 - <p class="track-description empty">No description provided.</p> 60 - {{end}} 61 - {{if $Track.Lyrics}} 62 - <p class="track-lyrics">{{$Track.GetLyricsHTML}}</p> 63 - {{else}} 64 - <p class="track-lyrics empty">There are no lyrics.</p> 65 - {{end}} 66 - </div> 67 - {{end}} 68 - {{if not .Artists}} 69 - <p>There are no artists.</p> 52 + {{range .Tracks}} 53 + {{block "track" .}}{{end}} 70 54 {{end}} 71 55 </div> 72 56 </div> ··· 74 58 </main> 75 59 76 60 <script type="module" src="/admin/static/admin.js"></script> 61 + <script type="module" src="/admin/static/artists.js"></script> 77 62 <script type="module" src="/admin/static/index.js"></script> 78 63 {{end}}
+1 -1
admin/templates/html/logs.html
··· 55 55 </thead> 56 56 <tbody> 57 57 {{range .Logs}} 58 - <tr class="log {{lower (parseLevel .Level)}}"> 58 + <tr class="log {{toLower (parseLevel .Level)}}"> 59 59 <td class="log-time">{{prettyTime .CreatedAt}}</td> 60 60 <td class="log-level">{{parseLevel .Level}}</td> 61 61 <td class="log-type">{{titleCase .Type}}</td>
+11 -6
admin/templates/html/releases.html
··· 1 1 {{define "head"}} 2 2 <title>Releases - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon"> 4 - <link rel="stylesheet" href="/admin/static/index.css"> 4 + <link rel="stylesheet" href="/admin/static/admin.css"> 5 + <link rel="stylesheet" href="/admin/static/releases.css"> 5 6 {{end}} 6 7 7 8 {{define "content"}} 8 9 <main> 9 - <div class="card-header"> 10 + <header> 10 11 <h1><a href="/admin/releases/">Releases</a></h1> 11 12 <a class="button new" id="create-release">Create New</a> 13 + </header> 14 + 15 + {{if .Releases}} 16 + <div id="releases"> 17 + {{range .Releases}} 18 + {{block "release" .}}{{end}} 19 + {{end}} 12 20 </div> 13 - {{range .Releases}} 14 - {{block "release" .}}{{end}} 15 - {{end}} 16 - {{if not .Releases}} 21 + {{else}} 17 22 <p>There are no releases.</p> 18 23 {{end}} 19 24 </main>
+24 -27
admin/templates/html/tracks.html
··· 1 1 {{define "head"}} 2 2 <title>Releases - ari melody 💫</title> 3 3 <link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon"> 4 - <link rel="stylesheet" href="/admin/static/index.css"> 4 + <link rel="stylesheet" href="/admin/static/admin.css"> 5 + <link rel="stylesheet" href="/admin/static/tracks.css"> 5 6 {{end}} 6 7 7 8 {{define "content"}} 8 9 <main> 9 - <h1>Releases</h1> 10 - 11 - <div class="card-header"> 12 - <h2><a href="/admin/tracks/">Tracks</a></h2> 10 + <header> 11 + <h1><a href="/admin/tracks/">Tracks</a></h1> 13 12 <a class="button new" id="create-track">Create New</a> 13 + </header> 14 + 15 + <div id="tracks"> 16 + {{range $Track := .Tracks}} 17 + <div class="track"> 18 + <h2 class="track-title"> 19 + <a href="/admin/tracks/{{$Track.ID}}">{{$Track.Title}}</a> 20 + </h2> 21 + {{if $Track.Description}} 22 + <p class="track-description">{{$Track.GetDescriptionHTML}}</p> 23 + {{else}} 24 + <p class="track-description empty">No description provided.</p> 25 + {{end}} 26 + {{if $Track.Lyrics}} 27 + <p class="track-lyrics">{{$Track.GetLyricsHTML}}</p> 28 + {{else}} 29 + <p class="track-lyrics empty">There are no lyrics.</p> 30 + {{end}} 31 + </div> 32 + {{end}} 14 33 </div> 15 - <p><em>"Orphaned" tracks that have not yet been bound to a release.</em></p> 16 - <br> 17 - {{range $Track := .Tracks}} 18 - <div class="track"> 19 - <h2 class="track-title"> 20 - <a href="/admin/tracks/{{$Track.ID}}">{{$Track.Title}}</a> 21 - </h2> 22 - {{if $Track.Description}} 23 - <p class="track-description">{{$Track.GetDescriptionHTML}}</p> 24 - {{else}} 25 - <p class="track-description empty">No description provided.</p> 26 - {{end}} 27 - {{if $Track.Lyrics}} 28 - <p class="track-lyrics">{{$Track.GetLyricsHTML}}</p> 29 - {{else}} 30 - <p class="track-lyrics empty">There are no lyrics.</p> 31 - {{end}} 32 - </div> 33 - {{end}} 34 - {{if not .Artists}} 35 - <p>There are no artists.</p> 36 - {{end}} 37 34 </main> 38 35 39 36 <script type="module" src="/admin/static/admin.js"></script>
+80 -43
admin/templates/templates.go
··· 50 50 //go:embed "html/edit-track.html" 51 51 var editTrackHTML string 52 52 53 - //go:embed "html/components/credits/newcredit.html" 53 + //go:embed "html/components/credit/newcredit.html" 54 54 var componentNewCreditHTML string 55 - //go:embed "html/components/credits/addcredit.html" 55 + //go:embed "html/components/credit/addcredit.html" 56 56 var componentAddCreditHTML string 57 - //go:embed "html/components/credits/editcredits.html" 57 + //go:embed "html/components/credit/editcredits.html" 58 58 var componentEditCreditsHTML string 59 59 60 - //go:embed "html/components/links/editlinks.html" 60 + //go:embed "html/components/link/editlinks.html" 61 61 var componentEditLinksHTML string 62 62 63 - //go:embed "html/components/release/release-list-item.html" 64 - var componentReleaseListItemHTML string 63 + //go:embed "html/components/release/release.html" 64 + var componentReleaseHTML string 65 + //go:embed "html/components/artist/artist.html" 66 + var componentArtistHTML string 67 + //go:embed "html/components/track/track.html" 68 + var componentTrackHTML string 65 69 66 - //go:embed "html/components/tracks/newtrack.html" 70 + //go:embed "html/components/track/newtrack.html" 67 71 var componentNewTrackHTML string 68 - //go:embed "html/components/tracks/addtrack.html" 72 + //go:embed "html/components/track/addtrack.html" 69 73 var componentAddTrackHTML string 70 - //go:embed "html/components/tracks/edittracks.html" 74 + //go:embed "html/components/track/edittracks.html" 71 75 var componentEditTracksHTML string 72 76 73 77 var BaseTemplate = template.Must( 74 78 template.New("base").Funcs( 75 79 template.FuncMap{ 76 - "hasPrefix": func(s string, prefix string) bool { 77 - fmt.Printf("does \"%s\" start with \"%s\"?\n", s, prefix) 78 - return strings.HasPrefix(s, prefix) 79 - }, 80 + "hasPrefix": strings.HasPrefix, 80 81 }, 81 82 ).Parse(strings.Join([]string{ 82 83 layoutHTML, ··· 86 87 var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 87 88 strings.Join([]string{ 88 89 indexHTML, 89 - componentReleaseListItemHTML, 90 + componentReleaseHTML, 91 + componentArtistHTML, 92 + componentTrackHTML, 90 93 }, "\n"), 91 94 )) 92 95 96 + 97 + 93 98 var LoginTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginHTML)) 94 99 var LoginTOTPTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(loginTotpHTML)) 95 100 var RegisterTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(registerHTML)) ··· 98 103 var TOTPSetupTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpSetupHTML)) 99 104 var TOTPConfirmTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(totpConfirmHTML)) 100 105 106 + 107 + 101 108 var LogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Funcs(template.FuncMap{ 102 - "parseLevel": func(level log.LogLevel) string { 103 - switch level { 104 - case log.LEVEL_INFO: 105 - return "INFO" 106 - case log.LEVEL_WARN: 107 - return "WARN" 108 - } 109 - return fmt.Sprintf("%d?", level) 110 - }, 111 - "titleCase": func(logType string) string { 112 - runes := []rune(logType) 113 - for i, r := range runes { 114 - if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' { 115 - runes[i] = r + ('A' - 'a') 116 - } 117 - } 118 - return string(runes) 119 - }, 120 - "lower": func(str string) string { return strings.ToLower(str) }, 121 - "prettyTime": func(t time.Time) string { 122 - // return t.Format("2006-01-02 15:04:05") 123 - // return t.Format("15:04:05, 2 Jan 2006") 124 - return t.Format("02 Jan 2006, 15:04:05") 125 - }, 109 + "parseLevel": parseLevel, 110 + "titleCase": titleCase, 111 + "toLower": toLower, 112 + "prettyTime": prettyTime, 126 113 }).Parse(logsHTML)) 114 + 115 + 127 116 128 117 var ReleasesTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 129 118 strings.Join([]string{ 130 119 releasesHTML, 131 - componentReleaseListItemHTML, 120 + componentReleaseHTML, 121 + }, "\n"), 122 + )) 123 + var ArtistsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 124 + strings.Join([]string{ 125 + artistsHTML, 126 + componentArtistHTML, 127 + }, "\n"), 128 + )) 129 + var TracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 130 + strings.Join([]string{ 131 + tracksHTML, 132 + componentTrackHTML, 132 133 }, "\n"), 133 134 )) 134 - var ArtistsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(artistsHTML)) 135 - var TracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(tracksHTML)) 135 + 136 136 137 - var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editReleaseHTML)) 137 + 138 + var EditReleaseTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 139 + strings.Join([]string{ 140 + editReleaseHTML, 141 + componentTrackHTML, 142 + }, "\n"), 143 + )) 138 144 var EditArtistTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(editArtistHTML)) 139 145 var EditTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( 140 146 strings.Join([]string{ 141 147 editTrackHTML, 142 - componentReleaseListItemHTML, 148 + componentReleaseHTML, 143 149 }, "\n"), 144 150 )) 145 151 152 + 153 + 146 154 var EditCreditsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditCreditsHTML)) 147 155 var AddCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddCreditHTML)) 148 156 var NewCreditTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewCreditHTML)) ··· 152 160 var EditTracksTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentEditTracksHTML)) 153 161 var AddTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentAddTrackHTML)) 154 162 var NewTrackTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(componentNewTrackHTML)) 163 + 164 + 165 + 166 + func parseLevel(level log.LogLevel) string { 167 + switch level { 168 + case log.LEVEL_INFO: 169 + return "INFO" 170 + case log.LEVEL_WARN: 171 + return "WARN" 172 + } 173 + return fmt.Sprintf("%d?", level) 174 + } 175 + func titleCase(logType string) string { 176 + runes := []rune(logType) 177 + for i, r := range runes { 178 + if (i == 0 || runes[i - 1] == ' ') && r >= 'a' && r <= 'z' { 179 + runes[i] = r + ('A' - 'a') 180 + } 181 + } 182 + return string(runes) 183 + } 184 + func toLower(str string) string { 185 + return strings.ToLower(str) 186 + } 187 + func prettyTime(t time.Time) string { 188 + // return t.Format("2006-01-02 15:04:05") 189 + // return t.Format("15:04:05, 2 Jan 2006") 190 + return t.Format("02 Jan 2006, 15:04:05") 191 + }
+40 -8
admin/trackhttp.go
··· 12 12 13 13 func serveTracks(app *model.AppState) http.Handler { 14 14 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 + session := r.Context().Value("session").(*model.Session) 16 + 15 17 slices := strings.Split(strings.TrimPrefix(r.URL.Path, "/tracks")[1:], "/") 16 - id := slices[0] 17 - track, err := controller.GetTrack(app.DB, id) 18 + trackID := slices[0] 19 + 20 + if len(trackID) > 0 { 21 + serveTrack(app, trackID).ServeHTTP(w, r) 22 + return 23 + } 24 + 25 + tracks, err := controller.GetAllTracks(app.DB) 18 26 if err != nil { 19 - fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 27 + fmt.Printf("WARN: Failed to fetch tracks: %s\n", err) 28 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 29 + return 30 + } 31 + 32 + type TracksResponse struct { 33 + adminPageData 34 + Tracks []*model.Track 35 + } 36 + 37 + err = templates.TracksTemplate.Execute(w, TracksResponse{ 38 + adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, 39 + Tracks: tracks, 40 + }) 41 + if err != nil { 42 + fmt.Printf("WARN: Failed to serve admin tracks page: %s\n", err) 43 + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 44 + } 45 + }) 46 + } 47 + 48 + func serveTrack(app *model.AppState, trackID string) http.Handler { 49 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 50 + session := r.Context().Value("session").(*model.Session) 51 + 52 + track, err := controller.GetTrack(app.DB, trackID) 53 + if err != nil { 54 + fmt.Printf("WARN: Failed to serve admin track page for %s: %s\n", trackID, err) 20 55 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 21 56 return 22 57 } ··· 27 62 28 63 releases, err := controller.GetTrackReleases(app.DB, track.ID, true) 29 64 if err != nil { 30 - fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err) 65 + fmt.Printf("WARN: Failed to fetch releases for track %s: %s\n", trackID, err) 31 66 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 32 67 return 33 68 } ··· 37 72 Track *model.Track 38 73 Releases []*model.Release 39 74 } 40 - 41 - session := r.Context().Value("session").(*model.Session) 42 75 43 76 err = templates.EditTrackTemplate.Execute(w, TrackResponse{ 44 77 adminPageData: adminPageData{ Path: r.URL.Path, Session: session }, ··· 46 79 Releases: releases, 47 80 }) 48 81 if err != nil { 49 - fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) 82 + fmt.Printf("WARN: Failed to serve admin track page for %s: %s\n", trackID, err) 50 83 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 51 84 } 52 85 }) 53 86 } 54 -
+5
controller/artist.go
··· 29 29 30 30 return artists, nil 31 31 } 32 + func GetArtistCount(db *sqlx.DB) (int, error) { 33 + var count int 34 + err := db.Get(&count, "SELECT count(*) FROM artist") 35 + return count, err 36 + } 32 37 33 38 func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) { 34 39 var artists = []*model.Artist{}
+1 -1
controller/release.go
··· 99 99 100 100 return releases, nil 101 101 } 102 - func GetReleasesCount(db *sqlx.DB, onlyVisible bool) (int, error) { 102 + func GetReleaseCount(db *sqlx.DB, onlyVisible bool) (int, error) { 103 103 query := "SELECT count(*) FROM musicrelease" 104 104 if onlyVisible { 105 105 query += " WHERE visible=true"
+5
controller/track.go
··· 29 29 30 30 return tracks, nil 31 31 } 32 + func GetTrackCount(db *sqlx.DB) (int, error) { 33 + var count int 34 + err := db.Get(&count, "SELECT count(*) FROM musictrack") 35 + return count, err 36 + } 32 37 33 38 func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) { 34 39 var tracks = []*model.Track{}
+1 -1
model/release.go
··· 39 39 // GETTERS 40 40 41 41 func (release Release) GetDescriptionHTML() template.HTML { 42 - return template.HTML(strings.Replace(release.Description, "\n", "<br>", -1)) 42 + return template.HTML(strings.ReplaceAll(release.Description, "\n", "<br>")) 43 43 } 44 44 45 45 func (release Release) TextReleaseDate() string {
+3 -3
model/track.go
··· 13 13 Lyrics string `json:"lyrics" db:"lyrics"` 14 14 PreviewURL string `json:"previewURL" db:"preview_url"` 15 15 16 - Number int 16 + Number int `json:"-"` 17 17 } 18 18 ) 19 19 20 20 func (track Track) GetDescriptionHTML() template.HTML { 21 - return template.HTML(strings.Replace(track.Description, "\n", "<br>", -1)) 21 + return template.HTML(strings.ReplaceAll(track.Description, "\n", "<br>")) 22 22 } 23 23 24 24 func (track Track) GetLyricsHTML() template.HTML { 25 - return template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)) 25 + return template.HTML(strings.ReplaceAll(track.Lyrics, "\n", "<br>")) 26 26 } 27 27 28 28 // this function is stupid and i hate that i need it
+1 -1
public/style/music-gateway.css
··· 333 333 background-color: #fff; 334 334 text-align: center; 335 335 text-decoration: none; 336 - transition: filter .1s,-webkit-filter .1s 336 + transition: filter .1s ease-out, -webkit-filter .1s ease-out; 337 337 } 338 338 339 339 #buylink {