diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4c23cc07..c6537639 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ stages:
- bundle
variables:
- VERSION: 0.0.15
+ VERSION: 0.0.16
build-go:
image: golang:1.16.2
diff --git a/docs/changelog.md b/docs/changelog.md
index 58422c80..338106b5 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -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
- Fix spotify track duration
diff --git a/docs/config.md b/docs/config.md
index 2d4f67d9..2d7a2bf8 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -4,7 +4,6 @@ GoScrobble runs as UTC and connects to MySQL as UTC. All timezone handling is do
## FRONTEND VARS
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
diff --git a/internal/goscrobble/config.go b/internal/goscrobble/config.go
index 9100a40e..fd952ec4 100644
--- a/internal/goscrobble/config.go
+++ b/internal/goscrobble/config.go
@@ -12,7 +12,8 @@ type Config struct {
}
type ServerInfo struct {
- Version string `json:"version"`
+ Version string `json:"version"`
+ RegistrationEnabled string `json:"registration_enabled"`
}
func getAllConfigs() (Config, error) {
diff --git a/internal/goscrobble/ingress_multiscrobbler.go b/internal/goscrobble/ingress_multiscrobbler.go
index 408a7661..e82a05bc 100644
--- a/internal/goscrobble/ingress_multiscrobbler.go
+++ b/internal/goscrobble/ingress_multiscrobbler.go
@@ -2,7 +2,6 @@ package goscrobble
import (
"database/sql"
- "encoding/json"
"errors"
"fmt"
"log"
@@ -21,10 +20,9 @@ type MultiScrobblerRequest struct {
// ParseMultiScrobblerInput - Transform API data
func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip net.IP, tx *sql.Tx) error {
// Cache key
- json, _ := json.Marshal(data)
- redisKey := getMd5(string(json) + userUUID)
+ json := fmt.Sprintf("%s:%s:%s:%s", data.PlayedAt, data.Track, data.Album, userUUID)
+ redisKey := getMd5(json)
if getRedisKeyExists(redisKey) {
- fmt.Printf("Prevented duplicate entry!")
return nil
}
@@ -63,8 +61,7 @@ func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip ne
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(data.Duration*2) * time.Second
+ ttl := time.Duration(30) * time.Minute
setRedisValTtl(redisKey, "1", ttl)
return nil
diff --git a/internal/goscrobble/redis.go b/internal/goscrobble/redis.go
index 23a8ada8..b6e4eb3f 100644
--- a/internal/goscrobble/redis.go
+++ b/internal/goscrobble/redis.go
@@ -58,7 +58,7 @@ func setRedisVal(key string, val string) error {
// setRedisTtl - Allows custom TTL
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
diff --git a/internal/goscrobble/scrobble.go b/internal/goscrobble/scrobble.go
index 42ce51ec..13615a96 100644
--- a/internal/goscrobble/scrobble.go
+++ b/internal/goscrobble/scrobble.go
@@ -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
total, err := getDbCount(
- "SELECT COUNT(*) FROM `scrobbles` "+
- "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)
+ "SELECT COUNT(*) FROM `scrobbles` WHERE `user` = UUID_TO_BIN(?, true) ", userUuid)
if err != nil {
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(
- "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 track_artist ON track_artist.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 users ON scrobbles.user = users.uuid "+
"WHERE user = UUID_TO_BIN(?, true) "+
+ "GROUP BY scrobbles.uuid, albums.uuid "+
"ORDER BY scrobbles.created_at DESC LIMIT ?",
userUuid, limit)
+
if err != nil {
log.Printf("Failed to fetch scrobbles: %+v", err)
return scrobbleReq, errors.New("Failed to fetch scrobbles")
diff --git a/internal/goscrobble/server.go b/internal/goscrobble/server.go
index 04c55189..0a648387 100644
--- a/internal/goscrobble/server.go
+++ b/internal/goscrobble/server.go
@@ -321,6 +321,9 @@ func patchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser s
if isValidTimezone(val) {
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 {
- err = updateConfigValue(k, fmt.Sprintf("%s", v))
+ val := fmt.Sprintf("%s", v)
+ err = updateConfigValue(k, val)
if err != nil {
throwOkError(w, err.Error())
return
}
+ setRedisVal(k, val)
}
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) {
+ 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{
- Version: "0.0.15",
+ Version: "0.0.16",
+ RegistrationEnabled: cachedRegistrationEnabled,
}
js, _ := json.Marshal(&info)
diff --git a/internal/goscrobble/user.go b/internal/goscrobble/user.go
index 3d9722e1..5c7ec814 100644
--- a/internal/goscrobble/user.go
+++ b/internal/goscrobble/user.go
@@ -29,6 +29,7 @@ type User struct {
Active bool `json:"active"`
Admin bool `json:"admin"`
Timezone string `json:"timezone"`
+ Token string `json:"token"`
}
type UserResponse struct {
@@ -42,6 +43,7 @@ type UserResponse struct {
Verified bool `json:"verified"`
SpotifyUsername string `json:"spotify_username"`
Timezone string `json:"timezone"`
+ Token string `json:"token"`
}
// RegisterRequest - Incoming JSON
@@ -211,8 +213,8 @@ func userAlreadyExists(req *RegisterRequest) bool {
func getUser(uuid string) (User, error) {
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",
- uuid).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
+ 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, &user.Token)
if err == sql.ErrNoRows {
return user, errors.New("Invalid JWT Token")
@@ -223,8 +225,8 @@ func getUser(uuid string) (User, error) {
func getUserByUsername(username string) (User, error) {
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",
- username).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
+ 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, &user.Token)
if err == sql.ErrNoRows {
return user, errors.New("Invalid Username")
@@ -235,8 +237,8 @@ func getUserByUsername(username string) (User, error) {
func getUserByEmail(email string) (User, error) {
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",
- email).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone)
+ 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, &user.Token)
if err == sql.ErrNoRows {
return user, errors.New("Invalid Email")
@@ -247,9 +249,9 @@ func getUserByEmail(email string) (User, error) {
func getUserByResetToken(token string) (User, error) {
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",
- 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 {
return user, errors.New("Invalid Token")
diff --git a/migrations/2_users.up.sql b/migrations/2_users.up.sql
index 44bea470..469a2f46 100644
--- a/migrations/2_users.up.sql
+++ b/migrations/2_users.up.sql
@@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `users` (
`active` TINYINT(1) NOT NULL DEFAULT 1,
`admin` 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 `emailLookup` (`email`, `active`)
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
\ No newline at end of file
diff --git a/web/.env.example b/web/.env.example
index cfed93ef..fbedc8f2 100644
--- a/web/.env.example
+++ b/web/.env.example
@@ -1,2 +1 @@
REACT_APP_API_URL=http://127.0.0.1:42069/api/v1/
-REACT_APP_REGISTRATION_DISABLED=false
\ No newline at end of file
diff --git a/web/package-lock.json b/web/package-lock.json
index a9801c9f..dbf5b788 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -20,6 +20,7 @@
"jwt-decode": "^3.1.2",
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
+ "react-confirm-alert": "^2.7.0",
"react-cookie": "^4.0.3",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
@@ -27,6 +28,7 @@
"react-spinners": "^0.10.6",
"react-timezone-select": "^0.10.7",
"react-toastify": "^7.0.3",
+ "reactjs-popup": "^2.0.4",
"reactstrap": "^8.9.0"
},
"devDependencies": {
@@ -16633,6 +16635,15 @@
"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": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.3.tgz",
@@ -17101,6 +17112,18 @@
"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": {
"version": "8.9.0",
"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": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-4.0.3.tgz",
@@ -35934,6 +35963,12 @@
"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": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.9.0.tgz",
diff --git a/web/package.json b/web/package.json
index d781bbc9..ef773d6a 100644
--- a/web/package.json
+++ b/web/package.json
@@ -15,6 +15,7 @@
"jwt-decode": "^3.1.2",
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
+ "react-confirm-alert": "^2.7.0",
"react-cookie": "^4.0.3",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
@@ -22,6 +23,7 @@
"react-spinners": "^0.10.6",
"react-timezone-select": "^0.10.7",
"react-toastify": "^7.0.3",
+ "reactjs-popup": "^2.0.4",
"reactstrap": "^8.9.0"
},
"scripts": {
diff --git a/web/src/Api/index.js b/web/src/Api/index.js
index 1b64cc95..0e0a53e3 100644
--- a/web/src/Api/index.js
+++ b/web/src/Api/index.js
@@ -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)
+ });
+}
\ No newline at end of file
diff --git a/web/src/Components/Navigation.js b/web/src/Components/Navigation.js
index fdf6eb24..e625b218 100644
--- a/web/src/Components/Navigation.js
+++ b/web/src/Components/Navigation.js
@@ -24,7 +24,7 @@ const Navigation = () => {
const location = useLocation();
// Lovely hack to highlight the current page (:
- let active = "Home"
+ let active = "home"
if (location && location.pathname && location.pathname.length > 1) {
active = location.pathname.replace(/\//, "");
}
@@ -49,8 +49,8 @@ const Navigation = () => {
{menuItem}
@@ -76,8 +76,8 @@ const Navigation = () => {
{menuItem}
@@ -85,17 +85,17 @@ const Navigation = () => {
)}
Registration is temporarily disabled. Please try again soon!
: