mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-24 17:35:16 +00:00
0.0.3
- Clean up login/redirect flow - Add redirect when not authed on other endpoints - Add GET /stats endpoint for overal stats
This commit is contained in:
parent
5fd9d41069
commit
038823055a
@ -3,7 +3,7 @@ stages:
|
||||
- bundle
|
||||
|
||||
variables:
|
||||
VERSION: 0.0.2
|
||||
VERSION: 0.0.3
|
||||
|
||||
build-go:
|
||||
image: golang:1.16.2
|
||||
|
@ -22,7 +22,7 @@ Copy .env.example to .env and set variables. You can use https://www.grc.com/pas
|
||||
cp .env.example .env # Fill in the blanks
|
||||
go mod tidy
|
||||
CGO_ENABLED=0 go run cmd/go-scrobble/*.go
|
||||
# In another terminal set web/.env.development
|
||||
# In another terminal cp web/.env.example web/.env.development and set vars
|
||||
cd web && npm install && npm start --env development
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ Access dev frontend @ http://127.0.0.1:3000 + API @ http://127.0.0.1:42069/api/v
|
||||
We need to build NPM package, and then ship web/build with the binary.
|
||||
|
||||
cp .env.example .env # Fill in the blanks
|
||||
cp web/.env.example web/.env.production
|
||||
cd web npm install --production && npm run build --env production
|
||||
go build -o goscrobble cmd/go-scrobble/*.go
|
||||
./goscrobble
|
||||
|
@ -1,3 +1,11 @@
|
||||
# 0.0.4
|
||||
- Display stats on homepage
|
||||
|
||||
# 0.0.3
|
||||
- Clean up login/redirect flow
|
||||
- Add redirect when not authed on other endpoints
|
||||
- Add GET /stats endpoint for overal stats
|
||||
|
||||
# 0.0.2
|
||||
- Login flow working..
|
||||
- Jellyfin scrobble working
|
||||
|
@ -54,6 +54,7 @@ func HandleRequests(port string) {
|
||||
// No Auth
|
||||
v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST")
|
||||
v1.HandleFunc("/login", limitMiddleware(handleLogin, standardLimiter)).Methods("POST")
|
||||
v1.HandleFunc("/stats", handleStats).Methods("GET")
|
||||
|
||||
// This just prevents it serving frontend stuff over /api
|
||||
r.PathPrefix("/api")
|
||||
@ -233,6 +234,19 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
// handleStats - Returns stats for homepage
|
||||
func handleStats(w http.ResponseWriter, r *http.Request) {
|
||||
stats, err := getStats()
|
||||
if err != nil {
|
||||
throwOkError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(&stats)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(js)
|
||||
}
|
||||
|
||||
// serveEndpoint - API stuffs
|
||||
func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
|
||||
bodyJson, err := decodeJson(r.Body)
|
||||
|
94
internal/goscrobble/stats.go
Normal file
94
internal/goscrobble/stats.go
Normal file
@ -0,0 +1,94 @@
|
||||
package goscrobble
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StatsRequest struct {
|
||||
Users int `json:"users"`
|
||||
Scrobbles int `json:"scrobbles"`
|
||||
Tracks int `json:"songs"`
|
||||
Artists int `json:"artists"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
func getStats() (StatsRequest, error) {
|
||||
js := getRedisVal("stats")
|
||||
statsReq := StatsRequest{}
|
||||
var err error
|
||||
if js != "" {
|
||||
// If cached, deserialize and return
|
||||
err = json.Unmarshal([]byte(js), &statsReq)
|
||||
if err != nil {
|
||||
log.Printf("Error unmarshalling stats json: %+v", err)
|
||||
return statsReq, errors.New("Error fetching stats")
|
||||
}
|
||||
|
||||
// Check if older than 5 min - we want to update async for the next caller
|
||||
now := time.Now()
|
||||
if now.Sub(statsReq.LastUpdated) > time.Duration(5)*time.Minute {
|
||||
go goFetchStats()
|
||||
}
|
||||
} else {
|
||||
// If not cached, pull data then return
|
||||
statsReq, err = fetchStats()
|
||||
if err != nil {
|
||||
log.Printf("Error fetching stats: %+v", err)
|
||||
return statsReq, errors.New("Error fetching stats")
|
||||
}
|
||||
}
|
||||
|
||||
return statsReq, nil
|
||||
}
|
||||
|
||||
// goFetchStats - Async call
|
||||
func goFetchStats() {
|
||||
_, _ = fetchStats()
|
||||
}
|
||||
|
||||
func fetchStats() (StatsRequest, error) {
|
||||
statsReq := StatsRequest{}
|
||||
var err error
|
||||
|
||||
statsReq.Users, err = getDbCount("SELECT COUNT(*) FROM `users` WHERE `active` = 1")
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch user count: %+v", err)
|
||||
return statsReq, errors.New("Failed to fetch stats")
|
||||
}
|
||||
|
||||
statsReq.Scrobbles, err = getDbCount("SELECT COUNT(*) FROM `scrobbles`")
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch scrobble count: %+v", err)
|
||||
return statsReq, errors.New("Failed to fetch stats")
|
||||
}
|
||||
|
||||
statsReq.Tracks, err = getDbCount("SELECT COUNT(*) FROM `tracks`")
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch track count: %+v", err)
|
||||
return statsReq, errors.New("Failed to fetch stats")
|
||||
}
|
||||
|
||||
statsReq.Artists, err = getDbCount("SELECT COUNT(*) FROM `artists`")
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch artist count: %+v", err)
|
||||
return statsReq, errors.New("Failed to fetch stats")
|
||||
}
|
||||
|
||||
// Mark the time this was last updated
|
||||
statsReq.LastUpdated = time.Now()
|
||||
|
||||
b, err := json.Marshal(statsReq)
|
||||
if err != nil {
|
||||
return statsReq, errors.New("Failed to fetch stats")
|
||||
}
|
||||
|
||||
err = setRedisVal("stats", string(b))
|
||||
if err != nil {
|
||||
return statsReq, errors.New("Failed to fetch stats")
|
||||
}
|
||||
|
||||
return statsReq, nil
|
||||
}
|
2
web/.env.example
Normal file
2
web/.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
REACT_APP_API_URL=http://127.0.0.1:42069/api/v1/
|
||||
REACT_APP_REGISTRATION_DISABLED=false
|
29
web/src/Actions/api.js
Normal file
29
web/src/Actions/api.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import ApiService from "../Services/api.service";
|
||||
|
||||
export const getStats = () => () => {
|
||||
return ApiService.getStats().then(
|
||||
(data) => {
|
||||
console.log(data);
|
||||
if (data.error) {
|
||||
toast.error(data.error)
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error) => {
|
||||
const message =
|
||||
(error.response &&
|
||||
error.response.data &&
|
||||
error.response.data.message) ||
|
||||
error.message ||
|
||||
error.toString();
|
||||
console.log(message);
|
||||
|
||||
toast.error(message);
|
||||
return Promise.reject();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -3,20 +3,30 @@ import {
|
||||
REGISTER_FAIL,
|
||||
LOGIN_SUCCESS,
|
||||
LOGIN_FAIL,
|
||||
SET_MESSAGE,
|
||||
} from "./types";
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
import jwt from 'jwt-decode'
|
||||
import AuthService from "../Services/auth.service";
|
||||
|
||||
export const register = (username, email, password) => (dispatch) => {
|
||||
return AuthService.register(username, email, password).then(
|
||||
(response) => {
|
||||
(data) => {
|
||||
if (data.message) {
|
||||
toast.success('Successfully registered. Please sign in');
|
||||
dispatch({
|
||||
type: REGISTER_SUCCESS,
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
toast.error(data.error ? data.error: 'An Unknown Error has occurred')
|
||||
dispatch({
|
||||
type: REGISTER_FAIL,
|
||||
});
|
||||
|
||||
return Promise.reject();
|
||||
},
|
||||
(error) => {
|
||||
const message =
|
||||
@ -26,13 +36,10 @@ import {
|
||||
error.message ||
|
||||
error.toString();
|
||||
|
||||
dispatch({
|
||||
type: REGISTER_FAIL,
|
||||
});
|
||||
toast.error(message);
|
||||
|
||||
dispatch({
|
||||
type: SET_MESSAGE,
|
||||
payload: message,
|
||||
type: REGISTER_FAIL,
|
||||
});
|
||||
|
||||
return Promise.reject();
|
||||
@ -45,9 +52,11 @@ import {
|
||||
(data) => {
|
||||
if (data.token) {
|
||||
toast.success('Login Success');
|
||||
let user = jwt(data.token)
|
||||
|
||||
dispatch({
|
||||
type: LOGIN_SUCCESS,
|
||||
payload: { user: data },
|
||||
payload: { jwt: data.token, sub: user.sub, exp: user.exp },
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -71,17 +80,17 @@ import {
|
||||
type: LOGIN_FAIL,
|
||||
});
|
||||
|
||||
// dispatch({
|
||||
// type: SET_MESSAGE,
|
||||
// payload: message,
|
||||
// });
|
||||
|
||||
return Promise.reject();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const logout = () => () => {
|
||||
export const logout = () => (dispatch) => {
|
||||
AuthService.logout();
|
||||
|
||||
// dispatch({
|
||||
// type: LOGOUT,
|
||||
// });
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
13
web/src/Actions/eventBus.js
Normal file
13
web/src/Actions/eventBus.js
Normal file
@ -0,0 +1,13 @@
|
||||
const eventBus = {
|
||||
on(event, callback) {
|
||||
document.addEventListener(event, (e) => callback(e.detail));
|
||||
},
|
||||
dispatch(event, data) {
|
||||
document.dispatchEvent(new CustomEvent(event, { detail: data }));
|
||||
},
|
||||
remove(event, callback) {
|
||||
document.removeEventListener(event, callback);
|
||||
},
|
||||
};
|
||||
|
||||
export default eventBus
|
@ -11,8 +11,6 @@ import Register from './Pages/Register';
|
||||
import Navigation from './Components/Navigation';
|
||||
|
||||
import { logout } from './Actions/auth';
|
||||
import { clearMessage } from './Actions/message';
|
||||
import { history } from './Helpers/history';
|
||||
import { Route, Switch, withRouter } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { Component } from 'react';
|
||||
@ -37,10 +35,6 @@ class App extends Component {
|
||||
// exact="true".. it has to be a bool :|
|
||||
true: true,
|
||||
};
|
||||
|
||||
history.listen((location) => {
|
||||
props.dispatch(clearMessage()); // clear message when changing location
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
0
web/src/Components/HomeBanner.css
Normal file
0
web/src/Components/HomeBanner.css
Normal file
44
web/src/Components/HomeBanner.js
Normal file
44
web/src/Components/HomeBanner.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import '../App.css';
|
||||
import './HomeBanner.css';
|
||||
import { getStats } from '../Actions/api';
|
||||
|
||||
class HomeBanner extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
userCount: 0,
|
||||
scrobbleCount: 0,
|
||||
trackCount: 0,
|
||||
artistCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
getStats()
|
||||
// .then((data) => {
|
||||
// this.setState({
|
||||
// loading: false,
|
||||
// userCount: data.users,
|
||||
// scrobbleCount: data.scrobbles,
|
||||
// trackCount: data.tracks,
|
||||
// artistCount: data.artists,
|
||||
// });
|
||||
// })
|
||||
// .catch(() => {
|
||||
// this.setState({
|
||||
// loading: false
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container">
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HomeBanner;
|
@ -5,6 +5,12 @@ import logo from '../logo.png';
|
||||
import './Navigation.css';
|
||||
import { connect } from 'react-redux';
|
||||
import { logout } from '../Actions/auth';
|
||||
import eventBus from "../Actions/eventBus";
|
||||
|
||||
import {
|
||||
LOGIN_SUCCESS,
|
||||
LOGOUT,
|
||||
} from "../Actions/types";
|
||||
|
||||
const menuItems = [
|
||||
'Home',
|
||||
@ -24,32 +30,62 @@ class Navigation extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const isLoggedIn = this.props.isLoggedIn;
|
||||
|
||||
const { isLoggedIn } = this.props;
|
||||
if (isLoggedIn) {
|
||||
this.setState({
|
||||
isLoggedIn: true,
|
||||
});
|
||||
}
|
||||
|
||||
eventBus.on(LOGIN_SUCCESS, () =>
|
||||
this.setState({ isLoggedIn: true })
|
||||
);
|
||||
|
||||
eventBus.on(LOGOUT, () =>
|
||||
this.setState({ isLoggedIn: false })
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
eventBus.remove(LOGIN_SUCCESS);
|
||||
eventBus.remove(LOGOUT);
|
||||
}
|
||||
|
||||
|
||||
_handleClick(menuItem) {
|
||||
this.setState({ active: menuItem });
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const activeStyle = { color: '#FFFFFF' };
|
||||
|
||||
const renderAuthButtons = () => {
|
||||
if (this.state.isLoggedIn) {
|
||||
return <div className="navLinkLogin">
|
||||
<Link to="/profile" className="navLink">Profile</Link>
|
||||
<Link
|
||||
to="/profile"
|
||||
style={this.state.active === "profile" ? activeStyle : {}}
|
||||
onClick={this._handleClick.bind(this, "profile")}
|
||||
className="navLink"
|
||||
>Profile</Link>
|
||||
<Link to="/" className="navLink" onClick={logout()}>Logout</Link>
|
||||
</div>;
|
||||
} else {
|
||||
return <div className="navLinkLogin">
|
||||
<Link to="/login" className="navLink">Login</Link>
|
||||
<Link to="/register" className="navLink" history={this.props.history}>Register</Link>
|
||||
<Link
|
||||
to="/login"
|
||||
style={this.state.active === "login" ? activeStyle : {}}
|
||||
onClick={this._handleClick.bind(this, "login")}
|
||||
className="navLink"
|
||||
>Login</Link>
|
||||
<Link
|
||||
to="/register"
|
||||
className="navLink"
|
||||
style={this.state.active === "register" ? activeStyle : {}}
|
||||
onClick={this._handleClick.bind(this, "register")}
|
||||
history={this.props.history}
|
||||
>Register</Link>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@ -89,7 +125,7 @@ class Navigation extends Component {
|
||||
return (
|
||||
<div>
|
||||
<Navbar color="dark" dark fixed="top">
|
||||
<NavbarBrand href="/" className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
|
||||
<NavbarBrand className="mr-auto"><img src={logo} className="nav-logo" alt="logo" /> GoScrobble</NavbarBrand>
|
||||
{renderMenuButtons()}
|
||||
{renderAuthButtons()}
|
||||
</Navbar>
|
||||
|
@ -10,7 +10,6 @@ class Dashboard extends React.Component {
|
||||
|
||||
if (!isLoggedIn) {
|
||||
history.push("/login")
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +17,7 @@ class Dashboard extends React.Component {
|
||||
return (
|
||||
<div className="pageWrapper">
|
||||
<h1>
|
||||
Hai Dashboard!
|
||||
Dashboard!
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,7 +1,10 @@
|
||||
import logo from '../logo.png';
|
||||
import '../App.css';
|
||||
import HomeBanner from '../Components/HomeBanner';
|
||||
import React from 'react';
|
||||
|
||||
function Home() {
|
||||
class Home extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
@ -16,8 +19,10 @@ function Home() {
|
||||
>
|
||||
gitlab.com/idanoo/go-scrobble
|
||||
</a>
|
||||
<HomeBanner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Home;
|
||||
|
@ -6,6 +6,8 @@ import { Formik, Form, Field } from 'formik';
|
||||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
||||
import { connect } from 'react-redux';
|
||||
import { login } from '../Actions/auth';
|
||||
import eventBus from "../Actions/eventBus";
|
||||
import { LOGIN_SUCCESS } from '../Actions/types';
|
||||
|
||||
class Login extends React.Component {
|
||||
constructor(props) {
|
||||
@ -13,6 +15,14 @@ class Login extends React.Component {
|
||||
this.state = {username: '', password: '', loading: false};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { history, isLoggedIn } = this.props;
|
||||
|
||||
if (isLoggedIn) {
|
||||
history.push("/dashboard")
|
||||
}
|
||||
}
|
||||
|
||||
handleLogin(values) {
|
||||
this.setState({loading: true});
|
||||
|
||||
@ -22,9 +32,11 @@ class Login extends React.Component {
|
||||
.then(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
isLoggedIn: true
|
||||
});
|
||||
|
||||
eventBus.dispatch(LOGIN_SUCCESS, { isLoggedIn: true });
|
||||
history.push("/dashboard");
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
@ -82,10 +94,8 @@ class Login extends React.Component {
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const { isLoggedIn } = state.auth;
|
||||
const { message } = state.message;
|
||||
return {
|
||||
isLoggedIn,
|
||||
message
|
||||
isLoggedIn
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,10 @@ import { connect } from 'react-redux';
|
||||
|
||||
class Profile extends React.Component {
|
||||
componentDidMount() {
|
||||
const { history } = this.props;
|
||||
const isLoggedIn = this.props.isLoggedIn;
|
||||
const { history, isLoggedIn } = this.props;
|
||||
|
||||
if (!isLoggedIn) {
|
||||
history.push("/login")
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,86 +1,42 @@
|
||||
import React from 'react';
|
||||
import '../App.css';
|
||||
import './Login.css';
|
||||
import { Button } from 'reactstrap';
|
||||
import { Button, Form } from 'reactstrap';
|
||||
import ScaleLoader from "react-spinners/ScaleLoader";
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { register } from '../Actions/auth';
|
||||
import { Formik, Field } from 'formik';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
class Register extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {username: '', email: '', password: '', passwordconfirm: '', loading: false};
|
||||
this.handleUsernameChange = this.handleUsernameChange.bind(this);
|
||||
this.handleEmailChange = this.handleEmailChange.bind(this);
|
||||
this.handlePasswordChange = this.handlePasswordChange.bind(this);
|
||||
this.handlePasswordConfirmChange = this.handlePasswordConfirmChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
handleUsernameChange(event) {
|
||||
this.setState({username: event.target.value});
|
||||
componentDidMount() {
|
||||
const { history, isLoggedIn } = this.props;
|
||||
|
||||
if (isLoggedIn) {
|
||||
history.push("/dashboard")
|
||||
}
|
||||
}
|
||||
|
||||
handleEmailChange(event) {
|
||||
this.setState({email: event.target.value});
|
||||
}
|
||||
|
||||
handlePasswordChange(event) {
|
||||
this.setState({password: event.target.value});
|
||||
}
|
||||
|
||||
handlePasswordConfirmChange(event) {
|
||||
this.setState({passwordconfirm: event.target.value});
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.state.password !== this.state.passwordconfirm) {
|
||||
this.props.addToast('Passwords do not match', { appearance: 'error' });
|
||||
return
|
||||
}
|
||||
|
||||
// if (this.state.password.len < 8) {
|
||||
// this.props.addToast('Password must be at least 8 characters', { appearance: 'error' });
|
||||
// return
|
||||
// }
|
||||
|
||||
handleRegister(values) {
|
||||
this.setState({loading: true});
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 5000,
|
||||
body: JSON.stringify({
|
||||
username: this.state.username,
|
||||
email: this.state.email,
|
||||
password: this.state.password,
|
||||
})
|
||||
};
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL + '/api/v1/register';
|
||||
console.log(apiUrl);
|
||||
fetch(apiUrl, requestOptions)
|
||||
.then((response) => {
|
||||
if (response.status === 429) {
|
||||
this.props.addToast("Rate limited. Please try again soon", { appearance: 'error' });
|
||||
return "{}"
|
||||
} else {
|
||||
return response.json()
|
||||
}
|
||||
const { dispatch, history } = this.props;
|
||||
|
||||
dispatch(register(values.username, values.email, values.password))
|
||||
.then(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
history.push("/login");
|
||||
})
|
||||
.then((function(data) {
|
||||
console.log(data);
|
||||
if (data.error) {
|
||||
this.props.addToast(data.error, { appearance: 'error' });
|
||||
} else if (data.message) {
|
||||
this.props.addToast(data.message, { appearance: 'success' });
|
||||
this.props.history.push('/login')
|
||||
}
|
||||
this.setState({loading: false});
|
||||
}).bind(this))
|
||||
.catch(() => {
|
||||
this.props.addToast('Error submitting form. Please try again', { appearance: 'error' });
|
||||
this.setState({loading: false});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -98,47 +54,46 @@ class Register extends React.Component {
|
||||
Register
|
||||
</h1>
|
||||
<div className="loginBody">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<Formik
|
||||
initialValues={{ username: '', email: '', password: '', passwordconfirm: '' }}
|
||||
onSubmit={async values => this.handleRegister(values)}>
|
||||
<Form>
|
||||
<label>
|
||||
Username*<br/>
|
||||
<input
|
||||
<Field
|
||||
name="username"
|
||||
type="text"
|
||||
required={trueBool}
|
||||
className="loginFields"
|
||||
value={this.state.username}
|
||||
onChange={this.handleUsernameChange}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Email<br/>
|
||||
<input
|
||||
<Field
|
||||
name="email"
|
||||
type="email"
|
||||
className="loginFields"
|
||||
value={this.state.email}
|
||||
onChange={this.handleEmailChange}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Password*<br/>
|
||||
<input
|
||||
<Field
|
||||
name="password"
|
||||
type="password"
|
||||
required={trueBool}
|
||||
className="loginFields"
|
||||
value={this.state.password}
|
||||
onChange={this.handlePasswordChange}
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
<label>
|
||||
Password*<br/>
|
||||
<input
|
||||
Confirm Password*<br/>
|
||||
<Field
|
||||
name="passwordconfirm"
|
||||
type="password"
|
||||
required={trueBool}
|
||||
className="loginFields"
|
||||
value={this.state.passwordconfirm}
|
||||
onChange={this.handlePasswordConfirmChange}
|
||||
/>
|
||||
</label>
|
||||
<br/><br/>
|
||||
@ -148,7 +103,8 @@ class Register extends React.Component {
|
||||
className="loginButton"
|
||||
disabled={this.state.loading}
|
||||
>{this.state.loading ? <ScaleLoader color="#FFF" size={35} /> : "Register"}</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -157,4 +113,11 @@ class Register extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Register);
|
||||
function mapStateToProps(state) {
|
||||
const { isLoggedIn } = state.auth;
|
||||
return {
|
||||
isLoggedIn
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Register);
|
||||
|
@ -6,11 +6,11 @@ import {
|
||||
LOGOUT,
|
||||
} from "../Actions/types";
|
||||
|
||||
const jwt = localStorage.getItem("jwt");
|
||||
const user = JSON.parse(localStorage.getItem('user'));
|
||||
|
||||
const initialState = jwt
|
||||
? { isLoggedIn: true, jwt }
|
||||
: { isLoggedIn: false, jwt };
|
||||
const initialState = user
|
||||
? { isLoggedIn: true, user: user }
|
||||
: { isLoggedIn: false, user: null };
|
||||
|
||||
export default function authReducer(state = initialState, action) {
|
||||
const { type, payload } = action;
|
||||
@ -30,13 +30,16 @@ import {
|
||||
return {
|
||||
...state,
|
||||
isLoggedIn: true,
|
||||
user: payload.user,
|
||||
user: {
|
||||
jwt: payload.jwt,
|
||||
uuid: payload.sub,
|
||||
exp: payload.exp,
|
||||
}
|
||||
};
|
||||
case LOGIN_FAIL:
|
||||
return {
|
||||
...state,
|
||||
isLoggedIn: false,
|
||||
user: null,
|
||||
};
|
||||
case LOGOUT:
|
||||
return {
|
||||
|
13
web/src/Services/api.service.js
Normal file
13
web/src/Services/api.service.js
Normal file
@ -0,0 +1,13 @@
|
||||
import axios from "axios";
|
||||
|
||||
class ApiService {
|
||||
async getStats() {
|
||||
return axios.get(process.env.REACT_APP_API_URL + "stats")
|
||||
.then((response) => {
|
||||
return response.data;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiService();
|
@ -1,8 +1,8 @@
|
||||
export default function authHeader() {
|
||||
const token = JSON.parse(localStorage.getItem('jwt'));
|
||||
const auth = localStorage.getItem('user');
|
||||
|
||||
if (token) {
|
||||
return { Authorization: 'Bearer ' + token };
|
||||
if (auth && auth.jwt) {
|
||||
return { Authorization: 'Bearer ' + auth.jwt };
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
@ -7,10 +7,13 @@ class AuthService {
|
||||
.post(process.env.REACT_APP_API_URL + "login", { username, password })
|
||||
.then((response) => {
|
||||
if (response.data.token) {
|
||||
let user = jwt(response.data.token)
|
||||
localStorage.setItem("jwt", response.data.token);
|
||||
localStorage.setItem("uuid", user.sub);
|
||||
localStorage.setItem("exp", user.exp);
|
||||
let expandedUser = jwt(response.data.token)
|
||||
let user = {
|
||||
jwt: response.data.token,
|
||||
uuid: expandedUser.sub,
|
||||
exp: expandedUser.exp,
|
||||
}
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
}
|
||||
|
||||
return response.data;
|
||||
@ -18,9 +21,7 @@ class AuthService {
|
||||
}
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem("jwt");
|
||||
localStorage.removeItem("uuid");
|
||||
localStorage.removeItem("exp");
|
||||
localStorage.removeItem("user");
|
||||
}
|
||||
|
||||
register(username, email, password) {
|
||||
|
@ -6,7 +6,6 @@ import { HashRouter } from 'react-router-dom';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.min.css'
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import store from "./store";
|
||||
|
||||
ReactDOM.render(
|
||||
|
Loading…
Reference in New Issue
Block a user