- Add client TZ support + Selectable on user page
- Move token auth to GET ?key=XYZ for wider webhook support
- Add Multiscrobbler support
- Add /api/v1/serverinfo for version information
This commit is contained in:
Daniel Mason 2021-04-03 01:11:05 +13:00
parent 9866dea36b
commit 67e43b8984
Signed by: idanoo
GPG key ID: 387387CDBC02F132
14 changed files with 415 additions and 114 deletions

View file

@ -11,6 +11,10 @@ type Config struct {
Setting map[string]string `json:"configs"`
}
type ServerInfo struct {
Version string `json:"version"`
}
func getAllConfigs() (Config, error) {
config := Config{}
configs := make(map[string]string)

View file

@ -4,63 +4,70 @@ import (
"database/sql"
"fmt"
"net"
"time"
)
type MultiScrobblerInput struct {
Artists []string `json:"artists"`
Album string `json:"album"`
Track string `json:"track"`
PlayedAt time.Time `json:"playDate"`
Duration string `json:"duration"`
}
// ParseMultiScrobblerInput - Transform API data
func ParseMultiScrobblerInput(userUUID string, data map[string]interface{}, ip net.IP, tx *sql.Tx) error {
// Debugging
fmt.Printf("%+v", data)
// if data["ItemType"] != "Audio" {
// return errors.New("Media type not audio")
// }
// // Safety Checks
// if data["Artist"] == nil {
// if data["artists"] == nil {
// return errors.New("Missing artist data")
// }
// if data["Album"] == nil {
// if data["album"] == nil {
// return errors.New("Missing album data")
// }
// if data["Name"] == nil {
// if data["track"] == nil {
// return errors.New("Missing track data")
// }
// // Insert artist if not exist
// artist, err := insertArtist(fmt.Sprintf("%s", data["Artist"]), fmt.Sprintf("%s", data["Provider_musicbrainzartist"]), tx)
// if err != nil {
// log.Printf("%+v", err)
// return errors.New("Failed to map artist")
// }
// // Insert track artists
// for _, artist := range data["artists"] {
// artist, err := insertArtist(artist.Name, "", artist.ID.String(), tx)
// if err != nil {
// log.Printf("%+v", err)
// return errors.New("Failed to map artist: " + artist.Name)
// }
// artists = append(artists, artist.Uuid)
// }
// // Insert album if not exist
// artists := []string{artist.Uuid}
// album, err := insertAlbum(fmt.Sprintf("%s", data["Album"]), fmt.Sprintf("%s", data["Provider_musicbrainzalbum"]), artists, tx)
// album, err := insertAlbum(fmt.Sprintf("%s", data["Album"]), fmt.Sprintf("%s", data["Provider_musicbrainzalbum"]), "", artists, tx)
// if err != nil {
// log.Printf("%+v", err)
// return errors.New("Failed to map album")
// }
// // Insert album if not exist
// track, err := insertTrack(fmt.Sprintf("%s", data["Name"]), fmt.Sprintf("%s", data["Provider_musicbrainztrack"]), album.Uuid, artists, tx)
// // Insert track if not exist
// length := timestampToSeconds(fmt.Sprintf("%s", data["RunTime"]))
// track, err := insertTrack(fmt.Sprintf("%s", data["Name"]), length, fmt.Sprintf("%s", data["Provider_musicbrainztrack"]), "", album.Uuid, artists, tx)
// if err != nil {
// log.Printf("%+v", err)
// return errors.New("Failed to map track")
// }
// // Insert album if not exist
// err = insertScrobble(userUUID, track.Uuid, "jellyfin", ip, tx)
// // Insert scrobble if not exist
// timestamp := time.Now()
// fmt.Println(timestamp)
// err = insertScrobble(userUUID, track.Uuid, "jellyfin", timestamp, ip, tx)
// if err != nil {
// log.Printf("%+v", err)
// return errors.New("Failed to map track")
// }
// _ = album
// _ = artist
// _ = track
// Insert track if not exist
return nil
}

View file

@ -124,7 +124,6 @@ func (user *User) updateSpotifyPlaydata() {
break
}
tx.Commit()
fmt.Printf("Updated spotify track: %+v", v.Track.Name)
}
}

View file

@ -38,7 +38,7 @@ func HandleRequests(port string) {
// JWT Auth - Own profile only (Uses uuid in JWT)
v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(fetchUser), lightLimiter)).Methods("GET")
// v1.HandleFunc("/user", jwtMiddleware(fetchScrobbleResponse)).Methods("PATCH")
v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(patchUser), lightLimiter)).Methods("PATCH")
v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(getSpotifyClientID), lightLimiter)).Methods("GET")
v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(deleteSpotifyLink), lightLimiter)).Methods("DELETE")
v1.HandleFunc("/user/{uuid}/scrobbles", jwtMiddleware(fetchScrobbleResponse)).Methods("GET")
@ -55,6 +55,7 @@ func HandleRequests(port string) {
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
v1.HandleFunc("/sendreset", limitMiddleware(handleSendReset, heavyLimiter)).Methods("POST")
v1.HandleFunc("/resetpassword", limitMiddleware(handleResetPassword, heavyLimiter)).Methods("POST")
v1.HandleFunc("/serverinfo", fetchServerInfo).Methods("GET")
// Redirect from Spotify Oauth
v1.HandleFunc("/link/spotify", limitMiddleware(postSpotifyReponse, lightLimiter))
@ -67,11 +68,12 @@ func HandleRequests(port string) {
r.PathPrefix("/").Handler(spa)
c := cors.New(cors.Options{
// Grrrr CORS. To clean up at a later date
AllowedOrigins: []string{"*"},
AllowCredentials: true,
AllowedMethods: []string{"GET", "POST", "PATCH", "DELETE"},
AllowedHeaders: []string{"*"},
})
handler := c.Handler(r)
// Serve it up!
@ -219,6 +221,7 @@ func handleResetPassword(w http.ResponseWriter, r *http.Request) {
// serveEndpoint - API stuffs
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
bodyJson, err := decodeJson(r.Body)
fmt.Println(err)
if err != nil {
throwInvalidJson(w)
return
@ -291,6 +294,29 @@ func fetchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser s
w.Write(json)
}
// patchUser - Update specific values
func patchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
userFull, err := getUser(jwtUser)
if err != nil {
throwOkError(w, "Failed to fetch user information")
return
}
bodyJson, _ := decodeJson(r.Body)
ip := getUserIp(r)
for k, v := range bodyJson {
val := fmt.Sprintf("%s", v)
if k == "timezone" {
if isValidTimezone(val) {
userFull.updateUser("timezone", val, ip)
}
}
}
throwOkMessage(w, "User updated successfully")
}
// fetchScrobbles - Return an array of scrobbles
func fetchScrobbleResponse(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
resp, err := fetchScrobblesForUser(reqUser, 100, 1)
@ -408,3 +434,13 @@ func deleteSpotifyLink(w http.ResponseWriter, r *http.Request, u string, v strin
throwOkMessage(w, "Spotify account successfully unlinked")
}
func fetchServerInfo(w http.ResponseWriter, r *http.Request) {
info := ServerInfo{
Version: "0.0.11",
}
js, _ := json.Marshal(&info)
w.WriteHeader(http.StatusOK)
w.Write(js)
}

View file

@ -20,14 +20,21 @@ var lightLimiter = NewIPRateLimiter(10, 10)
// tokenMiddleware - Validates token to a user
func tokenMiddleware(next func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fullToken := r.Header.Get("Authorization")
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
if authToken == "" {
key := ""
urlParams := r.URL.Query()
if val, ok := urlParams["key"]; ok {
key = val[0]
} else {
throwUnauthorized(w, "No key parameter provided")
return
}
if key == "" {
throwUnauthorized(w, "A token is required")
return
}
userUuid, err := getUserUuidForToken(authToken)
userUuid, err := getUserUuidForToken(key)
if err != nil {
throwUnauthorized(w, err.Error())
return

View file

@ -156,14 +156,14 @@ func insertUser(username string, email string, password []byte, ip net.IP) error
return err
}
func updateUser(uuid string, field string, value string, ip net.IP) error {
_, err := db.Exec("UPDATE users SET `"+field+"` = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = ?", value, uuid, ip)
func (user *User) updateUser(field string, value string, ip net.IP) error {
_, err := db.Exec("UPDATE users SET `"+field+"` = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = UUID_TO_BIN(?, true)", value, ip, user.UUID)
return err
}
func updateUserDirect(uuid string, field string, value string) error {
_, err := db.Exec("UPDATE users SET `"+field+"` = ? WHERE uuid = ?", value, uuid)
func (user *User) updateUserDirect(field string, value string) error {
_, err := db.Exec("UPDATE users SET `"+field+"` = ? WHERE uuid = UUID_TO_BIN(?, true)", value, user.UUID)
return err
}

View file

@ -10,6 +10,7 @@ import (
"net/http"
"regexp"
"strings"
"time"
)
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
@ -79,6 +80,10 @@ func getUserIp(r *http.Request) net.IP {
}
}
if host == "" {
host = "0.0.0.0"
}
ip = net.ParseIP(host)
return ip
}
@ -144,3 +149,8 @@ func filterSlice(s []string) []string {
fmt.Printf("RESTULS: %+v", result)
return result
}
func isValidTimezone(tz string) bool {
_, err := time.LoadLocation(tz)
return err == nil
}