diff --git a/admin/accounthttp.go b/admin/accounthttp.go new file mode 100644 index 0000000..56aa247 --- /dev/null +++ b/admin/accounthttp.go @@ -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) + } + }) +} + diff --git a/admin/http.go b/admin/http.go index 4dbc66b..9eae2b7 100644 --- a/admin/http.go +++ b/admin/http.go @@ -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 diff --git a/admin/static/edit-account.css b/admin/static/edit-account.css new file mode 100644 index 0000000..7c75b66 --- /dev/null +++ b/admin/static/edit-account.css @@ -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; +} diff --git a/admin/static/index.css b/admin/static/index.css index ec426af..1411eff 100644 --- a/admin/static/index.css +++ b/admin/static/index.css @@ -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; +} diff --git a/admin/templates.go b/admin/templates.go index e91313a..1fa7a65 100644 --- a/admin/templates.go +++ b/admin/templates.go @@ -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"), diff --git a/admin/views/create-account.html b/admin/views/create-account.html index 5d92627..b0aff03 100644 --- a/admin/views/create-account.html +++ b/admin/views/create-account.html @@ -1,7 +1,7 @@ {{define "head"}}