add basic lastfm hook
This commit is contained in:
@@ -187,6 +187,7 @@ func main() {
|
||||
&db.Track{},
|
||||
&db.Cover{},
|
||||
&db.User{},
|
||||
&db.Setting{},
|
||||
)
|
||||
// 🤫🤫🤫
|
||||
orm.Exec(`
|
||||
|
||||
@@ -87,10 +87,13 @@ func setAdminRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/admin/home", withUserWare(cont.ServeHome))
|
||||
mux.HandleFunc("/admin/change_own_password", withUserWare(cont.ServeChangeOwnPassword))
|
||||
mux.HandleFunc("/admin/change_own_password_do", withUserWare(cont.ServeChangeOwnPasswordDo))
|
||||
mux.HandleFunc("/admin/link_lastfm_callback", withUserWare(cont.ServeLinkLastFMCallback))
|
||||
mux.HandleFunc("/admin/change_password", withAdminWare(cont.ServeChangePassword))
|
||||
mux.HandleFunc("/admin/change_password_do", withAdminWare(cont.ServeChangePasswordDo))
|
||||
mux.HandleFunc("/admin/create_user", withAdminWare(cont.ServeCreateUser))
|
||||
mux.HandleFunc("/admin/create_user_do", withAdminWare(cont.ServeCreateUserDo))
|
||||
mux.HandleFunc("/admin/update_lastfm_api_key", withAdminWare(cont.ServeUpdateLastFMAPIKey))
|
||||
mux.HandleFunc("/admin/update_lastfm_api_key_do", withAdminWare(cont.ServeUpdateLastFMAPIKeyDo))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -55,3 +55,10 @@ type User struct {
|
||||
Password string
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
// Setting represents the settings table
|
||||
type Setting struct {
|
||||
CrudBase
|
||||
Key string `gorm:"primary_key;auto_increment:false"`
|
||||
Value string
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -2,6 +2,7 @@ module github.com/sentriz/gonic
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.37.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
||||
github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||
|
||||
@@ -74,6 +74,15 @@ func (c *Controller) ServeChangeOwnPasswordDo(w http.ResponseWriter, r *http.Req
|
||||
http.Redirect(w, r, "/admin/home", 303)
|
||||
}
|
||||
|
||||
func (c *Controller) ServeLinkLastFMCallback(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
http.Error(w, "please provide a token", 400)
|
||||
return
|
||||
}
|
||||
_ = token
|
||||
}
|
||||
|
||||
func (c *Controller) ServeChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
username := r.URL.Query().Get("user")
|
||||
if username == "" {
|
||||
@@ -148,3 +157,36 @@ func (c *Controller) ServeCreateUserDo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
http.Redirect(w, r, "/admin/home", 303)
|
||||
}
|
||||
|
||||
func (c *Controller) ServeUpdateLastFMAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
var data templateData
|
||||
var apiKey db.Setting
|
||||
var secret db.Setting
|
||||
c.DB.Where("key = ?", "lastfm_api_key").First(&apiKey)
|
||||
c.DB.Where("key = ?", "lastfm_secret").First(&secret)
|
||||
data.CurrentLastFMAPIKey = apiKey.Value
|
||||
data.CurrentLastFMAPISecret = secret.Value
|
||||
renderTemplate(w, r, "update_lastfm_api_key", &data)
|
||||
}
|
||||
|
||||
func (c *Controller) ServeUpdateLastFMAPIKeyDo(w http.ResponseWriter, r *http.Request) {
|
||||
session := r.Context().Value("session").(*sessions.Session)
|
||||
apiKey := r.FormValue("api_key")
|
||||
secret := r.FormValue("secret")
|
||||
err := utilities.ValidateAPIKey(apiKey, secret)
|
||||
if err != nil {
|
||||
session.AddFlash(err.Error())
|
||||
session.Save(r, w)
|
||||
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
||||
return
|
||||
}
|
||||
c.DB.
|
||||
Where(db.Setting{Key: "lastfm_api_key"}).
|
||||
Assign(db.Setting{Value: apiKey}).
|
||||
FirstOrCreate(&db.Setting{})
|
||||
c.DB.
|
||||
Where(db.Setting{Key: "lastfm_secret"}).
|
||||
Assign(db.Setting{Value: secret}).
|
||||
FirstOrCreate(&db.Setting{})
|
||||
http.Redirect(w, r, "/admin/home", 303)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ func init() {
|
||||
filepath.Join("templates", "user.tmpl"),
|
||||
filepath.Join("templates", "pages", "create_user.tmpl"),
|
||||
))
|
||||
templates["update_lastfm_api_key"] = template.Must(template.ParseFiles(
|
||||
filepath.Join("templates", "layout.tmpl"),
|
||||
filepath.Join("templates", "user.tmpl"),
|
||||
filepath.Join("templates", "pages", "update_lastfm_api_key.tmpl"),
|
||||
))
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
@@ -55,13 +60,16 @@ type Controller struct {
|
||||
}
|
||||
|
||||
type templateData struct {
|
||||
Flashes []interface{}
|
||||
User *db.User
|
||||
SelectedUser *db.User
|
||||
AllUsers []*db.User
|
||||
ArtistCount uint
|
||||
AlbumCount uint
|
||||
TrackCount uint
|
||||
Flashes []interface{}
|
||||
User *db.User
|
||||
SelectedUser *db.User
|
||||
AllUsers []*db.User
|
||||
ArtistCount uint
|
||||
AlbumCount uint
|
||||
TrackCount uint
|
||||
CurrentLastFMAPIKey string
|
||||
CurrentLastFMAPISecret string
|
||||
RequestRoot string
|
||||
}
|
||||
|
||||
func getStrParam(r *http.Request, key string) string {
|
||||
@@ -146,6 +154,11 @@ func renderTemplate(w http.ResponseWriter, r *http.Request,
|
||||
if ok {
|
||||
data.User = user
|
||||
}
|
||||
if r.URL.Host == "" {
|
||||
data.RequestRoot = "http://localhost"
|
||||
} else {
|
||||
data.RequestRoot = fmt.Sprintf("%s://%s", r.URL.Scheme, r.URL.Host)
|
||||
}
|
||||
err := templates[name].ExecuteTemplate(w, "layout", data)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("500 when executing: %v", err), 500)
|
||||
|
||||
@@ -18,3 +18,10 @@ func ValidatePasswords(pOne, pTwo string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAPIKey(apiKey, secret string) error {
|
||||
if apiKey == "" || secret == "" {
|
||||
return fmt.Errorf("please enter both the api key and secret")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
55
lastfm/lastfm.go
Normal file
55
lastfm/lastfm.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package lastfm
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
baseURL = "http://ws.audioscrobbler.com/2.0/"
|
||||
client = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
)
|
||||
|
||||
func getParamSignature(params url.Values, secret string) string {
|
||||
toHash := ""
|
||||
for k, v := range params {
|
||||
toHash += k
|
||||
toHash += v[0]
|
||||
}
|
||||
toHash += secret
|
||||
hash := md5.Sum([]byte(toHash))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func GetSession(apiKey, secret, token string) (error, string) {
|
||||
params := url.Values{}
|
||||
// the first 3 parameters here must be in alphabetical order
|
||||
params.Add("api_key", apiKey)
|
||||
params.Add("method", "auth.getSession")
|
||||
params.Add("token", token)
|
||||
params.Add("api_sig", getParamSignature(params, secret))
|
||||
req, _ := http.NewRequest("GET", baseURL, nil)
|
||||
req.URL.RawQuery = params.Encode()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when making request to last.fm: %v", err), ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
decoder := xml.NewDecoder(resp.Body)
|
||||
var lastfm LastFM
|
||||
err = decoder.Decode(&lastfm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when decoding last.fm response: %v", err), ""
|
||||
}
|
||||
if lastfm.Error != nil {
|
||||
return fmt.Errorf("error when parsing last.fm response: %v", lastfm.Error.Value), ""
|
||||
}
|
||||
return nil, lastfm.Session.Key
|
||||
}
|
||||
21
lastfm/models.go
Normal file
21
lastfm/models.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package lastfm
|
||||
|
||||
import "encoding/xml"
|
||||
|
||||
type LastFM struct {
|
||||
XMLName xml.Name `xml:"lfm"`
|
||||
Status string `xml:"status,attr"`
|
||||
Session *Session `xml:"session"`
|
||||
Error *Error `xml:"error"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Name string `xml:"name"`
|
||||
Key string `xml:"key"`
|
||||
Subscriber uint `xml:"subscriber"`
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Code uint `xml:"code,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
@@ -8,6 +8,9 @@ form {
|
||||
max-width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* reset from awsm */
|
||||
@@ -30,6 +33,10 @@ form input[type=password], form input[type=text] {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
form input[type=submit] {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
#content > * {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
@@ -81,12 +88,3 @@ form input[type=password], form input[type=text] {
|
||||
background-color: #fd1b1b1c;
|
||||
}
|
||||
|
||||
#login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
#login-form input[type=submit] {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
{{ define "user" }}
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<span><u>changing account password</u></span>
|
||||
<u>changing account password</u>
|
||||
</div>
|
||||
<form id="login-form" action="/admin/change_own_password_do" method="post">
|
||||
<form action="/admin/change_own_password_do" method="post">
|
||||
<input type="password" id="password_one" name="password_one" placeholder="new password">
|
||||
<input type="password" id="password_two" name="password_two" placeholder="verify new password">
|
||||
<input type="submit" value="change">
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
{{ define "user" }}
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<span><u>changing {{ .SelectedUser.Name }}'s password</u></span>
|
||||
<u>changing {{ .SelectedUser.Name }}'s password</u>
|
||||
</div>
|
||||
<form id="login-form" action="/admin/change_password_do?user={{ .SelectedUser.Name }}" method="post">
|
||||
<form action="/admin/change_password_do?user={{ .SelectedUser.Name }}" method="post">
|
||||
<input type="password" id="password_one" name="password_one" placeholder="new password">
|
||||
<input type="password" id="password_two" name="password_two" placeholder="verify new password">
|
||||
<input type="submit" value="change">
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
{{ define "user" }}
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<span><u>create new user</u></span>
|
||||
<u>create new user</u>
|
||||
</div>
|
||||
<form id="login-form" action="/admin/create_user_do" method="post">
|
||||
<form action="/admin/create_user_do" method="post">
|
||||
<input type="text" id="username" name="username" placeholder="username">
|
||||
<input type="password" id="password_one" name="password_one" placeholder="password">
|
||||
<input type="password" id="password_two" name="password_two" placeholder="verify password">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{ define "user" }}
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<span><u>stats</u></span>
|
||||
<u>stats</u>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span class="pre">artists: {{ printf "%7v" .ArtistCount }}</span><br/>
|
||||
@@ -13,32 +13,32 @@
|
||||
</div>
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<span><u>last fm</u></span>
|
||||
<u>last.fm</u>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span class="pre">unlinked</span><br/>
|
||||
<a href="/admin/update_lastfm_api_key">update last.fm api key</a><br/>
|
||||
<a href="https://www.last.fm/api/auth/?api_key={{ .CurrentLastFMAPIKey }}&cb={{ .RequestRoot }}/admin/link_lastfm_callback">link account</a><br/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="padded box mono">
|
||||
{{ if .User.IsAdmin }}
|
||||
{{/* admin panel to manage all users */}}
|
||||
<div class="box-title">
|
||||
<span><u>users</u></span>
|
||||
<u>users</u>
|
||||
</div>
|
||||
<div class="right">
|
||||
{{ range $user := .AllUsers }}
|
||||
<span class="pre">{{ $user.Name }} <span class="light">created</span> <u>{{ $user.CreatedAt.Format "Jan 02, 2006" }}</u> <a href="/admin/change_password?user={{ $user.Name }}">change password</a></span><br/>
|
||||
{{ $user.Name }} <span class="light">created</span> <u>{{ $user.CreatedAt.Format "Jan 02, 2006" }}</u> <a href="/admin/change_password?user={{ $user.Name }}">change password</a><br/>
|
||||
{{ end }}
|
||||
<br>
|
||||
<a href="/admin/create_user" class="button">create new</a>
|
||||
</div>
|
||||
{{ else }}
|
||||
{{/* user panel to manage themselves */}}
|
||||
<div class="box-title">
|
||||
<span><u>your account</u></span>
|
||||
<u>your account</u>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="/admin/change_own_password" class="button">change password</a>
|
||||
<a href="/admin/change_own_password" class="button">change password</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
{{ define "content" }}
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<span><u>please login</u></span>
|
||||
<u>please login</u>
|
||||
</div>
|
||||
<form id="login-form" action="/admin/login_do" method="post">
|
||||
<form action="/admin/login_do" method="post">
|
||||
<input type="text" id="username" name="username" placeholder="username">
|
||||
<input type="password" id="password" name="password" placeholder="password">
|
||||
<input type="submit" value="login">
|
||||
|
||||
18
templates/pages/update_lastfm_api_key.tmpl
Normal file
18
templates/pages/update_lastfm_api_key.tmpl
Normal file
@@ -0,0 +1,18 @@
|
||||
{{ define "title" }}home{{ end }}
|
||||
|
||||
{{ define "user" }}
|
||||
<div class="padded box mono">
|
||||
<div class="box-title">
|
||||
<u>updating last.fm api key</u>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span class="light">current key</span> <i>{{ if .CurrentLastFMAPIKey }}{{ .CurrentLastFMAPIKey }}{{ else }}not set{{ end }}</i><br/>
|
||||
<span class="light">current secret</span> <i>{{ if .CurrentLastFMAPISecret }}{{ .CurrentLastFMAPISecret }}{{ else }}not set{{ end }}</i>
|
||||
</div>
|
||||
<form action="/admin/update_lastfm_api_key_do" method="post">
|
||||
<input type="text" id="api_key" name="api_key" placeholder="new key">
|
||||
<input type="text" id="secret" name="secret" placeholder="new secret">
|
||||
<input type="submit" value="update">
|
||||
</form>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{{ define "content" }}
|
||||
<div class="light right">
|
||||
<span>welcome <u>{{ .User.Name }}</u> | <a href="/admin/home">home</a> | <a href="/admin/logout">logout</a></span>
|
||||
welcome <u>{{ .User.Name }}</u> | <a href="/admin/home">home</a> | <a href="/admin/logout">logout</a>
|
||||
</div>
|
||||
{{ template "user" . }}
|
||||
{{ end }}
|
||||
|
||||
Reference in New Issue
Block a user