2021-03-23 08:43:44 +00:00
|
|
|
package goscrobble
|
|
|
|
|
|
|
|
import (
|
2021-03-24 10:07:46 +00:00
|
|
|
"encoding/json"
|
2021-03-23 08:43:44 +00:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
2021-04-02 09:24:00 +00:00
|
|
|
"os"
|
2021-03-28 08:52:34 +00:00
|
|
|
"strings"
|
2021-04-09 21:49:32 +00:00
|
|
|
"time"
|
2021-03-23 08:43:44 +00:00
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2021-03-26 08:06:28 +00:00
|
|
|
"github.com/rs/cors"
|
2021-03-23 08:43:44 +00:00
|
|
|
)
|
|
|
|
|
2021-03-25 05:15:01 +00:00
|
|
|
type jsonResponse struct {
|
2021-04-01 12:56:08 +00:00
|
|
|
Err string `json:"error,omitempty"`
|
|
|
|
Msg string `json:"message,omitempty"`
|
|
|
|
Valid bool `json:"valid,omitempty"`
|
2021-03-25 05:15:01 +00:00
|
|
|
}
|
|
|
|
|
2021-03-25 23:21:28 +00:00
|
|
|
// List of Reverse proxies
|
|
|
|
var ReverseProxies []string
|
|
|
|
|
2021-04-09 21:49:32 +00:00
|
|
|
// RequestRequest - Incoming JSON!
|
|
|
|
type RequestRequest struct {
|
|
|
|
URL string `json:"url"`
|
|
|
|
Token string `json:"token"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type RequestResponse struct {
|
|
|
|
Token string `json:"token,omitempty"`
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
2021-03-26 08:06:28 +00:00
|
|
|
func enableCors(w *http.ResponseWriter) {
|
|
|
|
(*w).Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
}
|
|
|
|
|
2021-03-24 09:28:05 +00:00
|
|
|
// HandleRequests - Boot HTTP!
|
2021-03-27 06:33:27 +00:00
|
|
|
func HandleRequests(port string) {
|
2021-03-24 09:28:05 +00:00
|
|
|
// Create a new router
|
2021-03-24 10:07:46 +00:00
|
|
|
r := mux.NewRouter().StrictSlash(true)
|
2021-03-24 03:29:35 +00:00
|
|
|
|
2021-03-24 10:07:46 +00:00
|
|
|
v1 := r.PathPrefix("/api/v1").Subrouter()
|
2021-03-24 03:29:35 +00:00
|
|
|
|
2021-03-25 05:15:01 +00:00
|
|
|
// Static Token for /ingress
|
2021-04-02 09:24:00 +00:00
|
|
|
v1.HandleFunc("/ingress/jellyfin", limitMiddleware(tokenMiddleware(handleIngress), lightLimiter)).Methods("POST")
|
|
|
|
v1.HandleFunc("/ingress/multiscrobbler", limitMiddleware(tokenMiddleware(handleIngress), lightLimiter)).Methods("POST")
|
2021-03-24 09:28:05 +00:00
|
|
|
|
2021-04-02 09:24:00 +00:00
|
|
|
// JWT Auth - Own profile only (Uses uuid in JWT)
|
2021-04-04 09:54:53 +00:00
|
|
|
v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(getUser), lightLimiter)).Methods("GET")
|
2021-04-02 12:11:05 +00:00
|
|
|
v1.HandleFunc("/user", limitMiddleware(jwtMiddleware(patchUser), lightLimiter)).Methods("PATCH")
|
2021-04-09 21:49:32 +00:00
|
|
|
v1.HandleFunc("/user/navidrome", limitMiddleware(jwtMiddleware(postNavidrome), lightLimiter)).Methods("POST")
|
|
|
|
v1.HandleFunc("/user/navidrome", limitMiddleware(jwtMiddleware(deleteNavidrome), lightLimiter)).Methods("DELETE")
|
2021-04-02 09:24:00 +00:00
|
|
|
v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(getSpotifyClientID), lightLimiter)).Methods("GET")
|
2021-04-09 21:49:32 +00:00
|
|
|
v1.HandleFunc("/user/spotify", limitMiddleware(jwtMiddleware(deleteSpotify), lightLimiter)).Methods("DELETE")
|
2021-04-04 09:54:53 +00:00
|
|
|
v1.HandleFunc("/user/{uuid}/scrobbles", jwtMiddleware(getScrobbles)).Methods("GET")
|
2021-03-25 05:15:01 +00:00
|
|
|
|
2021-03-31 08:40:20 +00:00
|
|
|
// Config auth
|
2021-04-04 09:54:53 +00:00
|
|
|
v1.HandleFunc("/config", limitMiddleware(adminMiddleware(getConfig), standardLimiter)).Methods("GET")
|
2021-04-02 09:24:00 +00:00
|
|
|
v1.HandleFunc("/config", limitMiddleware(adminMiddleware(postConfig), standardLimiter)).Methods("POST")
|
2021-03-31 08:40:20 +00:00
|
|
|
|
2021-03-25 05:15:01 +00:00
|
|
|
// No Auth
|
2021-04-02 09:24:00 +00:00
|
|
|
v1.HandleFunc("/stats", limitMiddleware(handleStats, lightLimiter)).Methods("GET")
|
2021-04-04 09:54:53 +00:00
|
|
|
v1.HandleFunc("/profile/{username}", limitMiddleware(getProfile, lightLimiter)).Methods("GET")
|
2021-04-08 07:50:43 +00:00
|
|
|
v1.HandleFunc("/artists/top/{uuid}", limitMiddleware(getArtists, lightLimiter)).Methods("GET")
|
|
|
|
v1.HandleFunc("/artists/{uuid}", limitMiddleware(getArtist, lightLimiter)).Methods("GET")
|
|
|
|
v1.HandleFunc("/albums/top/{uuid}", limitMiddleware(getArtists, lightLimiter)).Methods("GET")
|
|
|
|
v1.HandleFunc("/albums/{uuid}", limitMiddleware(getAlbum, lightLimiter)).Methods("GET")
|
|
|
|
v1.HandleFunc("/tracks/top/{uuid}", limitMiddleware(getTracks, lightLimiter)).Methods("GET")
|
|
|
|
v1.HandleFunc("/tracks/{uuid}", limitMiddleware(getTrack, lightLimiter)).Methods("GET")
|
2021-04-01 10:17:46 +00:00
|
|
|
|
2021-03-25 10:30:35 +00:00
|
|
|
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
|
2021-03-25 23:21:28 +00:00
|
|
|
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
|
2021-04-01 12:56:08 +00:00
|
|
|
v1.HandleFunc("/sendreset", limitMiddleware(handleSendReset, heavyLimiter)).Methods("POST")
|
|
|
|
v1.HandleFunc("/resetpassword", limitMiddleware(handleResetPassword, heavyLimiter)).Methods("POST")
|
2021-04-04 09:54:53 +00:00
|
|
|
v1.HandleFunc("/serverinfo", getServerInfo).Methods("GET")
|
2021-04-06 08:28:04 +00:00
|
|
|
v1.HandleFunc("/refresh", limitMiddleware(handleTokenRefresh, standardLimiter)).Methods("POST")
|
2021-03-24 10:07:46 +00:00
|
|
|
|
2021-04-02 09:24:00 +00:00
|
|
|
// Redirect from Spotify Oauth
|
|
|
|
v1.HandleFunc("/link/spotify", limitMiddleware(postSpotifyReponse, lightLimiter))
|
|
|
|
|
2021-03-25 05:15:01 +00:00
|
|
|
// This just prevents it serving frontend stuff over /api
|
2021-03-24 10:07:46 +00:00
|
|
|
r.PathPrefix("/api")
|
2021-03-24 09:28:05 +00:00
|
|
|
|
|
|
|
// SERVE FRONTEND - NO AUTH
|
2021-03-24 04:24:53 +00:00
|
|
|
spa := spaHandler{staticPath: "web/build", indexPath: "index.html"}
|
2021-03-24 10:07:46 +00:00
|
|
|
r.PathPrefix("/").Handler(spa)
|
2021-03-24 03:29:35 +00:00
|
|
|
|
2021-03-26 08:06:28 +00:00
|
|
|
c := cors.New(cors.Options{
|
|
|
|
AllowedOrigins: []string{"*"},
|
|
|
|
AllowCredentials: true,
|
2021-04-02 12:11:05 +00:00
|
|
|
AllowedMethods: []string{"GET", "POST", "PATCH", "DELETE"},
|
2021-03-29 07:56:34 +00:00
|
|
|
AllowedHeaders: []string{"*"},
|
2021-03-26 08:06:28 +00:00
|
|
|
})
|
2021-04-02 12:11:05 +00:00
|
|
|
|
2021-03-26 08:06:28 +00:00
|
|
|
handler := c.Handler(r)
|
|
|
|
|
2021-03-24 09:28:05 +00:00
|
|
|
// Serve it up!
|
2021-03-27 06:33:27 +00:00
|
|
|
fmt.Printf("Goscrobble listening on port %s", port)
|
2021-04-02 09:24:00 +00:00
|
|
|
fmt.Println("")
|
|
|
|
|
2021-03-27 06:33:27 +00:00
|
|
|
log.Fatal(http.ListenAndServe(":"+port, handler))
|
2021-03-23 08:43:44 +00:00
|
|
|
}
|
|
|
|
|
2021-03-25 10:09:17 +00:00
|
|
|
// API ENDPOINT HANDLING
|
|
|
|
// handleRegister - Does as it says!
|
|
|
|
func handleRegister(w http.ResponseWriter, r *http.Request) {
|
2021-04-03 01:16:13 +00:00
|
|
|
cachedRegistrationEnabled := getRedisVal("REGISTRATION_ENABLED")
|
|
|
|
if cachedRegistrationEnabled == "" {
|
|
|
|
registrationEnabled, err := getConfigValue("REGISTRATION_ENABLED")
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Error checking if registration is enabled")
|
|
|
|
}
|
|
|
|
setRedisVal("REGISTRATION_ENABLED", registrationEnabled)
|
|
|
|
cachedRegistrationEnabled = registrationEnabled
|
|
|
|
}
|
|
|
|
|
|
|
|
if cachedRegistrationEnabled == "0" {
|
|
|
|
throwOkError(w, "Registration is currently disabled")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-09 21:49:32 +00:00
|
|
|
regReq := RequestRequest{}
|
2021-03-25 10:09:17 +00:00
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
err := decoder.Decode(®Req)
|
|
|
|
if err != nil {
|
|
|
|
throwBadReq(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-03-25 23:21:28 +00:00
|
|
|
ip := getUserIp(r)
|
|
|
|
err = createUser(®Req, ip)
|
2021-03-25 10:09:17 +00:00
|
|
|
if err != nil {
|
2021-03-30 08:36:28 +00:00
|
|
|
throwOkError(w, err.Error())
|
2021-03-25 10:09:17 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-03-29 07:56:34 +00:00
|
|
|
throwOkMessage(w, "User created succesfully. You may now login")
|
2021-03-25 10:09:17 +00:00
|
|
|
}
|
2021-03-25 05:15:01 +00:00
|
|
|
|
2021-03-25 23:21:28 +00:00
|
|
|
// handleLogin - Does as it says!
|
|
|
|
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
2021-04-09 21:49:32 +00:00
|
|
|
logReq := RequestRequest{}
|
2021-03-25 23:21:28 +00:00
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
err := decoder.Decode(&logReq)
|
|
|
|
if err != nil {
|
|
|
|
throwBadReq(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := getUserIp(r)
|
|
|
|
data, err := loginUser(&logReq, ip)
|
|
|
|
if err != nil {
|
2021-03-29 07:56:34 +00:00
|
|
|
throwOkError(w, err.Error())
|
2021-03-25 23:21:28 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(data)
|
|
|
|
}
|
|
|
|
|
2021-04-06 08:28:04 +00:00
|
|
|
// handleTokenRefresh - Refresh access token based on refresh token
|
|
|
|
func handleTokenRefresh(w http.ResponseWriter, r *http.Request) {
|
2021-04-09 21:49:32 +00:00
|
|
|
logReq := RequestRequest{}
|
2021-04-06 08:28:04 +00:00
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
err := decoder.Decode(&logReq)
|
|
|
|
user, err := isValidRefreshToken(logReq.Token)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Invalid refresh token")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issue JWT + Response
|
|
|
|
token, err := generateJWTToken(user, logReq.Token)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to refresh Token")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-09 21:49:32 +00:00
|
|
|
loginResp := RequestResponse{
|
2021-04-06 08:28:04 +00:00
|
|
|
Token: token,
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, _ := json.Marshal(&loginResp)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(resp)
|
|
|
|
}
|
|
|
|
|
2021-03-30 02:02:04 +00:00
|
|
|
// handleStats - Returns stats for homepage
|
|
|
|
func handleStats(w http.ResponseWriter, r *http.Request) {
|
|
|
|
stats, err := getStats()
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
js, _ := json.Marshal(&stats)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(js)
|
|
|
|
}
|
|
|
|
|
2021-04-01 12:56:08 +00:00
|
|
|
// handleSendReset - Does as it says!
|
|
|
|
func handleSendReset(w http.ResponseWriter, r *http.Request) {
|
2021-04-09 21:49:32 +00:00
|
|
|
req := RequestRequest{}
|
2021-04-01 12:56:08 +00:00
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
err := decoder.Decode(&req)
|
|
|
|
if err != nil {
|
|
|
|
throwBadReq(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.Email == "" {
|
|
|
|
throwOkError(w, "Invalid Email")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = getUserIp(r)
|
|
|
|
user, err := getUserByEmail(req.Email)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := getUserIp(r)
|
|
|
|
err = user.sendResetEmail(ip)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "Password reset email sent")
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleSendReset - Does as it says!
|
|
|
|
func handleResetPassword(w http.ResponseWriter, r *http.Request) {
|
|
|
|
bodyJson, err := decodeJson(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
throwInvalidJson(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if bodyJson["password"] == nil {
|
|
|
|
// validating
|
|
|
|
valid, err := checkResetToken(fmt.Sprintf("%s", bodyJson["token"]))
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
jr := jsonResponse{
|
|
|
|
Valid: valid,
|
|
|
|
}
|
|
|
|
msg, _ := json.Marshal(&jr)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(msg)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
// resetting
|
|
|
|
token := fmt.Sprintf("%s", bodyJson["token"])
|
|
|
|
pw := fmt.Sprintf("%s", bodyJson["password"])
|
|
|
|
if len(pw) < 8 {
|
|
|
|
throwOkError(w, "Password must be at least 8 characters")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := getUserIp(r)
|
|
|
|
user, err := getUserByResetToken(token)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
err = user.updatePassword(pw, ip)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "Password updated successfully!")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 05:15:01 +00:00
|
|
|
// serveEndpoint - API stuffs
|
2021-03-29 07:56:34 +00:00
|
|
|
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
|
2021-04-02 12:44:11 +00:00
|
|
|
var err error
|
2021-04-01 12:56:08 +00:00
|
|
|
ip := getUserIp(r)
|
|
|
|
tx, _ := db.Begin()
|
|
|
|
|
2021-03-28 08:52:34 +00:00
|
|
|
ingressType := strings.Replace(r.URL.Path, "/api/v1/ingress/", "", 1)
|
2021-03-28 09:17:39 +00:00
|
|
|
|
2021-03-28 08:52:34 +00:00
|
|
|
switch ingressType {
|
|
|
|
case "jellyfin":
|
2021-04-02 13:48:38 +00:00
|
|
|
jfInput := JellyfinRequest{}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&jfInput)
|
2021-04-02 12:44:11 +00:00
|
|
|
if err != nil {
|
|
|
|
throwInvalidJson(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-02 13:48:38 +00:00
|
|
|
err = ParseJellyfinInput(userUuid, jfInput, ip, tx)
|
2021-03-28 08:52:34 +00:00
|
|
|
if err != nil {
|
2021-04-03 08:29:31 +00:00
|
|
|
fmt.Println(err)
|
2021-03-28 08:52:34 +00:00
|
|
|
tx.Rollback()
|
2021-04-01 10:17:46 +00:00
|
|
|
throwOkError(w, err.Error())
|
2021-03-28 08:52:34 +00:00
|
|
|
return
|
|
|
|
}
|
2021-04-01 12:56:08 +00:00
|
|
|
case "multiscrobbler":
|
2021-04-02 13:48:38 +00:00
|
|
|
msInput := MultiScrobblerRequest{}
|
2021-04-02 12:44:11 +00:00
|
|
|
err := json.NewDecoder(r.Body).Decode(&msInput)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
tx.Rollback()
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ParseMultiScrobblerInput(userUuid, msInput, ip, tx)
|
2021-03-28 08:52:34 +00:00
|
|
|
if err != nil {
|
2021-04-02 12:44:11 +00:00
|
|
|
fmt.Println(err)
|
2021-04-01 12:56:08 +00:00
|
|
|
tx.Rollback()
|
2021-04-01 10:17:46 +00:00
|
|
|
throwOkError(w, err.Error())
|
2021-03-28 08:52:34 +00:00
|
|
|
return
|
|
|
|
}
|
2021-04-01 12:56:08 +00:00
|
|
|
default:
|
|
|
|
tx.Rollback()
|
|
|
|
throwBadReq(w, "Unknown ingress type")
|
|
|
|
}
|
2021-03-24 10:07:46 +00:00
|
|
|
|
2021-04-01 12:56:08 +00:00
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
2021-03-28 08:52:34 +00:00
|
|
|
return
|
|
|
|
}
|
2021-03-28 09:24:18 +00:00
|
|
|
|
2021-04-01 12:56:08 +00:00
|
|
|
throwOkMessage(w, "success")
|
|
|
|
return
|
2021-03-23 08:43:44 +00:00
|
|
|
}
|
2021-03-24 03:29:35 +00:00
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
// getUser - Return personal userprofile
|
2021-04-06 08:28:04 +00:00
|
|
|
func getUser(w http.ResponseWriter, r *http.Request, claims CustomClaims, reqUser string) {
|
|
|
|
jwtUser := claims.Subject
|
2021-04-04 09:54:53 +00:00
|
|
|
userFull, err := getUserByUUID(jwtUser)
|
2021-04-01 10:17:46 +00:00
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to fetch user information")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonFull, _ := json.Marshal(&userFull)
|
|
|
|
|
|
|
|
// Lets strip out vars we don't want to send.
|
|
|
|
user := UserResponse{}
|
|
|
|
err = json.Unmarshal(jsonFull, &user)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to fetch user information")
|
|
|
|
return
|
|
|
|
}
|
2021-04-02 09:24:00 +00:00
|
|
|
|
2021-04-09 21:49:32 +00:00
|
|
|
oauthNavi, err := getOauthToken(user.UUID, "navidrome")
|
2021-04-02 09:24:00 +00:00
|
|
|
if err == nil {
|
2021-04-09 21:49:32 +00:00
|
|
|
user.NavidromeURL = oauthNavi.URL
|
|
|
|
}
|
|
|
|
|
|
|
|
oauthSpotify, err := getOauthToken(user.UUID, "spotify")
|
|
|
|
if err == nil {
|
|
|
|
user.SpotifyUsername = oauthSpotify.Username
|
2021-04-02 09:24:00 +00:00
|
|
|
}
|
|
|
|
|
2021-04-01 10:17:46 +00:00
|
|
|
json, _ := json.Marshal(&user)
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
2021-04-02 12:11:05 +00:00
|
|
|
// patchUser - Update specific values
|
2021-04-06 08:28:04 +00:00
|
|
|
func patchUser(w http.ResponseWriter, r *http.Request, claims CustomClaims, reqUser string) {
|
|
|
|
jwtUser := claims.Subject
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
userFull, err := getUserByUUID(jwtUser)
|
2021-04-02 12:11:05 +00:00
|
|
|
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)
|
|
|
|
}
|
2021-04-03 00:54:07 +00:00
|
|
|
} else if k == "token" {
|
|
|
|
token := generateToken(32)
|
|
|
|
userFull.updateUser("token", token, ip)
|
2021-04-02 12:11:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "User updated successfully")
|
|
|
|
}
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
// getScrobbles - Return an array of scrobbles
|
2021-04-06 08:28:04 +00:00
|
|
|
func getScrobbles(w http.ResponseWriter, r *http.Request, claims CustomClaims, reqUser string) {
|
2021-04-04 09:54:53 +00:00
|
|
|
resp, err := getScrobblesForUser(reqUser, 100, 1)
|
2021-03-29 07:56:34 +00:00
|
|
|
if err != nil {
|
2021-04-01 10:17:46 +00:00
|
|
|
throwOkError(w, "Failed to fetch scrobbles")
|
2021-03-29 07:56:34 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch last 500 scrobbles
|
|
|
|
json, _ := json.Marshal(&resp)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
// getConfig - Return an array of scrobbles
|
|
|
|
func getConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
|
2021-03-31 08:40:20 +00:00
|
|
|
config, err := getAllConfigs()
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to fetch scrobbles")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&config)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
// postConfig - Return an array of scrobbles
|
2021-03-31 08:40:20 +00:00
|
|
|
func postConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
|
|
|
|
bodyJson, err := decodeJson(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
throwInvalidJson(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range bodyJson {
|
2021-04-03 00:54:07 +00:00
|
|
|
val := fmt.Sprintf("%s", v)
|
|
|
|
err = updateConfigValue(k, val)
|
2021-03-31 08:40:20 +00:00
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
2021-04-03 00:54:07 +00:00
|
|
|
setRedisVal(k, val)
|
2021-03-31 08:40:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "Config updated successfully")
|
|
|
|
}
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
// getProfile - Returns public user profile data
|
|
|
|
func getProfile(w http.ResponseWriter, r *http.Request) {
|
2021-04-01 10:17:46 +00:00
|
|
|
var username string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "username" {
|
|
|
|
username = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if username == "" {
|
|
|
|
throwOkError(w, "Invalid Username")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
user, err := getUserByUsername(username)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
resp, err := getProfileForUser(user)
|
2021-04-01 10:17:46 +00:00
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&resp)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
2021-04-02 09:24:00 +00:00
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
// getArtist - Returns artist data
|
|
|
|
func getArtist(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var uuid string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "uuid" {
|
|
|
|
uuid = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
throwOkError(w, "Invalid UUID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
artist, err := getArtistByUUID(uuid)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&artist)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAlbum - Returns album data
|
|
|
|
func getAlbum(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var uuid string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "uuid" {
|
|
|
|
uuid = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
throwOkError(w, "Invalid UUID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
album, err := getAlbumByUUID(uuid)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&album)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getTrack - Returns track data
|
|
|
|
func getTrack(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var uuid string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "uuid" {
|
|
|
|
uuid = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
throwOkError(w, "Invalid UUID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
track, err := getTrackByUUID(uuid)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&track)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
2021-04-08 07:00:13 +00:00
|
|
|
// getArtists - Returns artist data for a user
|
|
|
|
func getArtists(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var uuid string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "uuid" {
|
|
|
|
uuid = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
throwOkError(w, "Invalid UUID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-08 08:46:31 +00:00
|
|
|
track, err := getTopArtists(uuid)
|
2021-04-08 07:00:13 +00:00
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-08 08:46:31 +00:00
|
|
|
json, _ := json.Marshal(&track)
|
2021-04-08 07:00:13 +00:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAlbums - Returns album data for a user
|
|
|
|
func getAlbums(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var uuid string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "uuid" {
|
|
|
|
uuid = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
throwOkError(w, "Invalid UUID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
album, err := getAlbumByUUID(uuid)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&album)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getTracks - Returns track data for a user
|
|
|
|
func getTracks(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var uuid string
|
|
|
|
for k, v := range mux.Vars(r) {
|
|
|
|
if k == "uuid" {
|
|
|
|
uuid = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if uuid == "" {
|
|
|
|
throwOkError(w, "Invalid UUID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
track, err := getTopTracks(uuid)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
json, _ := json.Marshal(&track)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(json)
|
|
|
|
}
|
|
|
|
|
2021-04-02 09:24:00 +00:00
|
|
|
// postSpotifyResponse - Oauth Response from Spotify
|
|
|
|
func postSpotifyReponse(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := connectSpotifyResponse(r)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to connect to spotify")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
http.Redirect(w, r, os.Getenv("GOSCROBBLE_DOMAIN")+"/user", 302)
|
|
|
|
}
|
|
|
|
|
|
|
|
// getSpotifyClientID - Returns public spotify APP ID
|
2021-04-06 08:28:04 +00:00
|
|
|
func getSpotifyClientID(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) {
|
2021-04-02 09:24:00 +00:00
|
|
|
key, err := getConfigValue("SPOTIFY_APP_ID")
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to get Spotify ID")
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
2021-04-09 21:49:32 +00:00
|
|
|
response := RequestResponse{
|
2021-04-02 09:24:00 +00:00
|
|
|
Token: key,
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, _ := json.Marshal(&response)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(resp)
|
|
|
|
}
|
|
|
|
|
2021-04-09 21:49:32 +00:00
|
|
|
// deleteSpotify - Unlinks spotify account
|
|
|
|
func deleteSpotify(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) {
|
2021-04-06 08:28:04 +00:00
|
|
|
jwtUser := claims.Subject
|
|
|
|
err := removeOauthToken(jwtUser, "spotify")
|
2021-04-02 09:24:00 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
throwOkError(w, "Failed to unlink spotify account")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "Spotify account successfully unlinked")
|
|
|
|
}
|
2021-04-02 12:11:05 +00:00
|
|
|
|
2021-04-09 21:49:32 +00:00
|
|
|
// postNavidrome - Submits data for navidrome URL/User/Password
|
|
|
|
func postNavidrome(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) {
|
|
|
|
jwtUser := claims.Subject
|
|
|
|
|
|
|
|
request := RequestRequest{}
|
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
err := decoder.Decode(&request)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Invalid JSON")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// hash password with salt
|
|
|
|
salt := generateToken(32)
|
|
|
|
hash := getMd5(request.Password + salt)
|
|
|
|
|
|
|
|
err = validateNavidromeConnection(request.URL, request.Username, hash, salt)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to validate credentials")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lets set this back 30min
|
|
|
|
time := time.Now().UTC().Add(-(time.Duration(30) * time.Minute))
|
|
|
|
err = insertOauthToken(jwtUser, "navidrome", hash, salt, time, request.Username, time, request.URL)
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Failed to save Navidome token")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "Successfully saved!")
|
|
|
|
}
|
|
|
|
|
|
|
|
// deleteNavidrome - Unlinks Navidrome account
|
|
|
|
func deleteNavidrome(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) {
|
|
|
|
jwtUser := claims.Subject
|
|
|
|
err := removeOauthToken(jwtUser, "navidrome")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
throwOkError(w, "Failed to unlink navidrome account")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
throwOkMessage(w, "Navidrome account successfully unlinked")
|
|
|
|
}
|
|
|
|
|
2021-04-04 09:54:53 +00:00
|
|
|
func getServerInfo(w http.ResponseWriter, r *http.Request) {
|
2021-04-03 00:54:07 +00:00
|
|
|
cachedRegistrationEnabled := getRedisVal("REGISTRATION_ENABLED")
|
|
|
|
if cachedRegistrationEnabled == "" {
|
|
|
|
registrationEnabled, err := getConfigValue("REGISTRATION_ENABLED")
|
|
|
|
if err != nil {
|
|
|
|
throwOkError(w, "Error fetching serverinfo")
|
|
|
|
}
|
|
|
|
setRedisVal("REGISTRATION_ENABLED", registrationEnabled)
|
|
|
|
cachedRegistrationEnabled = registrationEnabled
|
|
|
|
}
|
|
|
|
|
2021-04-02 12:11:05 +00:00
|
|
|
info := ServerInfo{
|
2021-04-11 05:26:48 +00:00
|
|
|
Version: "0.0.28",
|
2021-04-03 00:54:07 +00:00
|
|
|
RegistrationEnabled: cachedRegistrationEnabled,
|
2021-04-02 12:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
js, _ := json.Marshal(&info)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(js)
|
|
|
|
}
|