diff options
| author | Thibaut Horel <thibaut.horel@gmail.com> | 2017-06-04 19:30:25 -0400 |
|---|---|---|
| committer | Thibaut Horel <thibaut.horel@gmail.com> | 2017-06-04 19:30:25 -0400 |
| commit | 81522cbae16c6aadb1b789f3f875dc50f10cc005 (patch) | |
| tree | ab95db0a843783d4cfcd328e091a01597e241ee1 | |
| parent | f154ae1ec88146017abf3de9d14d119facb5fc4c (diff) | |
| download | lastfm-api-81522cbae16c6aadb1b789f3f875dc50f10cc005.tar.gz | |
Clean up
| -rw-r--r-- | apiv1.go | 58 | ||||
| -rw-r--r-- | data.go | 104 | ||||
| -rw-r--r-- | main.go | 6 | ||||
| -rw-r--r-- | modern.go | 7 | ||||
| -rw-r--r-- | schema.sql | 48 | ||||
| -rw-r--r-- | static/style.css | 25 | ||||
| -rw-r--r-- | templates/base.tmpl | 15 | ||||
| -rw-r--r-- | templates/index.tmpl | 15 | ||||
| -rw-r--r-- | utils.go | 17 |
9 files changed, 187 insertions, 108 deletions
@@ -42,15 +42,25 @@ func (app *App) mainHandler(w http.ResponseWriter, r *http.Request) { return } - client := r.FormValue("c") - s := NewSession(user, client, protocol) - app.PutSession(s) + s := &Session{ + User: user, + Client: r.FormValue("c"), + ClientVersion: r.FormValue("v"), + Key: randomToken(16), + Protocol: protocol, + } + if err := app.PutSession(s); err != nil { + log.Println(err) + fmt.Fprintln(w, "FAILED Couldn't create session") + return + } + fmt.Fprintln(w, "OK") - fmt.Fprintf(w, "%s\n", s.Key) + fmt.Fprintln(w, s.Key) fmt.Fprintln(w, "http://post.audioscrobbler.com:80/np") fmt.Fprintln(w, "http://post.audioscrobbler.com:80/scrobble") } else { - fmt.Fprintf(w, "<html>This is an endpoint, see <a href=\"http://www.last.fm/api/submissions\">here</a></html>") + fmt.Fprintf(w, "api") } } @@ -160,27 +170,33 @@ func parseScrobbles(values url.Values, session *Session) ([]Scrobble, int) { } func (app *App) scrobbleHandler(w http.ResponseWriter, r *http.Request) { - if session, err := app.GetSession(r.FormValue("s")); err != nil { + session, err := app.GetSession(r.FormValue("s")) + if err != nil { + log.Println(err) fmt.Fprintln(w, "BADSESSION") - } else { - scrobbles, _ := parseScrobbles(r.Form, session) + return + } + scrobbles, _ := parseScrobbles(r.Form, session) - for i, s := range scrobbles { - t := app.TrackInfo(s.TrackName.Name, s.Artist.Name) - s.Mbid = t.Mbid - scrobbles[i] = s - _, err = app.DB.Exec("INSERT INTO songs VALUES ($1, $2) "+ - "ON CONFLICT (mbid) DO UPDATE SET mbid=$1, image=$2", - t.Mbid, t.GetImage("medium")) - if err != nil { - log.Println(err) - } + for i, s := range scrobbles { + t := app.TrackInfo(s.TrackName.Name, s.Artist.Name) + s.MbidComp = t.Mbid + scrobbles[i] = s + _, err = app.DB.Exec("INSERT INTO songs VALUES ($1, $2) "+ + "ON CONFLICT (mbid) DO UPDATE SET mbid=$1, image=$2", + t.Mbid, t.GetImage("medium")) + if err != nil { + log.Println(err) } + } - fmt.Printf("%v\n", scrobbles) - app.PutScrobbles(scrobbles) - fmt.Fprintln(w, "OK") + err = app.PutScrobbles(scrobbles) + if err != nil { + log.Println(err) + fmt.Fprintln(w, "Failed Database") + return } + fmt.Fprintln(w, "OK") } func (app *App) nowPlayingHandler(w http.ResponseWriter, r *http.Request) { @@ -3,7 +3,6 @@ package main import ( "database/sql" "encoding/xml" - "fmt" "log" "time" @@ -20,25 +19,28 @@ type Scrobble struct { Time time.Time `xml:"timestamp" json:"timestamp,string"` Chosen bool `xml:"-" json:"-"` Mbid string `xml:"-" json:"-"` - Session string `xml:"-" json:"-"` + MbidComp string + Session string `xml:"-" json:"-"` + User string Image string } type Session struct { - XMLName xml.Name `json:"-" xml:"session"` - User string `json:"name" xml:"name"` - Key string `json:"key" xml:"key"` - Client string - Protocol string - Created time.Time - Subscriber int64 `json:"subscriber" xml:"subscriber"` + XMLName xml.Name `json:"-" xml:"session"` + User string `json:"name" xml:"name"` + Key string `json:"key" xml:"key"` + Client string + ClientVersion string + Protocol string + Created time.Time + Subscriber int64 `json:"subscriber" xml:"subscriber"` } type DataStore interface { - PutSession(*Session) + PutSession(*Session) error GetSession(key string) (*Session, error) GetPassword(userName string) (string, error) - PutScrobbles([]Scrobble) + PutScrobbles([]Scrobble) error RecentScrobbles(lfmName string) []*Scrobble Api } @@ -47,65 +49,67 @@ type SqlStore struct { *sql.DB } -func NewSession(user string, client string, protocol string) *Session { - return &Session{ - User: user, - Key: randomToken(16), - Client: client, - Protocol: protocol, - Created: time.Now(), - } -} - -func (store *SqlStore) PutSession(s *Session) { - _, err := store.Exec("INSERT INTO scrobbling_sessions VALUES ($1, $2, $3, $4, $5)", - s.User, s.Key, s.Client, s.Protocol, s.Created) - if err != nil { - log.Println(err) - } +func (store *SqlStore) PutSession(s *Session) error { + query := ` + INSERT INTO scrobbling_sessions (lfm_name, session_key, client, client_version, protocol) + VALUES ($1, $2, $3, $4, $5)` + _, err := store.Exec(query, s.User, s.Key, s.Client, s.ClientVersion, s.Protocol) + return err } func (store *SqlStore) GetSession(key string) (*Session, error) { + query := ` + SELECT lfm_name, session_key, client, client_version, protocol, created + FROM scrobbling_sessions WHERE session_key = $1` + row := store.QueryRow(query, key) s := &Session{} - row := store.QueryRow("SELECT * FROM scrobbling_sessions WHERE key = $1", key) - err := row.Scan(&s.User, &s.Key, &s.Client, &s.Protocol, &s.Created) + err := row.Scan(&s.User, &s.Key, &s.Client, &s.ClientVersion, &s.Protocol, + &s.Created) return s, err } func (store *SqlStore) GetPassword(name string) (string, error) { var password string - row := store.QueryRow("SELECT lfm_password FROM users WHERE lfm_name = $1", name) + row := store.QueryRow("SELECT lfm_password FROM users WHERE lfm_name = $1", + name) err := row.Scan(&password) return password, err } -func (store *SqlStore) PutScrobbles(scrobbles []Scrobble) { +func (store *SqlStore) PutScrobbles(scrobbles []Scrobble) error { + tx, err := store.Begin() + if err != nil { + return err + } + query := ` + INSERT INTO scrobbles + (artist, albumartist, trackname, album, tracknumber, duration, time, + chosen, mbid, mbid_computed, session_key, lfm_name) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)` + st, err := tx.Prepare(query) + if err != nil { + return err + } for _, s := range scrobbles { - if _, err := store.Exec( - "INSERT INTO scrobbles VALUES ($1, $2, $3, $4,$5, $6, $7, $8, $9, $10)", - s.Artist, - s.AlbumArtist, - s.TrackName, - s.Album, - s.TrackNumber, - s.Duration, - s.Time, - s.Chosen, - s.Mbid, - s.Session, - ); err != nil { - fmt.Printf("error : %v\n", err) + _, err = st.Exec(s.Artist, s.AlbumArtist, s.TrackName, s.Album, + s.TrackNumber, s.Duration, s.Time, s.Chosen, s.Mbid, s.MbidComp, + s.Session, s.User) + if err != nil { + tx.Rollback() + return err } } + return tx.Commit() } func (store *SqlStore) RecentScrobbles(lfmName string) []*Scrobble { scrobbles := make([]*Scrobble, 0, 10) - rows, err := store.Query("SELECT artist, album, trackname, time, image "+ - "FROM scrobbles JOIN scrobbling_sessions ON session = key "+ - "LEFT JOIN songs ON scrobbles.mbid = songs.mbid "+ - "WHERE lfm_name=$1 ORDER BY time DESC LIMIT 10", - lfmName) + query := ` + SELECT artist, album, trackname, time, image + FROM scrobbles + LEFT JOIN songs ON COALESCE(scrobbles.mbid, scrobbles.mbid_computed) = songs.mbid + WHERE lfm_name=$1 ORDER BY time DESC LIMIT 10` + rows, err := store.Query(query, lfmName) if err != nil { log.Println(err) } @@ -36,7 +36,10 @@ func New() *App { blockKey, err := hex.DecodeString(config.HashKey) s := securecookie.New(hashKey, blockKey) app.CookieHandler = s - templates := template.Must(template.ParseGlob("templates/*.tmpl")) + fmap := template.FuncMap{ + "ago": ago, + } + templates := template.Must(template.New("").Funcs(fmap).ParseGlob("templates/*.tmpl")) app.Template = templates return app } @@ -51,6 +54,7 @@ func logg(fn http.HandlerFunc) http.HandlerFunc { } func main() { + log.SetFlags(log.LstdFlags | log.Lshortfile) app := New() go func() { @@ -83,7 +83,12 @@ func (store *SqlStore) AuthGetSession(r *http.Request) ApiResponse { var response struct { Session *Session `json:"session"` } - session := NewSession("thibauthorel", r.FormValue("api_key"), "2.0") + session := &Session{ + User: "thibauthorel", + Client: r.FormValue("api_key"), + Protocol: "2.0", + Key: randomToken(16), + } store.PutSession(session) response.Session = session return response @@ -1,37 +1,41 @@ +CREATE TABLE IF NOT EXISTS users ( + user_id SERIAL PRIMARY KEY, + type text, + op_id text, + name text, + email text, + lfm_name text UNIQUE, + lfm_password text, + created timestamptz DEFAULT current_timestamp +); + CREATE TABLE IF NOT EXISTS scrobbling_sessions ( - lfm_name text, - key text PRIMARY KEY, + session_key text PRIMARY KEY, + lfm_name text REFERENCES users(lfm_name), client text, + client_version text, protocol text, created timestamptz DEFAULT current_timestamp ); - CREATE TABLE IF NOT EXISTS scrobbles ( - artist text, + artist text, albumartist text, - trackname text, - album text, + trackname text, + album text, tracknumber int, - duration int, - time timestamptz, - chosen bool, - mbid text, - session text -); - -CREATE TABLE IF NOT EXISTS users ( - user_id SERIAL PRIMARY KEY, - type text, - op_id text, - name text, - email text, - lfm_name text, - lfm_password text + duration int, + time timestamptz, + chosen bool, + mbid text, + mbid_computed text, + session_key text REFERENCES scrobbling_sessions, + lfm_name text REFERENCES users(lfm_name) ); CREATE TABLE IF NOT EXISTS user_sessions ( id text PRIMARY KEY, - user_id integer REFERENCES users + user_id integer REFERENCES users, + created timestamptz DEFAULT current_timestamp ); CREATE TABLE IF NOT EXISTS songs ( diff --git a/static/style.css b/static/style.css index fca47c8..e4730aa 100644 --- a/static/style.css +++ b/static/style.css @@ -1,3 +1,17 @@ +/* Rules for sizing the icon. */ +.material-icons.md-18 { font-size: 18px; } +.material-icons.md-24 { font-size: 24px; } +.material-icons.md-36 { font-size: 36px; } +.material-icons.md-48 { font-size: 48px; } + +/* Rules for using icons as black on a light background. */ +.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); } +.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); } + +/* Rules for using icons as white on a dark background. */ +.material-icons.md-light { color: rgba(255, 255, 255, 1); } +.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } + body { font-family: 'Roboto'; font-size: 16px; @@ -49,6 +63,7 @@ ul.scrobbles li { ul.scrobbles li:hover { background-color: #E8EAF6; + cursor: pointer; } ul.scrobbles li img { @@ -70,6 +85,16 @@ ul.scrobbles li .album { color: #4f4f4f; } +ul.scrobbles .like { + margin: 0; + padding: 0; + margin-left: auto; + padding-left: 4px; + box-sizing: border-box; + min-width: 28px; + text-align: right; +} + #center { height: 100%; max-width: 1000px; diff --git a/templates/base.tmpl b/templates/base.tmpl index 2ee087a..07331e2 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -1,4 +1,4 @@ -{{define "header"}} +{{define "header" -}} <!DOCTYPE html> <html lang="en"> <head> @@ -6,17 +6,18 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <title>ListenDiary</title> <link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700" rel="stylesheet"> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link rel="stylesheet" href="static/style.css"> </head> <body> <header> - <div id="center"> - <h1><a href="/">ListenDiary</a></h1> - <a href="/settings" class="user">{{ .Session.UserName }}</a> - </div> + <div id="center"> + <h1><a href="/">ListenDiary</a></h1> + <a href="/settings" class="user">{{ .Session.UserName }}</a> + </div> </header> -<div id="main"> -{{end}} + <div id="main"> +{{- end}} {{define "footer"}} </div> diff --git a/templates/index.tmpl b/templates/index.tmpl index 7ee1801..8468b31 100644 --- a/templates/index.tmpl +++ b/templates/index.tmpl @@ -1,15 +1,18 @@ {{template "header" . }} <h2>Recent Listens</h2> <ul class="scrobbles"> -{{range .Scrobbles}} +{{- range .Scrobbles}} <li> - <img src="{{or .Image "https://lastfm-img2.akamaized.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.png"}}" - height="40"/> + <img src="{{or .Image "https://lastfm-img2.akamaized.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.png"}}"/> <div> - {{.Artist}} — {{.TrackName}}<br/> - <span class="album">in {{.Album}}</span> + {{.Artist}} — {{.TrackName}}<br/> + <span class="album">in {{.Album}}</span> + </div> + <div class="like"> + <span class="album">{{ago .Time}}</span><br/> + <i class="material-icons">favorite_border</i> </div> </li> -{{end}} +{{- end}} </ul> {{template "footer" }} @@ -4,7 +4,9 @@ import ( "crypto/md5" "crypto/rand" "encoding/hex" + "fmt" "net/http" + "time" ) func randomToken(length int) string { @@ -43,3 +45,18 @@ func (app *App) SetCookie(w http.ResponseWriter, name string, v interface{}, exp } http.SetCookie(w, cookie) } + +func ago(t time.Time) string { + delta := time.Since(t) + if delta < time.Minute { + return fmt.Sprintf("%ds ago", int(delta/time.Second)) + } else if delta < time.Hour { + return fmt.Sprintf("%dm ago", int(delta/time.Minute)) + } else if delta < 24*time.Hour { + return fmt.Sprintf("%dh ago", int(delta/time.Hour)) + } else if delta < 5*24*time.Hour { + return fmt.Sprintf("%dd ago", int(delta/(24*time.Hour))) + } else { + return t.Format("Jan 2") + } +} |
