mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-24 17:35:16 +00:00
Scrobbles work!
This commit is contained in:
parent
74d1fec817
commit
16531f9fa1
@ -8,6 +8,8 @@ REDIS_DB=
|
|||||||
REDIS_PREFIX="gs:"
|
REDIS_PREFIX="gs:"
|
||||||
REDIS_AUTH=""
|
REDIS_AUTH=""
|
||||||
|
|
||||||
|
TIMEZONE=Etc/UTC
|
||||||
|
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
JWT_EXPIRY=86400
|
JWT_EXPIRY=86400
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ There are prebuilt binaries/packages available.
|
|||||||
Copy .env.example to .env and set variables. You can use https://www.grc.com/passwords.htm to generate a JWT_SECRET.
|
Copy .env.example to .env and set variables. You can use https://www.grc.com/passwords.htm to generate a JWT_SECRET.
|
||||||
|
|
||||||
## More documentation
|
## More documentation
|
||||||
|
[Changelog](docs/changelog.md)
|
||||||
|
|
||||||
[Environment Variables](docs/config.md)
|
[Environment Variables](docs/config.md)
|
||||||
|
|
||||||
## Setup MySQL
|
## Setup MySQL
|
||||||
|
3
docs/changelog.md
Normal file
3
docs/changelog.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 0.0.1
|
||||||
|
- Initial commit
|
||||||
|
- Added basic registration/login flow
|
85
internal/goscrobble/album.go
Normal file
85
internal/goscrobble/album.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package goscrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Album struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Desc sql.NullString `json:"desc"`
|
||||||
|
Img sql.NullString `json:"img"`
|
||||||
|
MusicBrainzID sql.NullString `json:"mbid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertAlbum - This will return if it exists or create it based on MBID > Name
|
||||||
|
func insertAlbum(name string, mbid string, artists []string, tx *sql.Tx) (Album, error) {
|
||||||
|
album := Album{}
|
||||||
|
|
||||||
|
if mbid != "" {
|
||||||
|
album = fetchAlbum("mbid", mbid, tx)
|
||||||
|
if album.Uuid == "" {
|
||||||
|
err := insertNewAlbum(name, mbid, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting album via MBID %s %+v", name, err)
|
||||||
|
return album, errors.New("Failed to insert album")
|
||||||
|
}
|
||||||
|
|
||||||
|
album = fetchAlbum("mbid", mbid, tx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
album = fetchAlbum("name", name, tx)
|
||||||
|
if album.Uuid == "" {
|
||||||
|
err := insertNewAlbum(name, mbid, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting album via Name %s %+v", name, err)
|
||||||
|
return album, errors.New("Failed to insert album")
|
||||||
|
}
|
||||||
|
|
||||||
|
album = fetchAlbum("name", name, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if album.Uuid == "" {
|
||||||
|
return album, errors.New("Unable to fetch album!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return album, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAlbum(col string, val string, tx *sql.Tx) Album {
|
||||||
|
var album Album
|
||||||
|
err := tx.QueryRow(
|
||||||
|
"SELECT BIN_TO_UUID(`uuid`, true), `name`, `desc`, `img`, `mbid` FROM `albums` WHERE `"+col+"` = ?",
|
||||||
|
val).Scan(&album.Uuid, &album.Name, &album.Desc, &album.Img, &album.MusicBrainzID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
log.Printf("Error fetching albums: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return album
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertNewAlbum(name string, mbid string, tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("INSERT INTO `albums` (`uuid`, `name`, `mbid`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(UUID(), true),?,?)", name, mbid)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (album *Album) linkAlbumToArtists(artists []string, tx *sql.Tx) error {
|
||||||
|
var err error
|
||||||
|
for _, artist := range artists {
|
||||||
|
_, err = tx.Exec("INSERT INTO `track_artist` (`track`, `artist`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(?, true), UUID_TO_BIN(?, true)", album.Uuid, artist)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
72
internal/goscrobble/artist.go
Normal file
72
internal/goscrobble/artist.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package goscrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Artist struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Desc sql.NullString `json:"desc"`
|
||||||
|
Img sql.NullString `json:"img"`
|
||||||
|
MusicBrainzID sql.NullString `json:"mbid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertArtist - This will return if it exists or create it based on MBID > Name
|
||||||
|
func insertArtist(name string, mbid string, tx *sql.Tx) (Artist, error) {
|
||||||
|
artist := Artist{}
|
||||||
|
|
||||||
|
if mbid != "" {
|
||||||
|
artist = fetchArtist("mbid", mbid, tx)
|
||||||
|
if artist.Uuid == "" {
|
||||||
|
err := insertNewArtist(name, mbid, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting artist via MBID %s %+v", name, err)
|
||||||
|
return artist, errors.New("Failed to insert artist")
|
||||||
|
}
|
||||||
|
|
||||||
|
artist = fetchArtist("mbid", mbid, tx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
artist = fetchArtist("name", name, tx)
|
||||||
|
if artist.Uuid == "" {
|
||||||
|
err := insertNewArtist(name, mbid, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting artist via Name %s %+v", name, err)
|
||||||
|
return artist, errors.New("Failed to insert artist")
|
||||||
|
}
|
||||||
|
|
||||||
|
artist = fetchArtist("name", name, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if artist.Uuid == "" {
|
||||||
|
return artist, errors.New("Unable to fetch artist!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return artist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchArtist(col string, val string, tx *sql.Tx) Artist {
|
||||||
|
var artist Artist
|
||||||
|
err := tx.QueryRow(
|
||||||
|
"SELECT BIN_TO_UUID(`uuid`, true), `name`, `desc`, `img`, `mbid` FROM `artists` WHERE `"+col+"` = ?",
|
||||||
|
val).Scan(&artist.Uuid, &artist.Name, &artist.Desc, &artist.Img, &artist.MusicBrainzID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
log.Printf("Error fetching artists: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return artist
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertNewArtist(name string, mbid string, tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("INSERT INTO `artists` (`uuid`, `name`, `mbid`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(UUID(), true),?,?)", name, mbid)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
@ -22,8 +23,13 @@ func InitDb() {
|
|||||||
dbUser := os.Getenv("MYSQL_USER")
|
dbUser := os.Getenv("MYSQL_USER")
|
||||||
dbPass := os.Getenv("MYSQL_PASS")
|
dbPass := os.Getenv("MYSQL_PASS")
|
||||||
dbName := os.Getenv("MYSQL_DB")
|
dbName := os.Getenv("MYSQL_DB")
|
||||||
|
timeZone := os.Getenv("TIMEZONE")
|
||||||
|
dbTz := ""
|
||||||
|
if timeZone != "" {
|
||||||
|
dbTz = "&loc=" + strings.Replace(timeZone, "/", fmt.Sprintf("%%2F"), 1)
|
||||||
|
}
|
||||||
|
|
||||||
dbConn, err := sql.Open("mysql", dbUser+":"+dbPass+"@tcp("+dbHost+")/"+dbName+"?multiStatements=true")
|
dbConn, err := sql.Open("mysql", dbUser+":"+dbPass+"@tcp("+dbHost+")/"+dbName+"?multiStatements=true"+dbTz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,50 @@
|
|||||||
package goscrobble
|
package goscrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
// ParseJellyfinInput - Transform API data into a common struct
|
// ParseJellyfinInput - Transform API data into a common struct
|
||||||
func ParseJellyfinInput(test string) string {
|
func ParseJellyfinInput(userUUID string, data map[string]interface{}, ip net.IP, tx *sql.Tx) error {
|
||||||
return test
|
log.Printf("%+v : %+v", userUUID, 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 album if not exist
|
||||||
|
artists := []string{artist.Uuid}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%+v", err)
|
||||||
|
return errors.New("Failed to map track")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert album if not exist
|
||||||
|
err = insertScrobble(userUUID, track.Uuid, 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
|
||||||
}
|
}
|
||||||
|
50
internal/goscrobble/scrobble.go
Normal file
50
internal/goscrobble/scrobble.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package goscrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scrobble struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
CreatedIp net.IP `json:"created_ip"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Track string `json:"track"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertScrobble - This will return if it exists or create it based on MBID > Name
|
||||||
|
func insertScrobble(user string, track string, ip net.IP, tx *sql.Tx) error {
|
||||||
|
err := insertNewScrobble(user, track, ip, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting scrobble %s %+v", user, err)
|
||||||
|
return errors.New("Failed to insert scrobble!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchScrobble(col string, val string, tx *sql.Tx) Scrobble {
|
||||||
|
var scrobble Scrobble
|
||||||
|
err := tx.QueryRow(
|
||||||
|
"SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `user`, `track` FROM `scrobbles` WHERE `"+col+"` = ?",
|
||||||
|
val).Scan(&scrobble.Uuid, &scrobble.CreatedAt, &scrobble.CreatedIp, &scrobble.User, &scrobble.Track)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
log.Printf("Error fetching scrobbles: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrobble
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertNewScrobble(user string, track string, ip net.IP, tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("INSERT INTO `scrobbles` (`uuid`, `created_at`, `created_ip`, `user`, `track`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(UUID(), true), NOW(), ?, UUID_TO_BIN(?, true),UUID_TO_BIN(?, true))", ip, user, track)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
@ -25,10 +26,10 @@ type jsonResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limits to 1 req / 10 sec
|
// Limits to 1 req / 10 sec
|
||||||
var heavyLimiter = NewIPRateLimiter(0.1, 1)
|
var heavyLimiter = NewIPRateLimiter(0.25, 2)
|
||||||
|
|
||||||
// Limits to 5 req / sec
|
// Limits to 5 req / sec
|
||||||
var standardLimiter = NewIPRateLimiter(5, 5)
|
var standardLimiter = NewIPRateLimiter(1, 5)
|
||||||
|
|
||||||
// List of Reverse proxies
|
// List of Reverse proxies
|
||||||
var ReverseProxies []string
|
var ReverseProxies []string
|
||||||
@ -45,15 +46,16 @@ func HandleRequests(port string) {
|
|||||||
v1 := r.PathPrefix("/api/v1").Subrouter()
|
v1 := r.PathPrefix("/api/v1").Subrouter()
|
||||||
|
|
||||||
// Static Token for /ingress
|
// Static Token for /ingress
|
||||||
v1.HandleFunc("/ingress/jellyfin", tokenMiddleware(serveEndpoint))
|
v1.HandleFunc("/ingress/jellyfin", tokenMiddleware(handleIngress))
|
||||||
|
|
||||||
// JWT Auth
|
// JWT Auth
|
||||||
v1.HandleFunc("/profile/{id}", jwtMiddleware(serveEndpoint))
|
// v1.HandleFunc("/profile/{id}", jwtMiddleware(handleIngress))
|
||||||
|
|
||||||
// No Auth
|
// No Auth
|
||||||
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
|
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
|
||||||
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
|
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
|
||||||
v1.HandleFunc("/logout", serveEndpoint).Methods("POST")
|
// For now just trash JWT in frontend until we have full state management "Good enough"
|
||||||
|
// v1.HandleFunc("/logout", handleIngress).Methods("POST")
|
||||||
|
|
||||||
// This just prevents it serving frontend stuff over /api
|
// This just prevents it serving frontend stuff over /api
|
||||||
r.PathPrefix("/api")
|
r.PathPrefix("/api")
|
||||||
@ -126,9 +128,21 @@ func generateJsonError(m string) []byte {
|
|||||||
// tokenMiddleware - Validates token to a user
|
// tokenMiddleware - Validates token to a user
|
||||||
func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
throwUnauthorized(w, "Invalid API Token")
|
fullToken := r.Header.Get("Authorization")
|
||||||
return
|
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
|
||||||
// next(res, req)
|
if authToken == "" {
|
||||||
|
throwUnauthorized(w, "A token is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
userUuid, err := getUserForToken(authToken)
|
||||||
|
if err != nil {
|
||||||
|
throwUnauthorized(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lets tack this on the request for now..
|
||||||
|
r.Header.Set("UserUUID", userUuid)
|
||||||
|
next(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,8 +150,7 @@ func tokenMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|||||||
func jwtMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
func jwtMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
throwUnauthorized(w, "Invalid JWT Token")
|
throwUnauthorized(w, "Invalid JWT Token")
|
||||||
return
|
next(w, r)
|
||||||
// next(res, req)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +188,7 @@ func handleRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := generateJsonMessage("User created succesfully")
|
msg := generateJsonMessage("User created succesfully. You may now login")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
w.Write(msg)
|
w.Write(msg)
|
||||||
}
|
}
|
||||||
@ -202,14 +215,35 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// serveEndpoint - API stuffs
|
// serveEndpoint - API stuffs
|
||||||
func serveEndpoint(w http.ResponseWriter, r *http.Request) {
|
func handleIngress(w http.ResponseWriter, r *http.Request) {
|
||||||
_, err := decodeJson(r.Body)
|
bodyJson, err := decodeJson(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't decode. Lets tell them nicely.
|
// If we can't decode. Lets tell them nicely.
|
||||||
http.Error(w, "{\"error\":\"Invalid JSON\"}", http.StatusBadRequest)
|
http.Error(w, "{\"error\":\"Invalid JSON\"}", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ingressType := strings.Replace(r.URL.Path, "/api/v1/ingress/", "", 1)
|
||||||
|
log.Println(ingressType)
|
||||||
|
switch ingressType {
|
||||||
|
case "jellyfin":
|
||||||
|
tx, _ := db.Begin()
|
||||||
|
ip := getUserIp(r)
|
||||||
|
err := ParseJellyfinInput(r.Header.Get("UserUUID"), bodyJson, ip, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting track: %+v", err)
|
||||||
|
tx.Rollback()
|
||||||
|
throwBadReq(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
throwBadReq(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throwOkMessage(w, "{}")
|
||||||
|
return
|
||||||
|
}
|
||||||
// Lets trick 'em for now ;) ;)
|
// Lets trick 'em for now ;) ;)
|
||||||
fmt.Fprintf(w, "{}")
|
fmt.Fprintf(w, "{}")
|
||||||
}
|
}
|
||||||
|
25
internal/goscrobble/tokens.go
Normal file
25
internal/goscrobble/tokens.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package goscrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
func generateToken(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserForToken(token string) (string, error) {
|
||||||
|
var uuid string
|
||||||
|
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true) FROM `users` WHERE `token` = ? AND `active` = 1", token).Scan(&uuid)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("Invalid Token")
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
}
|
112
internal/goscrobble/track.go
Normal file
112
internal/goscrobble/track.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package goscrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Track struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Desc sql.NullString `json:"desc"`
|
||||||
|
Img sql.NullString `json:"img"`
|
||||||
|
MusicBrainzID sql.NullString `json:"mbid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertTrack - This will return if it exists or create it based on MBID > Name
|
||||||
|
func insertTrack(name string, mbid string, album string, artists []string, tx *sql.Tx) (Track, error) {
|
||||||
|
track := Track{}
|
||||||
|
|
||||||
|
if mbid != "" {
|
||||||
|
track = fetchTrack("mbid", mbid, tx)
|
||||||
|
if track.Uuid == "" {
|
||||||
|
err := insertNewTrack(name, mbid, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting track via MBID %s %+v", name, err)
|
||||||
|
return track, errors.New("Failed to insert track")
|
||||||
|
}
|
||||||
|
|
||||||
|
track = fetchTrack("mbid", mbid, tx)
|
||||||
|
err = track.linkTrack(album, artists, tx)
|
||||||
|
if err != nil {
|
||||||
|
return track, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
track = fetchTrack("name", name, tx)
|
||||||
|
if track.Uuid == "" {
|
||||||
|
err := insertNewTrack(name, mbid, tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error inserting track via Name %s %+v", name, err)
|
||||||
|
return track, errors.New("Failed to insert track")
|
||||||
|
}
|
||||||
|
|
||||||
|
track = fetchTrack("name", name, tx)
|
||||||
|
err = track.linkTrack(album, artists, tx)
|
||||||
|
if err != nil {
|
||||||
|
return track, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if track.Uuid == "" {
|
||||||
|
return track, errors.New("Unable to fetch track!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return track, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchTrack(col string, val string, tx *sql.Tx) Track {
|
||||||
|
var track Track
|
||||||
|
err := tx.QueryRow(
|
||||||
|
"SELECT BIN_TO_UUID(`uuid`, true), `name`, `desc`, `img`, `mbid` FROM `tracks` WHERE `"+col+"` = ?",
|
||||||
|
val).Scan(&track.Uuid, &track.Name, &track.Desc, &track.Img, &track.MusicBrainzID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
log.Printf("Error fetching tracks: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return track
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertNewTrack(name string, mbid string, tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("INSERT INTO `tracks` (`uuid`, `name`, `mbid`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(UUID(), true),?,?)", name, mbid)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (track *Track) linkTrack(album string, artists []string, tx *sql.Tx) error {
|
||||||
|
err := track.linkTrackToAlbum(album, tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = track.linkTrackToArtists(artists, tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (track Track) linkTrackToAlbum(albumUuid string, tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("INSERT INTO `track_album` (`track`, `album`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(?, true), UUID_TO_BIN(?, true))", track.Uuid, albumUuid)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (track Track) linkTrackToArtists(artists []string, tx *sql.Tx) error {
|
||||||
|
var err error
|
||||||
|
for _, artist := range artists {
|
||||||
|
_, err = tx.Exec("INSERT INTO `track_artist` (`track`, `artist`) "+
|
||||||
|
"VALUES (UUID_TO_BIN(?, true),UUID_TO_BIN(?, true))", track.Uuid, artist)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -153,13 +153,14 @@ func generateJwt(user User) (string, error) {
|
|||||||
|
|
||||||
// insertUser - Does the dirtywork!
|
// insertUser - Does the dirtywork!
|
||||||
func insertUser(username string, email string, password []byte, ip net.IP) error {
|
func insertUser(username string, email string, password []byte, ip net.IP) error {
|
||||||
_, err := db.Exec("INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, email, password) "+
|
token := generateToken(32)
|
||||||
"VALUES (UUID_TO_BIN(UUID(), true),NOW(),?,NOW(),?,?,?,?)", ip, ip, username, email, password)
|
_, err := db.Exec("INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, token) "+
|
||||||
|
"VALUES (UUID_TO_BIN(UUID(), true),NOW(),?,NOW(),?,?,?,?,?)", ip, ip, username, email, password, token)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUser(uuid string, field string, value string, ip string) error {
|
func updateUser(uuid string, field string, value string, ip net.IP) error {
|
||||||
_, err := db.Exec("UPDATE users SET ? = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = ?", field, value, uuid, ip)
|
_, err := db.Exec("UPDATE users SET ? = ?, modified_at = NOW(), modified_ip = ? WHERE uuid = ?", field, value, uuid, ip)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -44,11 +45,14 @@ func getUserIp(r *http.Request) net.IP {
|
|||||||
var ip net.IP
|
var ip net.IP
|
||||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||||
if contains(ReverseProxies, host) {
|
if contains(ReverseProxies, host) {
|
||||||
host = r.Header.Get("X-FOWARDED-FOR")
|
forwardedFor := r.Header.Get("X-FOWARDED-FOR")
|
||||||
|
if forwardedFor != "" {
|
||||||
|
host = forwardedFor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ip = net.ParseIP(host)
|
ip = net.ParseIP(host)
|
||||||
|
log.Printf("%+v", ip)
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,23 +9,23 @@ CREATE TABLE IF NOT EXISTS `links` (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `artists` (
|
CREATE TABLE IF NOT EXISTS `artists` (
|
||||||
`uuid` BINARY(16) PRIMARY KEY,
|
`uuid` BINARY(16) PRIMARY KEY,
|
||||||
`name` BINARY(16) NOT NULL,
|
`name` VARCHAR(255) NOT NULL,
|
||||||
`desc` TEXT,
|
`desc` TEXT,
|
||||||
`img` VARCHAR(255) DEFAULT ''
|
`img` VARCHAR(255) DEFAULT NULL
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `albums` (
|
CREATE TABLE IF NOT EXISTS `albums` (
|
||||||
`uuid` BINARY(16) PRIMARY KEY,
|
`uuid` BINARY(16) PRIMARY KEY,
|
||||||
`name` BINARY(16) NOT NULL,
|
`name` VARCHAR(255) NOT NULL,
|
||||||
`desc` TEXT,
|
`desc` TEXT,
|
||||||
`img` VARCHAR(255) DEFAULT ''
|
`img` VARCHAR(255) DEFAULT NULL
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `tracks` (
|
CREATE TABLE IF NOT EXISTS `tracks` (
|
||||||
`uuid` BINARY(16) PRIMARY KEY,
|
`uuid` BINARY(16) PRIMARY KEY,
|
||||||
`name` BINARY(16) NOT NULL,
|
`name` VARCHAR(255) NOT NULL,
|
||||||
`desc` TEXT,
|
`desc` TEXT,
|
||||||
`img` VARCHAR(255) DEFAULT ''
|
`img` VARCHAR(255) DEFAULT NULL
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `scrobbles` (
|
CREATE TABLE IF NOT EXISTS `scrobbles` (
|
||||||
@ -40,14 +40,6 @@ CREATE TABLE IF NOT EXISTS `scrobbles` (
|
|||||||
FOREIGN KEY (user) REFERENCES users(uuid)
|
FOREIGN KEY (user) REFERENCES users(uuid)
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `track_artist` (
|
|
||||||
`track` BINARY(16) NOT NULL,
|
|
||||||
`artist` BINARY(16) NOT NULL,
|
|
||||||
PRIMARY KEY (`track`, `artist`),
|
|
||||||
FOREIGN KEY (track) REFERENCES tracks(uuid),
|
|
||||||
FOREIGN KEY (artist) REFERENCES artists(uuid)
|
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `album_artist` (
|
CREATE TABLE IF NOT EXISTS `album_artist` (
|
||||||
`album` BINARY(16) NOT NULL,
|
`album` BINARY(16) NOT NULL,
|
||||||
`artist` BINARY(16) NOT NULL,
|
`artist` BINARY(16) NOT NULL,
|
||||||
@ -56,4 +48,20 @@ CREATE TABLE IF NOT EXISTS `album_artist` (
|
|||||||
FOREIGN KEY (artist) REFERENCES artists(uuid)
|
FOREIGN KEY (artist) REFERENCES artists(uuid)
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `track_album` (
|
||||||
|
`track` BINARY(16) NOT NULL,
|
||||||
|
`album` BINARY(16) NOT NULL,
|
||||||
|
PRIMARY KEY (`track`, `album`),
|
||||||
|
FOREIGN KEY (track) REFERENCES tracks(uuid),
|
||||||
|
FOREIGN KEY (album) REFERENCES albums(uuid)
|
||||||
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `track_artist` (
|
||||||
|
`track` BINARY(16) NOT NULL,
|
||||||
|
`artist` BINARY(16) NOT NULL,
|
||||||
|
PRIMARY KEY (`track`, `artist`),
|
||||||
|
FOREIGN KEY (track) REFERENCES tracks(uuid),
|
||||||
|
FOREIGN KEY (artist) REFERENCES artists(uuid)
|
||||||
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
7
migrations/4_mbid.down.sql
Normal file
7
migrations/4_mbid.down.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE albums DROP COLUMN `mbid`;
|
||||||
|
ALTER TABLE artists DROP COLUMN `mbid`;
|
||||||
|
ALTER TABLE tracks DROP COLUMN `mbid`;
|
||||||
|
|
||||||
|
COMMIT;
|
1
migrations/5_apitoken.down.sql
Normal file
1
migrations/5_apitoken.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `users` DROP COLUMN `token`;
|
1
migrations/5_apitoken.up.sql
Normal file
1
migrations/5_apitoken.up.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `users` ADD COLUMN `token` VARCHAR(32) NOT NULL;
|
@ -1,14 +0,0 @@
|
|||||||
package goscrobble
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.m2.nz/go-scrobble/internal/goscrobble"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseJellyfinInput(t *testing.T) {
|
|
||||||
got := goscrobble.ParseJellyfinInput("TestString!")
|
|
||||||
if got != "TestString!" {
|
|
||||||
t.Errorf("ParseJellyfinInput returned: %s", got)
|
|
||||||
}
|
|
||||||
}
|
|
21
web/package-lock.json
generated
21
web/package-lock.json
generated
@ -21,6 +21,7 @@
|
|||||||
"react-redux": "^7.2.3",
|
"react-redux": "^7.2.3",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
|
"react-spinners": "^0.10.6",
|
||||||
"react-toast": "^1.0.1",
|
"react-toast": "^1.0.1",
|
||||||
"react-toast-notifications": "^2.4.3",
|
"react-toast-notifications": "^2.4.3",
|
||||||
"reactstrap": "^8.9.0",
|
"reactstrap": "^8.9.0",
|
||||||
@ -16679,6 +16680,18 @@
|
|||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-spinners": {
|
||||||
|
"version": "0.10.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.10.6.tgz",
|
||||||
|
"integrity": "sha512-UPLcaMFhFnLWtS1zVDDT14ssW08gnZ44cjPN6GL27dEGboiCvRSthOilZXRDWESp449GB2iI6gjEbxzaMtA+dg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/core": "^10.0.35"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.0.0 || ^17.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-toast": {
|
"node_modules/react-toast": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
||||||
@ -35210,6 +35223,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-spinners": {
|
||||||
|
"version": "0.10.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.10.6.tgz",
|
||||||
|
"integrity": "sha512-UPLcaMFhFnLWtS1zVDDT14ssW08gnZ44cjPN6GL27dEGboiCvRSthOilZXRDWESp449GB2iI6gjEbxzaMtA+dg==",
|
||||||
|
"requires": {
|
||||||
|
"@emotion/core": "^10.0.35"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-toast": {
|
"react-toast": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-toast/-/react-toast-1.0.1.tgz",
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"react-redux": "^7.2.3",
|
"react-redux": "^7.2.3",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
|
"react-spinners": "^0.10.6",
|
||||||
"react-toast": "^1.0.1",
|
"react-toast": "^1.0.1",
|
||||||
"react-toast-notifications": "^2.4.3",
|
"react-toast-notifications": "^2.4.3",
|
||||||
"reactstrap": "^8.9.0",
|
"reactstrap": "^8.9.0",
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
import Home from './Components/Pages/Home';
|
import Home from './Components/Pages/Home';
|
||||||
import About from './Components/Pages/About';
|
import About from './Components/Pages/About';
|
||||||
|
import Help from './Components/Pages/Help';
|
||||||
import Login from './Components/Pages/Login';
|
import Login from './Components/Pages/Login';
|
||||||
import Settings from './Components/Pages/Settings';
|
import Settings from './Components/Pages/Settings';
|
||||||
import Register from './Components/Pages/Register';
|
import Register from './Components/Pages/Register';
|
||||||
import Navigation from './Components/Navigation';
|
import Navigation from './Components/Navigation';
|
||||||
|
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch, withRouter } from 'react-router-dom';
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
|
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ const App = () => {
|
|||||||
<Route exact={exact} path="/" component={Home} />
|
<Route exact={exact} path="/" component={Home} />
|
||||||
<Route path="/about" component={About} />
|
<Route path="/about" component={About} />
|
||||||
<Route path="/settings" component={Settings} />
|
<Route path="/settings" component={Settings} />
|
||||||
|
<Route path="/help" component={Help} />
|
||||||
<Route path="/login" component={Login} />
|
<Route path="/login" component={Login} />
|
||||||
<Route path="/register" component={Register} />
|
<Route path="/register" component={Register} />
|
||||||
</Switch>
|
</Switch>
|
||||||
@ -39,4 +41,4 @@ const App = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
|
||||||
|
@ -5,6 +5,7 @@ import './Navigation.css';
|
|||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
'Home',
|
'Home',
|
||||||
|
'Help',
|
||||||
'About',
|
'About',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ class Navigation extends Component {
|
|||||||
} else {
|
} else {
|
||||||
return <div className="navLinkLogin">
|
return <div className="navLinkLogin">
|
||||||
<Link to="/login" className="navLink">Login</Link>
|
<Link to="/login" className="navLink">Login</Link>
|
||||||
<Link to="/register" className="navLink">Register</Link>
|
<Link to="/register" className="navLink" history={this.props.history}>Register</Link>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
web/src/Components/Pages/Help.css
Normal file
4
web/src/Components/Pages/Help.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.helpBody {
|
||||||
|
padding: 20px 5px 5px 5px;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
17
web/src/Components/Pages/Help.js
Normal file
17
web/src/Components/Pages/Help.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import '../../App.css';
|
||||||
|
import './Help.css';
|
||||||
|
|
||||||
|
function Help() {
|
||||||
|
return (
|
||||||
|
<div className="pageWrapper">
|
||||||
|
<h1>
|
||||||
|
Help Docs
|
||||||
|
</h1>
|
||||||
|
<p className="helpBody">
|
||||||
|
Jellyfin Configuration<br/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Help;
|
@ -4,6 +4,7 @@ import './Login.css';
|
|||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import { Formik, Form, Field } from 'formik';
|
import { Formik, Form, Field } from 'formik';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import ScaleLoader from "react-spinners/ScaleLoader";
|
||||||
|
|
||||||
function withToast(Component) {
|
function withToast(Component) {
|
||||||
return function WrappedComponent(props) {
|
return function WrappedComponent(props) {
|
||||||
@ -42,7 +43,14 @@ class Login extends React.Component {
|
|||||||
};
|
};
|
||||||
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/login';
|
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/login';
|
||||||
fetch(apiUrl, requestOptions)
|
fetch(apiUrl, requestOptions)
|
||||||
.then((response) => response.json())
|
.then((response) => {
|
||||||
|
if (response.status === 429) {
|
||||||
|
this.props.addToast("Rate limited. Please try again soon", { appearance: 'error' });
|
||||||
|
return "{}"
|
||||||
|
} else {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
})
|
||||||
.then((function(data) {
|
.then((function(data) {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
this.props.addToast(data.error, { appearance: 'error' });
|
this.props.addToast(data.error, { appearance: 'error' });
|
||||||
@ -95,7 +103,7 @@ class Login extends React.Component {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="loginButton"
|
className="loginButton"
|
||||||
disabled={this.state.loading}
|
disabled={this.state.loading}
|
||||||
>Login</Button>
|
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Login"}</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,8 @@ import '../../App.css';
|
|||||||
import './Login.css';
|
import './Login.css';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import ScaleLoader from "react-spinners/ScaleLoader";
|
||||||
|
import { withRouter } from 'react-router-dom'
|
||||||
|
|
||||||
function withToast(Component) {
|
function withToast(Component) {
|
||||||
return function WrappedComponent(props) {
|
return function WrappedComponent(props) {
|
||||||
@ -66,13 +68,21 @@ class Register extends React.Component {
|
|||||||
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/register';
|
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/register';
|
||||||
console.log(apiUrl);
|
console.log(apiUrl);
|
||||||
fetch(apiUrl, requestOptions)
|
fetch(apiUrl, requestOptions)
|
||||||
.then((response) => response.json())
|
.then((response) => {
|
||||||
|
if (response.status === 429) {
|
||||||
|
this.props.addToast("Rate limited. Please try again soon", { appearance: 'error' });
|
||||||
|
return "{}"
|
||||||
|
} else {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
})
|
||||||
.then((function(data) {
|
.then((function(data) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
this.props.addToast(data.error, { appearance: 'error' });
|
this.props.addToast(data.error, { appearance: 'error' });
|
||||||
} else {
|
} else if (data.message) {
|
||||||
this.props.addToast(data.message, { appearance: 'success' });
|
this.props.addToast(data.message, { appearance: 'success' });
|
||||||
|
this.props.history.push('/login')
|
||||||
}
|
}
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
}).bind(this))
|
}).bind(this))
|
||||||
@ -119,7 +129,7 @@ class Register extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
<label>
|
<label>
|
||||||
Password<br/>
|
Password*<br/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
required={trueBool}
|
required={trueBool}
|
||||||
@ -130,7 +140,7 @@ class Register extends React.Component {
|
|||||||
</label>
|
</label>
|
||||||
<br/>
|
<br/>
|
||||||
<label>
|
<label>
|
||||||
Password<br/>
|
Password*<br/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
required={trueBool}
|
required={trueBool}
|
||||||
@ -145,7 +155,7 @@ class Register extends React.Component {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="loginButton"
|
className="loginButton"
|
||||||
disabled={this.state.loading}
|
disabled={this.state.loading}
|
||||||
>Login</Button>
|
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -155,4 +165,4 @@ class Register extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withToast(Register);
|
export default withRouter(withToast(Register));
|
||||||
|
@ -16,7 +16,7 @@ const store = createStore(goScorbbleStore);
|
|||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<ToastProvider autoDismiss="true" autoDismissTimeout="5000" placement="bottom-right">
|
<ToastProvider autoDismiss="true" autoDismissTimeout="6000" placement="bottom-right">
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
|
Loading…
Reference in New Issue
Block a user