From 525d5c92b588a47654b85197cd094c5f525d612d Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Fri, 13 Aug 2021 22:38:03 +1200 Subject: [PATCH] 0.0.33 --- .gitlab-ci.yml | 2 +- docs/changelog.md | 4 ++ internal/goscrobble/jwt.go | 9 +-- internal/goscrobble/server.go | 2 +- internal/goscrobble/track.go | 2 +- internal/goscrobble/user.go | 33 +++++----- migrations/13_user_mod.down.sql | 1 + migrations/13_user_mod.up.sql | 1 + web/src/Api/index.js | 2 + web/src/App.js | 2 + web/src/Pages/Admin.js | 3 +- web/src/Pages/Track.js | 10 ++- web/src/Pages/TrackEdit.css | 0 web/src/Pages/TrackEdit.js | 113 ++++++++++++++++++++++++++++++++ 14 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 migrations/13_user_mod.down.sql create mode 100644 migrations/13_user_mod.up.sql create mode 100644 web/src/Pages/TrackEdit.css create mode 100644 web/src/Pages/TrackEdit.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9760bb90..cc240826 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - bundle variables: - VERSION: 0.0.32 + VERSION: 0.0.33 build-go: image: golang:1.16.7 diff --git a/docs/changelog.md b/docs/changelog.md index 36e65e13..34c38fa4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,7 @@ +# 0.0.33 +- Add mod permission +- Add track/edit page + # 0.0.32 - Add related records into track API - Build out track page to show links to related records diff --git a/internal/goscrobble/jwt.go b/internal/goscrobble/jwt.go index 4e9d2390..94ab019c 100644 --- a/internal/goscrobble/jwt.go +++ b/internal/goscrobble/jwt.go @@ -34,6 +34,7 @@ func generateJWTToken(user User, existingRefresh string) (string, error) { atClaims["username"] = user.Username atClaims["email"] = user.Email atClaims["admin"] = user.Admin + atClaims["mod"] = user.Mod atClaims["iat"] = time.Now().Unix() atClaims["exp"] = time.Now().Add(JwtExpiry).Unix() atClaims["refresh_token"] = refreshToken @@ -80,7 +81,7 @@ func verifyJWTToken(token string) (CustomClaims, error) { return claims, err } -func getClaims(token *jwt.Token) CustomClaims { - claims, _ := token.Claims.(CustomClaims) - return claims -} +// func getClaims(token *jwt.Token) CustomClaims { +// claims, _ := token.Claims.(CustomClaims) +// return claims +// } diff --git a/internal/goscrobble/server.go b/internal/goscrobble/server.go index 9a889710..d5a91dc1 100644 --- a/internal/goscrobble/server.go +++ b/internal/goscrobble/server.go @@ -777,7 +777,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) { } info := ServerInfo{ - Version: "0.0.32", + Version: "0.0.33", RegistrationEnabled: cachedRegistrationEnabled, } diff --git a/internal/goscrobble/track.go b/internal/goscrobble/track.go index 14ad1123..2e601b57 100644 --- a/internal/goscrobble/track.go +++ b/internal/goscrobble/track.go @@ -320,7 +320,7 @@ func getTopUsersForTrackUUID(trackUUID string, limit int, page int) (TopUserTrac // Yeah this isn't great. But for now.. it works! Cache later // TODO: This is counting total scrobbles, not unique users total, err := getDbCount( - "SELECT COUNT(*) FROM `scrobbles` WHERE `track` = UUID_TO_BIN(?, true) GROUP BY `user`", trackUUID) + "SELECT COUNT(*) FROM `scrobbles` WHERE `track` = UUID_TO_BIN(?, true) GROUP BY `track`, `user`", trackUUID) if err != nil { log.Printf("Failed to fetch scrobble count: %+v", err) diff --git a/internal/goscrobble/user.go b/internal/goscrobble/user.go index 5b31272f..936cbcc6 100644 --- a/internal/goscrobble/user.go +++ b/internal/goscrobble/user.go @@ -28,6 +28,7 @@ type User struct { Verified bool `json:"verified"` Active bool `json:"active"` Admin bool `json:"admin"` + Mod bool `json:"mod"` Timezone string `json:"timezone"` Token string `json:"token"` } @@ -98,16 +99,16 @@ func loginUser(logReq *RequestRequest, ip net.IP) ([]byte, error) { } if strings.Contains(logReq.Username, "@") { - err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin` FROM `users` WHERE `email` = ? AND `active` = 1", - logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin) + err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin`, `mod` FROM `users` WHERE `email` = ? AND `active` = 1", + logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin, &user.Mod) if err != nil { if err == sql.ErrNoRows { return resp, errors.New("Invalid Username or Password") } } } else { - err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin` FROM `users` WHERE `username` = ? AND `active` = 1", - logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin) + err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `username`, `email`, `password`, `admin`, `mod` FROM `users` WHERE `username` = ? AND `active` = 1", + logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin, &user.Mod) if err == sql.ErrNoRows { return resp, errors.New("Invalid Username or Password") } @@ -196,8 +197,8 @@ func userAlreadyExists(req *RequestRequest) bool { func getUserByUUID(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`, `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) + err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `mod`, `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.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { return user, errors.New("Invalid JWT Token") @@ -208,8 +209,8 @@ func getUserByUUID(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`, `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) + err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `mod`, `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.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { return user, errors.New("Invalid Username") @@ -220,8 +221,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`, `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) + err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `username`, `email`, `password`, `verified`, `admin`, `mod`, `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.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { return user, errors.New("Invalid Email") @@ -232,9 +233,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`, `token` 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`, `mod`, `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, &user.Token) + token).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { return user, errors.New("Invalid Token") @@ -316,7 +317,7 @@ func (user *User) getNavidromeTokens() (OauthToken, error) { func getAllSpotifyUsers() ([]User, error) { users := make([]User, 0) - rows, err := db.Query("SELECT BIN_TO_UUID(`users`.`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `users`.`username`, `email`, `password`, `verified`, `admin`, `timezone` FROM `users` " + + rows, err := db.Query("SELECT BIN_TO_UUID(`users`.`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `users`.`username`, `email`, `password`, `verified`, `admin`, `mod`, `timezone` FROM `users` " + "JOIN `oauth_tokens` ON `oauth_tokens`.`user` = `users`.`uuid` AND `oauth_tokens`.`service` = 'spotify' WHERE `users`.`active` = 1") if err != nil { @@ -328,7 +329,7 @@ func getAllSpotifyUsers() ([]User, error) { for rows.Next() { var user User - err := rows.Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone) + err := rows.Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone) if err != nil { log.Printf("Failed to fetch spotify user: %+v", err) return users, errors.New("Failed to fetch users") @@ -348,7 +349,7 @@ func getAllSpotifyUsers() ([]User, error) { func getAllNavidromeUsers() ([]User, error) { users := make([]User, 0) - rows, err := db.Query("SELECT BIN_TO_UUID(`users`.`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `users`.`username`, `email`, `password`, `verified`, `admin`, `timezone` FROM `users` " + + rows, err := db.Query("SELECT BIN_TO_UUID(`users`.`uuid`, true), `created_at`, `created_ip`, `modified_at`, `modified_ip`, `users`.`username`, `email`, `password`, `verified`, `admin`, `mod`, `timezone` FROM `users` " + "JOIN `oauth_tokens` ON `oauth_tokens`.`user` = `users`.`uuid` AND `oauth_tokens`.`service` = 'navidrome' WHERE `users`.`active` = 1") if err != nil { @@ -360,7 +361,7 @@ func getAllNavidromeUsers() ([]User, error) { for rows.Next() { var user User - err := rows.Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Timezone) + err := rows.Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone) if err != nil { log.Printf("Failed to fetch navidrome user: %+v", err) return users, errors.New("Failed to fetch users") diff --git a/migrations/13_user_mod.down.sql b/migrations/13_user_mod.down.sql new file mode 100644 index 00000000..cb584b55 --- /dev/null +++ b/migrations/13_user_mod.down.sql @@ -0,0 +1 @@ +ALTER TABLE `users` DROP COLUMN `mod`; \ No newline at end of file diff --git a/migrations/13_user_mod.up.sql b/migrations/13_user_mod.up.sql new file mode 100644 index 00000000..2ea56b7a --- /dev/null +++ b/migrations/13_user_mod.up.sql @@ -0,0 +1 @@ +ALTER TABLE `users` ADD COLUMN `mod` TINYINT(1) NOT NULL DEFAULT 0 AFTER `admin`; \ No newline at end of file diff --git a/web/src/Api/index.js b/web/src/Api/index.js index 5f2cbc7f..734462b8 100644 --- a/web/src/Api/index.js +++ b/web/src/Api/index.js @@ -59,6 +59,7 @@ export const PostLogin = (formValues) => { exp: expandedUser.exp, username: expandedUser.username, admin: expandedUser.admin, + mod: expandedUser.mod, refresh_token: expandedUser.refresh_token, refresh_exp: expandedUser.refresh_exp, } @@ -92,6 +93,7 @@ export const PostRefreshToken = (refreshToken) => { exp: expandedUser.exp, username: expandedUser.username, admin: expandedUser.admin, + mod: expandedUser.mod, refresh_token: expandedUser.refresh_token, refresh_exp: expandedUser.refresh_exp, } diff --git a/web/src/App.js b/web/src/App.js index 520cbe07..5ca5883f 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -5,6 +5,7 @@ import Profile from './Pages/Profile'; import Artist from './Pages/Artist'; import Album from './Pages/Album'; import Track from './Pages/Track'; +import TrackEdit from './Pages/TrackEdit'; import User from './Pages/User'; import Admin from './Pages/Admin'; import Login from './Pages/Login'; @@ -36,6 +37,7 @@ const App = () => { + diff --git a/web/src/Pages/Admin.js b/web/src/Pages/Admin.js index 473ccbad..283ea9fd 100644 --- a/web/src/Pages/Admin.js +++ b/web/src/Pages/Admin.js @@ -12,6 +12,7 @@ import { getConfigs, postConfigs } from '../Api/index' const Admin = () => { const history = useHistory(); const { user } = useContext(AuthContext); + const [loading, setLoading] = useState(true); const [configs, setConfigs] = useState({}) const [toggle, setToggle] = useState(false); @@ -47,8 +48,6 @@ const Admin = () => { ) } - - return (

diff --git a/web/src/Pages/Track.js b/web/src/Pages/Track.js index 7702954a..00128162 100644 --- a/web/src/Pages/Track.js +++ b/web/src/Pages/Track.js @@ -1,12 +1,15 @@ -import React, { useState, useEffect } from 'react'; +import React, { useContext, useState, useEffect } from 'react'; import '../App.css'; import './Track.css'; import TopUserTable from '../Components/TopUserTable'; import ScaleLoader from 'react-spinners/ScaleLoader'; import { getTrack } from '../Api/index' import { Link } from 'react-router-dom'; +import AuthContext from '../Contexts/AuthContext'; const Track = (route) => { + const { user } = useContext(AuthContext); + const [loading, setLoading] = useState(true); const [track, setTrack] = useState({}); @@ -76,7 +79,10 @@ const Track = (route) => { return (

- {track.name} + {track.name} {user && edit}

diff --git a/web/src/Pages/TrackEdit.css b/web/src/Pages/TrackEdit.css new file mode 100644 index 00000000..e69de29b diff --git a/web/src/Pages/TrackEdit.js b/web/src/Pages/TrackEdit.js new file mode 100644 index 00000000..b57d6445 --- /dev/null +++ b/web/src/Pages/TrackEdit.js @@ -0,0 +1,113 @@ +import React, { useContext, useState, useEffect } from 'react'; +import '../App.css'; +import './TrackEdit.css'; +import { useHistory } from 'react-router-dom'; +import ScaleLoader from 'react-spinners/ScaleLoader'; +import { getTrack } from '../Api/index' +import { Link } from 'react-router-dom'; +import AuthContext from '../Contexts/AuthContext'; + +const TrackEdit = (route) => { + const history = useHistory(); + const { user } = useContext(AuthContext); + + const [loading, setLoading] = useState(true); + const [track, setTrack] = useState({}); + + let trackUUID = false; + if (route && route.match && route.match.params && route.match.params.uuid) { + trackUUID = route.match.params.uuid; + } else { + trackUUID = false; + } + + useEffect(() => { + if (!trackUUID) { + return false; + } + + getTrack(trackUUID) + .then(data => { + setTrack(data); + setLoading(false); + }) + }, [trackUUID]) + + if (!user) { + history.push("/login") + } + + if (user && !user.mod) { + history.push("/Dashboard") + } + + if (loading) { + return ( +
+ +
+ ) + } + + if (!trackUUID || !track) { + return ( +
+ Unable to fetch user +
+ ) + } + + console.log(track) + let length = "0"; + if (track.length && track.length !== '') { + length = new Date(track.length * 1000).toISOString().substr(11, 8) + } + + let artists = []; + for (let artist of track.artists) { + const row = ( + {artist.name} + ); + artists.push(row); + } + + let albums = []; + for (let album of track.albums) { + const row = ( + {album.name} + ); + albums.push(row); + } + + return ( +
+

+ {track.name} {unedit} +

+
+ {track.name} +
+
+
+
+
+
+
+
+
+
+
+
+ ); +} + +export default TrackEdit; \ No newline at end of file