very rough updates to admin pages, reduced reliance on global.DB

This commit is contained in:
ari melody 2025-01-21 00:20:07 +00:00
parent ae254dd731
commit 7044f7344b
Signed by: ari
GPG key ID: CF99829C92678188
15 changed files with 192 additions and 106 deletions

38
admin/accounthttp.go Normal file
View file

@ -0,0 +1,38 @@
package admin
import (
"fmt"
"net/http"
"arimelody-web/controller"
"arimelody-web/model"
"github.com/jmoiron/sqlx"
)
func AccountHandler(db *sqlx.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
account := r.Context().Value("account").(*model.Account)
totps, err := controller.GetTOTPsForAccount(db, account.ID)
if err != nil {
fmt.Printf("WARN: Failed to fetch TOTPs: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
type AccountResponse struct {
Account *model.Account
TOTPs []model.TOTP
}
err = pages["account"].Execute(w, AccountResponse{
Account: account,
TOTPs: totps,
})
if err != nil {
fmt.Printf("WARN: Failed to render admin account page: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}

View file

@ -22,24 +22,24 @@ type TemplateData struct {
Token string
}
func Handler() http.Handler {
func Handler(db *sqlx.DB) http.Handler {
mux := http.NewServeMux()
mux.Handle("/login", LoginHandler())
mux.Handle("/register", createAccountHandler())
mux.Handle("/logout", RequireAccount(global.DB, LogoutHandler()))
// TODO: /admin/account
mux.Handle("/login", LoginHandler(db))
mux.Handle("/register", createAccountHandler(db))
mux.Handle("/logout", RequireAccount(db, LogoutHandler(db)))
mux.Handle("/account", RequireAccount(db, AccountHandler(db)))
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
mux.Handle("/release/", RequireAccount(global.DB, http.StripPrefix("/release", serveRelease())))
mux.Handle("/artist/", RequireAccount(global.DB, http.StripPrefix("/artist", serveArtist())))
mux.Handle("/track/", RequireAccount(global.DB, http.StripPrefix("/track", serveTrack())))
mux.Handle("/release/", RequireAccount(db, http.StripPrefix("/release", serveRelease())))
mux.Handle("/artist/", RequireAccount(db, http.StripPrefix("/artist", serveArtist())))
mux.Handle("/track/", RequireAccount(db, http.StripPrefix("/track", serveTrack())))
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
account, err := controller.GetAccountByRequest(global.DB, r)
account, err := controller.GetAccountByRequest(db, r)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %s\n", err)
}
@ -48,21 +48,21 @@ func Handler() http.Handler {
return
}
releases, err := controller.GetAllReleases(global.DB, false, 0, true)
releases, err := controller.GetAllReleases(db, false, 0, true)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
artists, err := controller.GetAllArtists(global.DB)
artists, err := controller.GetAllArtists(db)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
tracks, err := controller.GetOrphanTracks(global.DB)
tracks, err := controller.GetOrphanTracks(db)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -112,10 +112,10 @@ func RequireAccount(db *sqlx.DB, next http.Handler) http.HandlerFunc {
})
}
func LoginHandler() http.Handler {
func LoginHandler(db *sqlx.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
account, err := controller.GetAccountByRequest(global.DB, r)
account, err := controller.GetAccountByRequest(db, r)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account: %v\n", err)
@ -157,7 +157,7 @@ func LoginHandler() http.Handler {
TOTP: r.Form.Get("totp"),
}
account, err := controller.GetAccount(global.DB, credentials.Username)
account, err := controller.GetAccount(db, credentials.Username)
if err != nil {
http.Error(w, "Invalid username or password", http.StatusBadRequest)
return
@ -176,7 +176,7 @@ func LoginHandler() http.Handler {
// TODO: check TOTP
// login success!
token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent())
token, err := controller.CreateToken(db, account.ID, r.UserAgent())
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -206,17 +206,17 @@ func LoginHandler() http.Handler {
})
}
func LogoutHandler() http.Handler {
func LogoutHandler(db *sqlx.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.NotFound(w, r)
return
}
tokenStr := controller.GetTokenFromRequest(global.DB, r)
tokenStr := controller.GetTokenFromRequest(db, r)
if len(tokenStr) > 0 {
err := controller.DeleteToken(global.DB, tokenStr)
err := controller.DeleteToken(db, tokenStr)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to revoke token: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -238,9 +238,9 @@ func LogoutHandler() http.Handler {
})
}
func createAccountHandler() http.Handler {
func createAccountHandler(db *sqlx.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
checkAccount, err := controller.GetAccountByRequest(global.DB, r)
checkAccount, err := controller.GetAccountByRequest(db, r)
if err != nil {
fmt.Printf("WARN: Failed to fetch account: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -297,7 +297,7 @@ func createAccountHandler() http.Handler {
}
// make sure code exists in DB
invite, err := controller.GetInvite(global.DB, credentials.Invite)
invite, err := controller.GetInvite(db, credentials.Invite)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
render(CreateAccountResponse{
@ -307,7 +307,7 @@ func createAccountHandler() http.Handler {
}
if invite == nil || time.Now().After(invite.ExpiresAt) {
if invite != nil {
err := controller.DeleteInvite(global.DB, invite.Code)
err := controller.DeleteInvite(db, invite.Code)
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
}
render(CreateAccountResponse{
@ -331,7 +331,7 @@ func createAccountHandler() http.Handler {
Email: credentials.Email,
AvatarURL: "/img/default-avatar.png",
}
err = controller.CreateAccount(global.DB, &account)
err = controller.CreateAccount(db, &account)
if err != nil {
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
render(CreateAccountResponse{
@ -346,11 +346,11 @@ func createAccountHandler() http.Handler {
return
}
err = controller.DeleteInvite(global.DB, invite.Code)
err = controller.DeleteInvite(db, invite.Code)
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
// registration success!
token, err := controller.CreateToken(global.DB, account.ID, r.UserAgent())
token, err := controller.CreateToken(db, account.ID, r.UserAgent())
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to create token: %v\n", err)
// gracefully redirect user to login page

View file

@ -0,0 +1,41 @@
@import url("/admin/static/index.css");
form#change-password {
width: 100%;
display: flex;
flex-direction: column;
align-items: start;
}
form div {
width: 20rem;
}
form button {
margin-top: 1rem;
}
label {
width: 100%;
margin: 1rem 0 .5rem 0;
display: block;
color: #10101080;
}
input {
width: 100%;
margin: .5rem 0;
padding: .3rem .5rem;
display: block;
border-radius: 4px;
border: 1px solid #808080;
font-size: inherit;
font-family: inherit;
color: inherit;
}
#error {
background: #ffa9b8;
border: 1px solid #dc5959;
padding: 1em;
border-radius: 4px;
}

View file

@ -99,3 +99,48 @@
opacity: 0.75;
}
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;
}

View file

@ -28,6 +28,11 @@ var pages = map[string]*template.Template{
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "logout.html"),
)),
"account": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-account.html"),
)),
"release": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),

View file

@ -1,7 +1,7 @@
{{define "head"}}
<title>Register - ari melody 💫</title>
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/admin/static/index.css">
<style>
p a {
color: #2a67c8;
@ -44,28 +44,6 @@ input {
color: inherit;
}
button {
padding: .5em .8em;
font-family: inherit;
font-size: inherit;
border-radius: .5em;
border: 1px solid #a0a0a0;
background: #f0f0f0;
color: inherit;
}
button.new {
background: #c4ff6a;
border-color: #84b141;
}
button:hover {
background: #fff;
border-color: #d0d0d0;
}
button:active {
background: #d0d0d0;
border-color: #808080;
}
#error {
background: #ffa9b8;
border: 1px solid #dc5959;

View file

@ -1,7 +1,7 @@
{{define "head"}}
<title>Account Settings - ari melody 💫</title>
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/admin/static/index.css">
<link rel="stylesheet" href="/admin/static/edit-account.css">
{{end}}
{{define "content"}}
@ -10,7 +10,8 @@
<div class="card-title">
<h2>Change Password</h2>
</div>
<div class="card">
<form action="/api/v1/change-password" method="POST" id="change-password">
<div>
<label for="current-password">Current Password</label>

View file

@ -1,7 +1,6 @@
{{define "head"}}
<title>Editing {{.Artist.Name}} - ari melody 💫</title>
<link rel="shortcut icon" href="{{.Artist.GetAvatar}}" type="image/x-icon">
<link rel="stylesheet" href="/admin/static/edit-artist.css">
{{end}}

View file

@ -1,7 +1,6 @@
{{define "head"}}
<title>Editing {{.Release.Title}} - ari melody 💫</title>
<link rel="shortcut icon" href="{{.Release.GetArtwork}}" type="image/x-icon">
<link rel="stylesheet" href="/admin/static/edit-release.css">
{{end}}

View file

@ -1,6 +1,6 @@
{{define "head"}}
<title>Editing Track - ari melody 💫</title>
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/admin/static/edit-track.css">
{{end}}

View file

@ -47,28 +47,6 @@ input[disabled] {
opacity: .5;
cursor: not-allowed;
}
button {
padding: .5em .8em;
font-family: inherit;
font-size: inherit;
border-radius: .5em;
border: 1px solid #a0a0a0;
background: #f0f0f0;
color: inherit;
}
button.save {
background: #6fd7ff;
border-color: #6f9eb0;
}
button:hover {
background: #fff;
border-color: #d0d0d0;
}
button:active {
background: #d0d0d0;
border-color: #808080;
}
</style>
{{end}}