mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-22 00:21:55 +00:00
0.0.16
- Add registration_enabled to /api/v1/serverinfo - Add config table caching on save - Fix redis TTL not being parsed correctly - Move registration enabled to backend toggle - Fixed navbar when loading /u/profile URL - Token now shows on user page + can reset - Added basic popup validation to disconnect/reset buttons
This commit is contained in:
parent
f8bd321fbc
commit
9cbb94fc56
@ -3,7 +3,7 @@ stages:
|
|||||||
- bundle
|
- bundle
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
VERSION: 0.0.15
|
VERSION: 0.0.16
|
||||||
|
|
||||||
build-go:
|
build-go:
|
||||||
image: golang:1.16.2
|
image: golang:1.16.2
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
# 0.0.16
|
||||||
|
- Add registration_enabled to /api/v1/serverinfo
|
||||||
|
- Add config table caching on save
|
||||||
|
- Fix redis TTL not being parsed correctly
|
||||||
|
- Move registration enabled to backend toggle
|
||||||
|
- Fixed navbar when loading /u/profile URL
|
||||||
|
- Token now shows on user page + can reset
|
||||||
|
- Added basic popup validation to disconnect/reset buttons
|
||||||
|
|
||||||
# 0.0.15
|
# 0.0.15
|
||||||
- Fix spotify track duration
|
- Fix spotify track duration
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ GoScrobble runs as UTC and connects to MySQL as UTC. All timezone handling is do
|
|||||||
## FRONTEND VARS
|
## FRONTEND VARS
|
||||||
These are stored in `web/.env.production` and `web/.env.development`
|
These are stored in `web/.env.production` and `web/.env.development`
|
||||||
|
|
||||||
REACT_APP_REGISTRATION_DISABLED=true // Disables registration
|
|
||||||
REACT_APP_API_URL=https://goscrobble.com // Sets API URL
|
REACT_APP_API_URL=https://goscrobble.com // Sets API URL
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServerInfo struct {
|
type ServerInfo struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
RegistrationEnabled string `json:"registration_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAllConfigs() (Config, error) {
|
func getAllConfigs() (Config, error) {
|
||||||
|
@ -2,7 +2,6 @@ package goscrobble
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -21,10 +20,9 @@ type MultiScrobblerRequest struct {
|
|||||||
// ParseMultiScrobblerInput - Transform API data
|
// ParseMultiScrobblerInput - Transform API data
|
||||||
func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip net.IP, tx *sql.Tx) error {
|
func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip net.IP, tx *sql.Tx) error {
|
||||||
// Cache key
|
// Cache key
|
||||||
json, _ := json.Marshal(data)
|
json := fmt.Sprintf("%s:%s:%s:%s", data.PlayedAt, data.Track, data.Album, userUUID)
|
||||||
redisKey := getMd5(string(json) + userUUID)
|
redisKey := getMd5(json)
|
||||||
if getRedisKeyExists(redisKey) {
|
if getRedisKeyExists(redisKey) {
|
||||||
fmt.Printf("Prevented duplicate entry!")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +61,7 @@ func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip ne
|
|||||||
return errors.New("Failed to map track")
|
return errors.New("Failed to map track")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cache key for the duration of the song *2 since we're caching the start time too
|
ttl := time.Duration(30) * time.Minute
|
||||||
ttl := time.Duration(data.Duration*2) * time.Second
|
|
||||||
setRedisValTtl(redisKey, "1", ttl)
|
setRedisValTtl(redisKey, "1", ttl)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -58,7 +58,7 @@ func setRedisVal(key string, val string) error {
|
|||||||
|
|
||||||
// setRedisTtl - Allows custom TTL
|
// setRedisTtl - Allows custom TTL
|
||||||
func setRedisValTtl(key string, val string, ttl time.Duration) error {
|
func setRedisValTtl(key string, val string, ttl time.Duration) error {
|
||||||
return redisDb.Set(ctx, redisPrefix+key, val, 0).Err()
|
return redisDb.Set(ctx, redisPrefix+key, val, ttl).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRedisVal - Returns value if exists
|
// getRedisVal - Returns value if exists
|
||||||
|
@ -54,16 +54,7 @@ func fetchScrobblesForUser(userUuid string, limit int, page int) (ScrobbleRespon
|
|||||||
|
|
||||||
// Yeah this isn't great. But for now.. it works! Cache later
|
// Yeah this isn't great. But for now.. it works! Cache later
|
||||||
total, err := getDbCount(
|
total, err := getDbCount(
|
||||||
"SELECT COUNT(*) FROM `scrobbles` "+
|
"SELECT COUNT(*) FROM `scrobbles` WHERE `user` = UUID_TO_BIN(?, true) ", userUuid)
|
||||||
"JOIN tracks ON scrobbles.track = tracks.uuid "+
|
|
||||||
"JOIN track_artist ON track_artist.track = tracks.uuid "+
|
|
||||||
"JOIN track_album ON track_album.track = tracks.uuid "+
|
|
||||||
"JOIN artists ON track_artist.artist = artists.uuid "+
|
|
||||||
"JOIN albums ON track_album.album = albums.uuid "+
|
|
||||||
"JOIN users ON scrobbles.user = users.uuid "+
|
|
||||||
"WHERE user = UUID_TO_BIN(?, true) "+
|
|
||||||
"GROUP BY scrobbles.uuid",
|
|
||||||
userUuid)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to fetch scrobble count: %+v", err)
|
log.Printf("Failed to fetch scrobble count: %+v", err)
|
||||||
@ -71,7 +62,7 @@ func fetchScrobblesForUser(userUuid string, limit int, page int) (ScrobbleRespon
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows, err := db.Query(
|
rows, err := db.Query(
|
||||||
"SELECT BIN_TO_UUID(`scrobbles`.`uuid`, true), `scrobbles`.`created_at`, `artists`.`name`, `albums`.`name`,`tracks`.`name`, `scrobbles`.`source` FROM `scrobbles` "+
|
"SELECT BIN_TO_UUID(`scrobbles`.`uuid`, true), `scrobbles`.`created_at`, GROUP_CONCAT(`artists`.`name` separator ','), `albums`.`name`, `tracks`.`name`, `scrobbles`.`source` FROM `scrobbles` "+
|
||||||
"JOIN tracks ON scrobbles.track = tracks.uuid "+
|
"JOIN tracks ON scrobbles.track = tracks.uuid "+
|
||||||
"JOIN track_artist ON track_artist.track = tracks.uuid "+
|
"JOIN track_artist ON track_artist.track = tracks.uuid "+
|
||||||
"JOIN track_album ON track_album.track = tracks.uuid "+
|
"JOIN track_album ON track_album.track = tracks.uuid "+
|
||||||
@ -79,8 +70,10 @@ func fetchScrobblesForUser(userUuid string, limit int, page int) (ScrobbleRespon
|
|||||||
"JOIN albums ON track_album.album = albums.uuid "+
|
"JOIN albums ON track_album.album = albums.uuid "+
|
||||||
"JOIN users ON scrobbles.user = users.uuid "+
|
"JOIN users ON scrobbles.user = users.uuid "+
|
||||||
"WHERE user = UUID_TO_BIN(?, true) "+
|
"WHERE user = UUID_TO_BIN(?, true) "+
|
||||||
|
"GROUP BY scrobbles.uuid, albums.uuid "+
|
||||||
"ORDER BY scrobbles.created_at DESC LIMIT ?",
|
"ORDER BY scrobbles.created_at DESC LIMIT ?",
|
||||||
userUuid, limit)
|
userUuid, limit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to fetch scrobbles: %+v", err)
|
log.Printf("Failed to fetch scrobbles: %+v", err)
|
||||||
return scrobbleReq, errors.New("Failed to fetch scrobbles")
|
return scrobbleReq, errors.New("Failed to fetch scrobbles")
|
||||||
|
@ -321,6 +321,9 @@ func patchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser s
|
|||||||
if isValidTimezone(val) {
|
if isValidTimezone(val) {
|
||||||
userFull.updateUser("timezone", val, ip)
|
userFull.updateUser("timezone", val, ip)
|
||||||
}
|
}
|
||||||
|
} else if k == "token" {
|
||||||
|
token := generateToken(32)
|
||||||
|
userFull.updateUser("token", token, ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,11 +366,13 @@ func postConfig(w http.ResponseWriter, r *http.Request, jwtUser string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range bodyJson {
|
for k, v := range bodyJson {
|
||||||
err = updateConfigValue(k, fmt.Sprintf("%s", v))
|
val := fmt.Sprintf("%s", v)
|
||||||
|
err = updateConfigValue(k, val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwOkError(w, err.Error())
|
throwOkError(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setRedisVal(k, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
throwOkMessage(w, "Config updated successfully")
|
throwOkMessage(w, "Config updated successfully")
|
||||||
@ -446,8 +451,19 @@ func deleteSpotifyLink(w http.ResponseWriter, r *http.Request, u string, v strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchServerInfo(w http.ResponseWriter, r *http.Request) {
|
func fetchServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
info := ServerInfo{
|
info := ServerInfo{
|
||||||
Version: "0.0.15",
|
Version: "0.0.16",
|
||||||
|
RegistrationEnabled: cachedRegistrationEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
js, _ := json.Marshal(&info)
|
js, _ := json.Marshal(&info)
|
||||||
|
@ -29,6 +29,7 @@ type User struct {
|
|||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Admin bool `json:"admin"`
|
Admin bool `json:"admin"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserResponse struct {
|
type UserResponse struct {
|
||||||
@ -42,6 +43,7 @@ type UserResponse struct {
|
|||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
SpotifyUsername string `json:"spotify_username"`
|
SpotifyUsername string `json:"spotify_username"`
|
||||||
Timezone string `json:"timezone"`
|
Timezone string `json:"timezone"`
|
||||||
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterRequest - Incoming JSON
|
// RegisterRequest - Incoming JSON
|
||||||
@ -211,8 +213,8 @@ func userAlreadyExists(req *RegisterRequest) bool {
|
|||||||
|
|
||||||
func getUser(uuid string) (User, error) {
|
func getUser(uuid string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone` FROM `users` WHERE `uuid` = UUID_TO_BIN(?, true) AND `active` = 1",
|
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone`, `token` FROM `users` WHERE `uuid` = UUID_TO_BIN(?, true) AND `active` = 1",
|
||||||
uuid).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
|
uuid).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone, &user.Token)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return user, errors.New("Invalid JWT Token")
|
return user, errors.New("Invalid JWT Token")
|
||||||
@ -223,8 +225,8 @@ func getUser(uuid string) (User, error) {
|
|||||||
|
|
||||||
func getUserByUsername(username string) (User, error) {
|
func getUserByUsername(username string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone` FROM `users` WHERE `username` = ? AND `active` = 1",
|
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone`, `token` FROM `users` WHERE `username` = ? AND `active` = 1",
|
||||||
username).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
|
username).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone, &user.Token)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return user, errors.New("Invalid Username")
|
return user, errors.New("Invalid Username")
|
||||||
@ -235,8 +237,8 @@ func getUserByUsername(username string) (User, error) {
|
|||||||
|
|
||||||
func getUserByEmail(email string) (User, error) {
|
func getUserByEmail(email string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone` FROM `users` WHERE `email` = ? AND `active` = 1",
|
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone`, `token` FROM `users` WHERE `email` = ? AND `active` = 1",
|
||||||
email).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
|
email).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone, &user.Token)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return user, errors.New("Invalid Email")
|
return user, errors.New("Invalid Email")
|
||||||
@ -247,9 +249,9 @@ func getUserByEmail(email string) (User, error) {
|
|||||||
|
|
||||||
func getUserByResetToken(token string) (User, error) {
|
func getUserByResetToken(token string) (User, error) {
|
||||||
var user User
|
var user User
|
||||||
err := db.QueryRow("SELECT BIN_TO_UUID(`users`.`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, timezone FROM `users` "+
|
err := db.QueryRow("SELECT BIN_TO_UUID(`users`.`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `timezone`, `token` FROM `users` "+
|
||||||
"JOIN `resettoken` ON `resettoken`.`user` = `users`.`uuid` WHERE `resettoken`.`token` = ? AND `active` = 1",
|
"JOIN `resettoken` ON `resettoken`.`user` = `users`.`uuid` WHERE `resettoken`.`token` = ? AND `active` = 1",
|
||||||
token).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
|
token).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone, &user.Token)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return user, errors.New("Invalid Token")
|
return user, errors.New("Invalid Token")
|
||||||
|
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `users` (
|
|||||||
`active` TINYINT(1) NOT NULL DEFAULT 1,
|
`active` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
`admin` TINYINT(1) NOT NULL DEFAULT 0,
|
`admin` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
`private` TINYINT(1) NOT NULL DEFAULT 0,
|
`private` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
`timezone` VARCHAR(100) NOT NULL DEFAULT 'Etc/UTC',
|
`timezone` VARCHAR(100) NOT NULL DEFAULT 'Pacific/Auckland',
|
||||||
KEY `usernameLookup` (`username`, `active`),
|
KEY `usernameLookup` (`username`, `active`),
|
||||||
KEY `emailLookup` (`email`, `active`)
|
KEY `emailLookup` (`email`, `active`)
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
@ -1,2 +1 @@
|
|||||||
REACT_APP_API_URL=http://127.0.0.1:42069/api/v1/
|
REACT_APP_API_URL=http://127.0.0.1:42069/api/v1/
|
||||||
REACT_APP_REGISTRATION_DISABLED=false
|
|
35
web/package-lock.json
generated
35
web/package-lock.json
generated
@ -20,6 +20,7 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-bootstrap": "^1.5.2",
|
"react-bootstrap": "^1.5.2",
|
||||||
|
"react-confirm-alert": "^2.7.0",
|
||||||
"react-cookie": "^4.0.3",
|
"react-cookie": "^4.0.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
@ -27,6 +28,7 @@
|
|||||||
"react-spinners": "^0.10.6",
|
"react-spinners": "^0.10.6",
|
||||||
"react-timezone-select": "^0.10.7",
|
"react-timezone-select": "^0.10.7",
|
||||||
"react-toastify": "^7.0.3",
|
"react-toastify": "^7.0.3",
|
||||||
|
"reactjs-popup": "^2.0.4",
|
||||||
"reactstrap": "^8.9.0"
|
"reactstrap": "^8.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -16633,6 +16635,15 @@
|
|||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-confirm-alert": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-confirm-alert/-/react-confirm-alert-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-21NWtGK/e85+ZX3TLRpMc3IsU5Kj6Z9ElCOrkTIlwMzV9EancyXNlkqHGbtKP63a2iS6g5hOxROokmJOqKQiXA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.0.0",
|
||||||
|
"react-dom": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-cookie": {
|
"node_modules/react-cookie": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.3.tgz",
|
||||||
@ -17101,6 +17112,18 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reactjs-popup": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-G5jTXL2JkClKAYAdqedf+K9QvbNsFWvdbrXW1/vWiyanuCU/d7DtQzQux+uKOz2HeNVRsFQHvs7abs0Z7VLAhg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16",
|
||||||
|
"react-dom": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reactstrap": {
|
"node_modules/reactstrap": {
|
||||||
"version": "8.9.0",
|
"version": "8.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.9.0.tgz",
|
||||||
@ -35553,6 +35576,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-confirm-alert": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-confirm-alert/-/react-confirm-alert-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-21NWtGK/e85+ZX3TLRpMc3IsU5Kj6Z9ElCOrkTIlwMzV9EancyXNlkqHGbtKP63a2iS6g5hOxROokmJOqKQiXA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-cookie": {
|
"react-cookie": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.3.tgz",
|
||||||
@ -35934,6 +35963,12 @@
|
|||||||
"prop-types": "^15.6.2"
|
"prop-types": "^15.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"reactjs-popup": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-G5jTXL2JkClKAYAdqedf+K9QvbNsFWvdbrXW1/vWiyanuCU/d7DtQzQux+uKOz2HeNVRsFQHvs7abs0Z7VLAhg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"reactstrap": {
|
"reactstrap": {
|
||||||
"version": "8.9.0",
|
"version": "8.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.9.0.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-bootstrap": "^1.5.2",
|
"react-bootstrap": "^1.5.2",
|
||||||
|
"react-confirm-alert": "^2.7.0",
|
||||||
"react-cookie": "^4.0.3",
|
"react-cookie": "^4.0.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
@ -22,6 +23,7 @@
|
|||||||
"react-spinners": "^0.10.6",
|
"react-spinners": "^0.10.6",
|
||||||
"react-timezone-select": "^0.10.7",
|
"react-timezone-select": "^0.10.7",
|
||||||
"react-toastify": "^7.0.3",
|
"react-toastify": "^7.0.3",
|
||||||
|
"reactjs-popup": "^2.0.4",
|
||||||
"reactstrap": "^8.9.0"
|
"reactstrap": "^8.9.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -243,3 +243,21 @@ export const spotifyDisonnectionRequest = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getServerInfo = () => {
|
||||||
|
return axios.get(process.env.REACT_APP_API_URL + "serverinfo")
|
||||||
|
.then((data) => {
|
||||||
|
return data.data
|
||||||
|
}).catch((error) => {
|
||||||
|
return handleErrorResp(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const resetScrobbleToken = () => {
|
||||||
|
return axios.patch(process.env.REACT_APP_API_URL + "user", { token: "" }, { headers: getHeaders() })
|
||||||
|
.then((data) => {
|
||||||
|
return data.data
|
||||||
|
}).catch((error) => {
|
||||||
|
return handleErrorResp(error)
|
||||||
|
});
|
||||||
|
}
|
@ -24,7 +24,7 @@ const Navigation = () => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Lovely hack to highlight the current page (:
|
// Lovely hack to highlight the current page (:
|
||||||
let active = "Home"
|
let active = "home"
|
||||||
if (location && location.pathname && location.pathname.length > 1) {
|
if (location && location.pathname && location.pathname.length > 1) {
|
||||||
active = location.pathname.replace(/\//, "");
|
active = location.pathname.replace(/\//, "");
|
||||||
}
|
}
|
||||||
@ -49,8 +49,8 @@ const Navigation = () => {
|
|||||||
<Link
|
<Link
|
||||||
key={menuItem}
|
key={menuItem}
|
||||||
className="navLinkMobile"
|
className="navLinkMobile"
|
||||||
style={active === menuItem ? activeStyle : {}}
|
style={active === menuItem.toLowerCase() ? activeStyle : {}}
|
||||||
to={menuItem}
|
to={menuItem.toLowerCase()}
|
||||||
onClick={toggleCollapsed}
|
onClick={toggleCollapsed}
|
||||||
>{menuItem}</Link>
|
>{menuItem}</Link>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
@ -76,8 +76,8 @@ const Navigation = () => {
|
|||||||
<Link
|
<Link
|
||||||
key={menuItem}
|
key={menuItem}
|
||||||
className="navLinkMobile"
|
className="navLinkMobile"
|
||||||
style={active === menuItem ? activeStyle : {}}
|
style={active === "home" && menuItem.toLowerCase() === "home" ? activeStyle : (active === menuItem.toLowerCase() ? activeStyle : {})}
|
||||||
to={menuItem === "Home" ? "/" : menuItem}
|
to={menuItem.toLowerCase() === "home" ? "/" : "/" + menuItem.toLowerCase()}
|
||||||
onClick={toggleCollapsed}
|
onClick={toggleCollapsed}
|
||||||
>{menuItem}
|
>{menuItem}
|
||||||
</Link>
|
</Link>
|
||||||
@ -85,17 +85,17 @@ const Navigation = () => {
|
|||||||
)}
|
)}
|
||||||
<NavItem>
|
<NavItem>
|
||||||
<Link
|
<Link
|
||||||
to="/Login"
|
to="/login"
|
||||||
style={active === "Login" ? activeStyle : {}}
|
style={active === "login" ? activeStyle : {}}
|
||||||
className="navLinkMobile"
|
className="navLinkMobile"
|
||||||
onClick={toggleCollapsed}
|
onClick={toggleCollapsed}
|
||||||
>Login</Link>
|
>Login</Link>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
<NavItem>
|
<NavItem>
|
||||||
<Link
|
<Link
|
||||||
to="/Register"
|
to="/register"
|
||||||
className="navLinkMobile"
|
className="navLinkMobile"
|
||||||
style={active === "Register" ? activeStyle : {}}
|
style={active === "register" ? activeStyle : {}}
|
||||||
onClick={toggleCollapsed}
|
onClick={toggleCollapsed}
|
||||||
>Register</Link>
|
>Register</Link>
|
||||||
</NavItem>
|
</NavItem>
|
||||||
@ -114,8 +114,8 @@ const Navigation = () => {
|
|||||||
<Link
|
<Link
|
||||||
key={menuItem}
|
key={menuItem}
|
||||||
className="navLink"
|
className="navLink"
|
||||||
style={active === menuItem ? activeStyle : {}}
|
style={active === menuItem.toLowerCase() ? activeStyle : {}}
|
||||||
to={menuItem}
|
to={"/" + menuItem.toLowerCase()}
|
||||||
>
|
>
|
||||||
{menuItem}
|
{menuItem}
|
||||||
</Link>
|
</Link>
|
||||||
@ -126,8 +126,8 @@ const Navigation = () => {
|
|||||||
<Link
|
<Link
|
||||||
key={menuItem}
|
key={menuItem}
|
||||||
className="navLink"
|
className="navLink"
|
||||||
style={active === menuItem ? activeStyle : {}}
|
style={active === "home" && menuItem.toLowerCase() === "home" ? activeStyle : (active === menuItem.toLowerCase() ? activeStyle : {})}
|
||||||
to={menuItem === "Home" ? "/" : menuItem}
|
to={menuItem.toLowerCase() === "home" ? "/" : "/" + menuItem.toLowerCase()}
|
||||||
>
|
>
|
||||||
{menuItem}
|
{menuItem}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -17,8 +17,9 @@ const ScrobbleTable = (props) => {
|
|||||||
{
|
{
|
||||||
props.data &&
|
props.data &&
|
||||||
props.data.map(function (element) {
|
props.data.map(function (element) {
|
||||||
|
let localTime = new Date(element.time);
|
||||||
return <tr key={element.uuid}>
|
return <tr key={element.uuid}>
|
||||||
<td>{element.time}</td>
|
<td>{localTime.toLocaleString()}</td>
|
||||||
<td>{element.track}</td>
|
<td>{element.track}</td>
|
||||||
<td>{element.artist}</td>
|
<td>{element.artist}</td>
|
||||||
<td>{element.album}</td>
|
<td>{element.album}</td>
|
||||||
|
@ -24,6 +24,7 @@ const Dashboard = () => {
|
|||||||
})
|
})
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
history.push("/login")
|
history.push("/login")
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ const Profile = (route) => {
|
|||||||
|
|
||||||
let username = false;
|
let username = false;
|
||||||
if (route && route.match && route.match.params && route.match.params.uuid) {
|
if (route && route.match && route.match.params && route.match.params.uuid) {
|
||||||
username = route.match.params.uuid
|
username = route.match.params.uuid;
|
||||||
|
} else {
|
||||||
|
username = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,16 +1,37 @@
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState, useEffect } from 'react';
|
||||||
import '../App.css';
|
import '../App.css';
|
||||||
import './Register.css';
|
import './Register.css';
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import ScaleLoader from "react-spinners/ScaleLoader";
|
import ScaleLoader from "react-spinners/ScaleLoader";
|
||||||
import AuthContext from '../Contexts/AuthContext';
|
import AuthContext from '../Contexts/AuthContext';
|
||||||
import { Formik, Field, Form } from 'formik';
|
import { Formik, Field, Form } from 'formik';
|
||||||
import { useHistory } from "react-router";
|
import { useHistory } from 'react-router';
|
||||||
|
import { getServerInfo } from '../Api/index';
|
||||||
const Register = () => {
|
const Register = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
let boolTrue = true;
|
let boolTrue = true;
|
||||||
let { Register, user, loading } = useContext(AuthContext);
|
let { Register, user, loading } = useContext(AuthContext);
|
||||||
|
let [serverInfo, setServerInfo] = useState({ registration_enabled: true });
|
||||||
|
let [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
getServerInfo()
|
||||||
|
.then(data => {
|
||||||
|
setServerInfo(data);
|
||||||
|
setIsLoading(false);
|
||||||
|
})
|
||||||
|
}, [user])
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="pageWrapper">
|
||||||
|
<ScaleLoader color="#6AD7E5" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
history.push("/dashboard");
|
history.push("/dashboard");
|
||||||
@ -19,8 +40,7 @@ const Register = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="pageWrapper">
|
<div className="pageWrapper">
|
||||||
{
|
{
|
||||||
// TODO: Move to DB:config REGISTRATION_DISABLED=1|0 :upsidedownsmile:
|
serverInfo.registration_enabled !== "1" ?
|
||||||
process.env.REACT_APP_REGISTRATION_DISABLED === "true" ?
|
|
||||||
<p>Registration is temporarily disabled. Please try again soon!</p>
|
<p>Registration is temporarily disabled. Please try again soon!</p>
|
||||||
:
|
:
|
||||||
<div>
|
<div>
|
||||||
|
@ -6,4 +6,44 @@
|
|||||||
.userDropdown {
|
.userDropdown {
|
||||||
color: #282C34;
|
color: #282C34;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userButton {
|
||||||
|
height: 50px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top:-5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.modal > .header {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.modal > .content {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 5px;
|
||||||
|
}
|
||||||
|
.modal > .actions {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 5px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.modal > .close {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
padding: 2px 5px;
|
||||||
|
line-height: 20px;
|
||||||
|
right: -10px;
|
||||||
|
top: -10px;
|
||||||
|
font-size: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid #cfcece;
|
||||||
}
|
}
|
@ -6,8 +6,9 @@ import AuthContext from '../Contexts/AuthContext';
|
|||||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
import ScaleLoader from 'react-spinners/ScaleLoader';
|
||||||
import { getUser, patchUser } from '../Api/index'
|
import { getUser, patchUser } from '../Api/index'
|
||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
|
import { confirmAlert } from 'react-confirm-alert';
|
||||||
import { spotifyConnectionRequest, spotifyDisonnectionRequest } from '../Api/index'
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
||||||
|
import { spotifyConnectionRequest, spotifyDisonnectionRequest, resetScrobbleToken } from '../Api/index'
|
||||||
import TimezoneSelect from 'react-timezone-select'
|
import TimezoneSelect from 'react-timezone-select'
|
||||||
|
|
||||||
const User = () => {
|
const User = () => {
|
||||||
@ -17,11 +18,54 @@ const User = () => {
|
|||||||
const [userdata, setUserdata] = useState({});
|
const [userdata, setUserdata] = useState({});
|
||||||
|
|
||||||
const updateTimezone = (vals) => {
|
const updateTimezone = (vals) => {
|
||||||
console.log(vals)
|
|
||||||
setUserdata({...userdata, timezone: vals});
|
setUserdata({...userdata, timezone: vals});
|
||||||
patchUser({timezone: vals.value})
|
patchUser({timezone: vals.value})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetTokenPopup = () => {
|
||||||
|
confirmAlert({
|
||||||
|
title: 'Reset token',
|
||||||
|
message: 'Resetting your token will require you to update your sources with the new token. Continue?',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: 'Reset',
|
||||||
|
onClick: () => resetToken()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnectSpotifyPopup = () => {
|
||||||
|
confirmAlert({
|
||||||
|
title: 'Disconnect Spotify',
|
||||||
|
message: 'Are you sure you want to disconnect your spotify account?',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: 'Disconnect',
|
||||||
|
onClick: () => spotifyDisonnectionRequest()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetToken = () => {
|
||||||
|
setLoading(true);
|
||||||
|
resetScrobbleToken(user.uuid)
|
||||||
|
.then(() => {
|
||||||
|
getUser()
|
||||||
|
.then(data => {
|
||||||
|
setUserdata(data);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
@ -58,23 +102,31 @@ const User = () => {
|
|||||||
value={userdata.timezone}
|
value={userdata.timezone}
|
||||||
onChange={updateTimezone}
|
onChange={updateTimezone}
|
||||||
/><br/>
|
/><br/>
|
||||||
|
Token: {userdata.token}<br/>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
type="button"
|
||||||
|
className="userButton"
|
||||||
|
onClick={resetTokenPopup}
|
||||||
|
>Reset Token</Button><br/><br/>
|
||||||
Created At: {userdata.created_at}<br/>
|
Created At: {userdata.created_at}<br/>
|
||||||
Email: {userdata.email}<br/>
|
Email: {userdata.email}<br/>
|
||||||
Verified: {userdata.verified ? '✓' : '✖'}<br/>
|
Verified: {userdata.verified ? '✓' : '✖'}<br/>
|
||||||
|
|
||||||
{userdata.spotify_username
|
{userdata.spotify_username
|
||||||
? <div>Spotify Account: {userdata.spotify_username}<br/><br/>
|
? <div>Spotify Account: {userdata.spotify_username}<br/><br/>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
type="button"
|
type="button"
|
||||||
className="loginButton"
|
className="userButton"
|
||||||
onClick={spotifyDisonnectionRequest}
|
onClick={disconnectSpotifyPopup}
|
||||||
>Disconnect Spotify</Button></div>
|
>Disconnect Spotify</Button></div>
|
||||||
: <div>
|
: <div>
|
||||||
<br/>
|
<br/>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
type="button"
|
type="button"
|
||||||
className="loginButton"
|
className="userButton"
|
||||||
onClick={spotifyConnectionRequest}
|
onClick={spotifyConnectionRequest}
|
||||||
>Connect To Spotify</Button>
|
>Connect To Spotify</Button>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user