add basic lastfm hook

This commit is contained in:
sentriz
2019-04-17 21:36:40 +01:00
parent 9f6cd20f5a
commit 4cd9c0c39c
17 changed files with 199 additions and 33 deletions

View File

@@ -187,6 +187,7 @@ func main() {
&db.Track{},
&db.Cover{},
&db.User{},
&db.Setting{},
)
// 🤫🤫🤫
orm.Exec(`

View File

@@ -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() {

View File

@@ -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
View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
View 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
View 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"`
}

View File

@@ -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;
}

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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>

View File

@@ -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">

View 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 }}

View File

@@ -2,7 +2,7 @@
{{ define "content" }}
<div class="light right">
<span>welcome <u>{{ .User.Name }}</u> &#124; <a href="/admin/home">home</a> &#124; <a href="/admin/logout">logout</a></span>
welcome <u>{{ .User.Name }}</u> &#124; <a href="/admin/home">home</a> &#124; <a href="/admin/logout">logout</a>
</div>
{{ template "user" . }}
{{ end }}