my god...it's finally done
This commit is contained in:
parent
2baf71214e
commit
19d76ebc47
43 changed files with 1008 additions and 550 deletions
|
@ -3,9 +3,10 @@ package admin
|
|||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"arimelody.me/arimelody.me/global"
|
||||
"arimelody-web/global"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -28,9 +29,9 @@ var ADMIN_BYPASS = func() bool {
|
|||
}()
|
||||
|
||||
var ADMIN_ID_DISCORD = func() string {
|
||||
id := global.Args["discordAdmin"]
|
||||
id := os.Getenv("DISCORD_ADMIN")
|
||||
if id == "" {
|
||||
fmt.Printf("WARN: Discord admin ID (-discordAdmin) was not provided. Admin login will be unavailable.\n")
|
||||
fmt.Printf("WARN: Discord admin ID (DISCORD_ADMIN) was not provided. Admin login will be unavailable.\n")
|
||||
}
|
||||
return id
|
||||
}()
|
||||
|
|
47
admin/artisthttp.go
Normal file
47
admin/artisthttp.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"arimelody-web/global"
|
||||
"arimelody-web/music/model"
|
||||
"arimelody-web/music/controller"
|
||||
)
|
||||
|
||||
func serveArtist() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
slices := strings.Split(r.URL.Path[1:], "/")
|
||||
id := slices[0]
|
||||
artist, err := music.GetArtist(global.DB, id)
|
||||
if err != nil {
|
||||
if artist == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Error rendering admin artist page for %s: %s\n", id, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
credits, err := music.GetArtistCredits(global.DB, artist.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
*model.Artist
|
||||
Credits []*model.Credit
|
||||
}
|
||||
|
||||
err = pages["artist"].Execute(w, Artist{ Artist: artist, Credits: credits })
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -61,6 +61,7 @@
|
|||
el.remove();
|
||||
});
|
||||
|
||||
el.draggable = true;
|
||||
el.addEventListener("dragstart", () => { el.classList.add("moving") });
|
||||
el.addEventListener("dragend", () => { el.classList.remove("moving") });
|
||||
}
|
||||
|
|
|
@ -3,17 +3,16 @@ package admin
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"arimelody.me/arimelody.me/discord"
|
||||
"arimelody.me/arimelody.me/global"
|
||||
musicModel "arimelody.me/arimelody.me/music/model"
|
||||
musicDB "arimelody.me/arimelody.me/music/controller"
|
||||
"arimelody-web/discord"
|
||||
"arimelody-web/global"
|
||||
musicDB "arimelody-web/music/controller"
|
||||
musicModel "arimelody-web/music/model"
|
||||
)
|
||||
|
||||
type loginData struct {
|
||||
|
@ -28,6 +27,7 @@ func Handler() http.Handler {
|
|||
mux.Handle("/logout", MustAuthorise(LogoutHandler()))
|
||||
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||
mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease())))
|
||||
mux.Handle("/artist/", MustAuthorise(http.StripPrefix("/artist", serveArtist())))
|
||||
mux.Handle("/track/", MustAuthorise(http.StripPrefix("/track", serveTrack())))
|
||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
|
@ -41,30 +41,12 @@ func Handler() http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
type (
|
||||
IndexData struct {
|
||||
Releases []musicModel.FullRelease
|
||||
Artists []*musicModel.Artist
|
||||
Tracks []musicModel.DisplayTrack
|
||||
}
|
||||
)
|
||||
|
||||
dbReleases, err := musicDB.GetAllReleases(global.DB)
|
||||
releases, err := musicDB.GetAllReleases(global.DB, false, 0, true)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull releases: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
releases := []musicModel.FullRelease{}
|
||||
for _, release := range dbReleases {
|
||||
fullRelease, err := musicDB.GetFullRelease(global.DB, release.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
releases = append(releases, *fullRelease)
|
||||
}
|
||||
|
||||
artists, err := musicDB.GetAllArtists(global.DB)
|
||||
if err != nil {
|
||||
|
@ -73,19 +55,17 @@ func Handler() http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
dbTracks, err := musicDB.GetOrphanTracks(global.DB)
|
||||
tracks, err := musicDB.GetOrphanTracks(global.DB)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull orphan tracks: %s\n", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var tracks = []musicModel.DisplayTrack{}
|
||||
for _, track := range dbTracks {
|
||||
tracks = append(tracks, musicModel.DisplayTrack{
|
||||
Track: track,
|
||||
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
||||
})
|
||||
type IndexData struct {
|
||||
Releases []*musicModel.Release
|
||||
Artists []*musicModel.Artist
|
||||
Tracks []*musicModel.Track
|
||||
}
|
||||
|
||||
err = pages["index"].Execute(w, IndexData{
|
||||
|
@ -125,8 +105,9 @@ func GetSession(r *http.Request) *Session {
|
|||
// is the session token in context?
|
||||
var ctx_session = r.Context().Value("session")
|
||||
if ctx_session != nil {
|
||||
token = ctx_session.(string)
|
||||
token = ctx_session.(*Session).Token
|
||||
}
|
||||
|
||||
// okay, is it in the auth header?
|
||||
if token == "" {
|
||||
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
|
||||
|
@ -168,11 +149,15 @@ func GetSession(r *http.Request) *Session {
|
|||
|
||||
func LoginHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if discord.CREDENTIALS_PROVIDED && ADMIN_ID_DISCORD == "" {
|
||||
if !discord.CREDENTIALS_PROVIDED || ADMIN_ID_DISCORD == "" {
|
||||
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(discord.CLIENT_ID)
|
||||
fmt.Println(discord.API_ENDPOINT)
|
||||
fmt.Println(discord.REDIRECT_URI)
|
||||
|
||||
code := r.URL.Query().Get("code")
|
||||
|
||||
if code == "" {
|
||||
|
@ -209,8 +194,9 @@ func LoginHandler() http.Handler {
|
|||
cookie.Name = "token"
|
||||
cookie.Value = session.Token
|
||||
cookie.Expires = time.Now().Add(24 * time.Hour)
|
||||
// TODO: uncomment this probably that might be nice i think
|
||||
// cookie.Secure = true
|
||||
if strings.HasPrefix(global.HTTP_DOMAIN, "https") {
|
||||
cookie.Secure = true
|
||||
}
|
||||
cookie.HttpOnly = true
|
||||
cookie.Path = "/"
|
||||
http.SetCookie(w, &cookie)
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"arimelody.me/arimelody.me/global"
|
||||
db "arimelody.me/arimelody.me/music/controller"
|
||||
"arimelody.me/arimelody.me/music/model"
|
||||
"arimelody-web/global"
|
||||
db "arimelody-web/music/controller"
|
||||
"arimelody-web/music/model"
|
||||
)
|
||||
|
||||
func serveRelease() http.Handler {
|
||||
|
@ -15,13 +15,13 @@ func serveRelease() http.Handler {
|
|||
slices := strings.Split(r.URL.Path[1:], "/")
|
||||
releaseID := slices[0]
|
||||
|
||||
release, err := db.GetFullRelease(global.DB, releaseID)
|
||||
release, err := db.GetRelease(global.DB, releaseID, true)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no rows") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err)
|
||||
fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", releaseID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func serveRelease() http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func serveEditCredits(release *model.FullRelease) http.Handler {
|
||||
func serveEditCredits(release *model.Release) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
err := components["editcredits"].Execute(w, release)
|
||||
|
@ -79,9 +79,9 @@ func serveEditCredits(release *model.FullRelease) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func serveAddCredit(release *model.FullRelease) http.Handler {
|
||||
func serveAddCredit(release *model.Release) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
artists, err := db.GetArtistsNotOnRelease(global.DB, release.Release.ID)
|
||||
artists, err := db.GetArtistsNotOnRelease(global.DB, release.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
@ -128,7 +128,7 @@ func serveNewCredit() http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func serveEditLinks(release *model.FullRelease) http.Handler {
|
||||
func serveEditLinks(release *model.Release) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
err := components["editlinks"].Execute(w, release)
|
||||
|
@ -139,7 +139,7 @@ func serveEditLinks(release *model.FullRelease) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func serveEditTracks(release *model.FullRelease) http.Handler {
|
||||
func serveEditTracks(release *model.Release) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
err := components["edittracks"].Execute(w, release)
|
||||
|
@ -150,9 +150,9 @@ func serveEditTracks(release *model.FullRelease) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func serveAddTrack(release *model.FullRelease) http.Handler {
|
||||
func serveAddTrack(release *model.Release) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tracks, err := db.GetTracksNotOnRelease(global.DB, release.Release.ID)
|
||||
tracks, err := db.GetTracksNotOnRelease(global.DB, release.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
|
151
admin/static/edit-artist.css
Normal file
151
admin/static/edit-artist.css
Normal file
|
@ -0,0 +1,151 @@
|
|||
h1 {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
#artist {
|
||||
margin-bottom: 1em;
|
||||
padding: 1.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.2em;
|
||||
|
||||
border-radius: .5em;
|
||||
background: #f8f8f8f8;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.artist-avatar {
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
.artist-avatar img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
.artist-avatar img:hover {
|
||||
outline: 1px solid #808080;
|
||||
cursor: pointer;
|
||||
}
|
||||
.artist-avatar #remove-avatar {
|
||||
padding: .3em .4em;
|
||||
}
|
||||
|
||||
.artist-info {
|
||||
margin: -1em 0 0 0;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.attribute-header {
|
||||
margin: 1em 0 .2em 0;
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.artist-name {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: calc(100% - .4em);
|
||||
padding: .1em .2em;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
color: inherit;
|
||||
background: #ffffff;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
}
|
||||
input[type="text"]:hover {
|
||||
border-color: #80808080;
|
||||
}
|
||||
input[type="text"]:active,
|
||||
input[type="text"]:focus {
|
||||
border-color: #808080;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
padding: .5em .8em;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
border-radius: .5em;
|
||||
border: 1px solid #a0a0a0;
|
||||
background: #f0f0f0;
|
||||
color: inherit;
|
||||
}
|
||||
button:hover, .button:hover {
|
||||
background: #fff;
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
button:active, .button:active {
|
||||
background: #d0d0d0;
|
||||
border-color: #808080;
|
||||
}
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
}
|
||||
button.save {
|
||||
background: #6fd7ff;
|
||||
border-color: #6f9eb0;
|
||||
}
|
||||
button.delete {
|
||||
background: #ff7171;
|
||||
border-color: #7d3535;
|
||||
}
|
||||
button:hover {
|
||||
background: #fff;
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
button:active {
|
||||
background: #d0d0d0;
|
||||
border-color: #808080;
|
||||
}
|
||||
button[disabled] {
|
||||
background: #d0d0d0 !important;
|
||||
border-color: #808080 !important;
|
||||
opacity: .5;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
a.delete {
|
||||
color: #d22828;
|
||||
}
|
||||
|
||||
.artist-actions {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
gap: .5em;
|
||||
flex-direction: row;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.card-title a.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.credit {
|
||||
margin: 1em 0;
|
||||
padding: .5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
background: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #808080;
|
||||
}
|
||||
|
||||
.release-artwork {
|
||||
width: 64px;
|
||||
height: min-content;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.credit-info h3,
|
||||
.credit-info p {
|
||||
margin: 0;
|
||||
font-size: .9em;
|
||||
}
|
79
admin/static/edit-artist.js
Normal file
79
admin/static/edit-artist.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
const artistID = document.getElementById("artist").dataset.id;
|
||||
const nameInput = document.getElementById("name");
|
||||
const avatarImg = document.getElementById("avatar");
|
||||
const removeAvatarBtn = document.getElementById("remove-avatar");
|
||||
const avatarInput = document.getElementById("avatar-file");
|
||||
const websiteInput = document.getElementById("website");
|
||||
const saveBtn = document.getElementById("save");
|
||||
const deleteBtn = document.getElementById("delete");
|
||||
|
||||
saveBtn.addEventListener("click", () => {
|
||||
fetch("/api/v1/artist/" + artistID, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
name: nameInput.value,
|
||||
website: websiteInput.value,
|
||||
avatar: avatarImg.src,
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
res.text().then(error => {
|
||||
console.error(error);
|
||||
alert("Failed to update release: " + error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
location = location;
|
||||
});
|
||||
});
|
||||
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
if (artistID != prompt(
|
||||
"You are about to permanently delete " + artistID + ". " +
|
||||
"This action is irreversible. " +
|
||||
"Please enter \"" + artistID + "\" to continue.")) return;
|
||||
fetch("/api/v1/artist/" + artistID, {
|
||||
method: "DELETE",
|
||||
}).then(res => {
|
||||
if (!res.ok) {
|
||||
res.text().then(error => {
|
||||
console.error(error);
|
||||
alert("Failed to delete release: " + error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
location = "/admin";
|
||||
});
|
||||
});
|
||||
|
||||
[nameInput, websiteInput].forEach(input => {
|
||||
input.addEventListener("change", () => {
|
||||
saveBtn.disabled = false;
|
||||
});
|
||||
input.addEventListener("keypress", () => {
|
||||
saveBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
avatarImg.addEventListener("click", () => {
|
||||
avatarInput.addEventListener("change", () => {
|
||||
if (avatarInput.files.length > 0) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const data = e.target.result;
|
||||
avatarImg.src = data;
|
||||
saveBtn.disabled = false;
|
||||
};
|
||||
reader.readAsDataURL(avatarInput.files[0]);
|
||||
}
|
||||
});
|
||||
avatarInput.click();
|
||||
});
|
||||
|
||||
removeAvatarBtn.addEventListener("click", () => {
|
||||
avatarImg.src = "/img/default-avatar.png"
|
||||
saveBtn.disabled = false;
|
||||
});
|
|
@ -1,5 +1,3 @@
|
|||
import Stateful from "/script/silver.min.js"
|
||||
|
||||
const releaseID = document.getElementById("release").dataset.id;
|
||||
const titleInput = document.getElementById("title");
|
||||
const artworkImg = document.getElementById("artwork");
|
||||
|
|
|
@ -6,7 +6,7 @@ h1 {
|
|||
|
||||
#track {
|
||||
margin-bottom: 1em;
|
||||
padding: 1.5em;
|
||||
padding: .5em 1.5em 1.5em 1.5em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1.2em;
|
||||
|
@ -34,7 +34,7 @@ h1 {
|
|||
}
|
||||
|
||||
#title {
|
||||
width: 100%;
|
||||
width: calc(100% - .4em);
|
||||
padding: .1em .2em;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const newReleaseBtn = document.getElementById("create-release");
|
||||
const newArtistBtn = document.getElementById("create-artist");
|
||||
const newTrackBtn = document.getElementById("create-track");
|
||||
|
||||
newReleaseBtn.addEventListener("click", event => {
|
||||
|
@ -24,6 +25,30 @@ newReleaseBtn.addEventListener("click", event => {
|
|||
});
|
||||
});
|
||||
|
||||
newArtistBtn.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
const id = prompt("Enter an ID for this artist:");
|
||||
if (id == null || id == "") return;
|
||||
|
||||
fetch("/api/v1/artist", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({id})
|
||||
}).then(res => {
|
||||
res.text().then(text => {
|
||||
if (res.ok) {
|
||||
location = "/admin/artist/" + id;
|
||||
} else {
|
||||
alert("Request failed: " + text);
|
||||
console.error(text);
|
||||
}
|
||||
})
|
||||
}).catch(err => {
|
||||
alert("Failed to create artist. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
newTrackBtn.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
const title = prompt("Enter an title for this track:");
|
||||
|
@ -43,7 +68,7 @@ newTrackBtn.addEventListener("click", event => {
|
|||
}
|
||||
})
|
||||
}).catch(err => {
|
||||
alert("Failed to create release. Check the console for details.");
|
||||
alert("Failed to create track. Check the console for details.");
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,6 +29,11 @@ var pages = map[string]*template.Template{
|
|||
filepath.Join("views", "prideflag.html"),
|
||||
filepath.Join("admin", "views", "edit-release.html"),
|
||||
)),
|
||||
"artist": template.Must(template.ParseFiles(
|
||||
filepath.Join("admin", "views", "layout.html"),
|
||||
filepath.Join("views", "prideflag.html"),
|
||||
filepath.Join("admin", "views", "edit-artist.html"),
|
||||
)),
|
||||
"track": template.Must(template.ParseFiles(
|
||||
filepath.Join("admin", "views", "layout.html"),
|
||||
filepath.Join("views", "prideflag.html"),
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"arimelody.me/arimelody.me/global"
|
||||
"arimelody.me/arimelody.me/music/model"
|
||||
"arimelody.me/arimelody.me/music/controller"
|
||||
"arimelody-web/global"
|
||||
"arimelody-web/music/model"
|
||||
"arimelody-web/music/controller"
|
||||
)
|
||||
|
||||
func serveTrack() http.Handler {
|
||||
|
@ -25,26 +25,16 @@ func serveTrack() http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
dbReleases, err := music.GetTrackReleases(global.DB, track.ID)
|
||||
releases, err := music.GetTrackReleases(global.DB, track.ID, true)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||
fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
releases := []model.FullRelease{}
|
||||
for _, release := range dbReleases {
|
||||
fullRelease, err := music.GetFullRelease(global.DB, release.ID)
|
||||
if err != nil {
|
||||
fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
releases = append(releases, *fullRelease)
|
||||
}
|
||||
|
||||
type Track struct {
|
||||
*model.Track
|
||||
Releases []model.FullRelease
|
||||
Releases []*model.Release
|
||||
}
|
||||
|
||||
err = pages["track"].Execute(w, Track{ Track: track, Releases: releases })
|
||||
|
|
72
admin/views/edit-artist.html
Normal file
72
admin/views/edit-artist.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
{{define "head"}}
|
||||
<title>Editing {{.Name}} - ari melody 💫</title>
|
||||
|
||||
<link rel="stylesheet" href="/admin/static/edit-artist.css">
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<main>
|
||||
<h1>Editing Artist</h1>
|
||||
|
||||
<div id="artist" data-id="{{.ID}}">
|
||||
<div class="artist-avatar">
|
||||
<img src="{{.Avatar}}" alt="" width="256" loading="lazy" id="avatar">
|
||||
<input type="file" id="avatar-file" name="Artwork" accept=".png,.jpg,.jpeg" hidden>
|
||||
<button id="remove-avatar">Remove</button>
|
||||
</div>
|
||||
<div class="artist-info">
|
||||
<p class="attribute-header">Name</p>
|
||||
<h2 class="artist-name">
|
||||
<input type="text" id="name" name="artist-name" value="{{.Name}}">
|
||||
</h2>
|
||||
|
||||
<p class="attribute-header">Website</p>
|
||||
<input type="text" id="website" name="website" value="{{.Website}}">
|
||||
|
||||
<div class="artist-actions">
|
||||
<button type="submit" class="save" id="save" disabled>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Featured in</h2>
|
||||
</div>
|
||||
<div class="card releases">
|
||||
{{if .Credits}}
|
||||
{{range .Credits}}
|
||||
<div class="credit">
|
||||
<img src="{{.Release.Artwork}}" alt="" width="64" loading="lazy" class="release-artwork">
|
||||
<div class="credit-info">
|
||||
<h3 class="credit-name"><a href="/admin/release/{{.Release.ID}}">{{.Release.Title}}</a></h3>
|
||||
<p class="credit-artists">{{.Release.PrintArtists true true}}</p>
|
||||
<p class="artist-role">
|
||||
Role: {{.Role}}
|
||||
{{if .Primary}}
|
||||
<small>(Primary)</small>
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<p>This artist has no credits.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="card-title">
|
||||
<h2>Danger Zone</h2>
|
||||
</div>
|
||||
<div class="card danger">
|
||||
<p>
|
||||
Clicking the button below will delete this artist.
|
||||
This action is <strong>irreversible</strong>.
|
||||
You will be prompted to confirm this decision.
|
||||
</p>
|
||||
<button class="delete" id="delete">Delete Artist</button>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="/admin/static/edit-artist.js" defer></script>
|
||||
{{end}}
|
|
@ -112,7 +112,7 @@
|
|||
<div class="credit">
|
||||
<img src="{{.Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<div class="credit-info">
|
||||
<p class="artist-name"><a href="/admin/artists/{{.Artist.ID}}">{{.Artist.Name}}</a></p>
|
||||
<p class="artist-name"><a href="/admin/artist/{{.Artist.ID}}">{{.Artist.Name}}</a></p>
|
||||
<p class="artist-role">
|
||||
{{.Role}}
|
||||
{{if .Primary}}
|
||||
|
@ -152,23 +152,23 @@
|
|||
>Edit</a>
|
||||
</div>
|
||||
<div class="card tracks">
|
||||
{{range .Tracks}}
|
||||
<div class="track" data-id="{{.ID}}">
|
||||
{{range $i, $track := .Tracks}}
|
||||
<div class="track" data-id="{{$track.ID}}">
|
||||
<h2 class="track-title">
|
||||
<span class="track-number">{{.Number}}</span>
|
||||
<a href="/admin/track/{{.ID}}">{{.Title}}</a>
|
||||
<span class="track-number">{{.Add $i 1}}</span>
|
||||
<a href="/admin/track/{{$track.ID}}">{{$track.Title}}</a>
|
||||
</h2>
|
||||
|
||||
<h3>Description</h3>
|
||||
{{if .Description}}
|
||||
<p class="track-description">{{.Description}}</p>
|
||||
{{if $track.Description}}
|
||||
<p class="track-description">{{$track.GetDescriptionHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-description empty">No description provided.</p>
|
||||
{{end}}
|
||||
|
||||
<h3>Lyrics</h3>
|
||||
{{if .Lyrics}}
|
||||
<p class="track-lyrics">{{.Lyrics}}</p>
|
||||
{{if $track.Lyrics}}
|
||||
<p class="track-lyrics">{{$track.GetLyricsHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||
{{end}}
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
|
||||
<div class="card-title">
|
||||
<h1>Artists</h1>
|
||||
<a class="create-btn">Create New</a>
|
||||
<a class="create-btn" id="create-artist">Create New</a>
|
||||
</div>
|
||||
<div class="card artists">
|
||||
{{range $Artist := .Artists}}
|
||||
<div class="artist">
|
||||
<img src="{{$Artist.GetAvatar}}" alt="" width="64" loading="lazy" class="artist-avatar">
|
||||
<a href="/admin/artists/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a>
|
||||
<a href="/admin/artist/{{$Artist.ID}}" class="artist-name">{{$Artist.Name}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .Artists}}
|
||||
|
@ -49,12 +49,12 @@
|
|||
<a href="/admin/track/{{$Track.ID}}">{{$Track.Title}}</a>
|
||||
</h2>
|
||||
{{if $Track.Description}}
|
||||
<p class="track-description">{{$Track.Description}}</p>
|
||||
<p class="track-description">{{$Track.GetDescriptionHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-description empty">No description provided.</p>
|
||||
{{end}}
|
||||
{{if $Track.Lyrics}}
|
||||
<p class="track-lyrics">{{$Track.Lyrics}}</p>
|
||||
<p class="track-lyrics">{{$Track.GetLyricsHTML}}</p>
|
||||
{{else}}
|
||||
<p class="track-lyrics empty">There are no lyrics.</p>
|
||||
{{end}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue