mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-09 15:27:30 -05:00
closer to working
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# build environment
|
||||
FROM node:13.12.0-alpine as build
|
||||
FROM node:19-alpine3.16 as build
|
||||
WORKDIR /app
|
||||
ENV PATH /app/node_modules/.bin:$PATH
|
||||
ARG REACT_APP_BACKEND_SERVER
|
||||
@@ -16,7 +16,7 @@ RUN npm run build
|
||||
RUN env
|
||||
|
||||
# production environment
|
||||
FROM node:13.12.0-alpine
|
||||
FROM node:19-alpine3.16
|
||||
RUN mkdir -p /app/public
|
||||
COPY ./package.json /tmp
|
||||
RUN cd /tmp && npm install
|
||||
|
||||
20140
account/package-lock.json
generated
20140
account/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,18 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@reduxjs/toolkit": "^1.1.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"axios": "0.21.1",
|
||||
"connected-react-router": "6.8.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-scripts": "4.0.1",
|
||||
"redux-thunk": "2.3.0",
|
||||
"semantic-ui-css": "2.4.1",
|
||||
"semantic-ui-react": "2.0.1"
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"axios": "1.3.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "6.9.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"redux-thunk": "2.4.2",
|
||||
"semantic-ui-css": "2.5.0",
|
||||
"semantic-ui-react": "2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -40,13 +39,13 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "5.0.0",
|
||||
"connect-mongo": "3.2.0",
|
||||
"express": "4.17.1",
|
||||
"express-session": "1.17.1",
|
||||
"mongoose": "5.11.10",
|
||||
"node-mailjet": "3.3.1",
|
||||
"passport": "0.4.1",
|
||||
"bcrypt": "5.1.0",
|
||||
"connect-mongo": "5.0.0",
|
||||
"express": "4.18.2",
|
||||
"express-session": "1.17.3",
|
||||
"mongoose": "7.0.3",
|
||||
"node-mailjet": "6.0.2",
|
||||
"passport": "0.6.0",
|
||||
"passport-local": "1.0.0",
|
||||
"passport-twitter": "1.0.4"
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@ const path = require("path");
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const bodyParser = require("body-parser");
|
||||
const connectMongo = require("connect-mongo");
|
||||
const MongoStore = require("connect-mongo");
|
||||
const secrets = require("./secrets");
|
||||
|
||||
const MongoStore = connectMongo(session)
|
||||
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.env['REACT_APP_BACKEND_SERVER'] : 'https://api.openmhz.com';
|
||||
var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
|
||||
var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
var dev_server = "http://admin.openmhz.test:3000"
|
||||
var dev_server = "http://account.openmhz.test:3000"
|
||||
|
||||
module.exports = function(app, passport) {
|
||||
app.set("port", 3009)
|
||||
@@ -26,8 +25,6 @@ module.exports = function(app, passport) {
|
||||
app.use(express.static(path.join(process.cwd(), 'public')));
|
||||
|
||||
const sess = {
|
||||
/*saveUninitialized: false,
|
||||
resave: false,*/
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
secret: secrets.sessionSecret,
|
||||
@@ -38,9 +35,12 @@ module.exports = function(app, passport) {
|
||||
secure: false,
|
||||
domain: cookie_domain
|
||||
},
|
||||
store: new MongoStore({
|
||||
url: secrets.db,
|
||||
autoReconnect: true
|
||||
store: MongoStore.create({
|
||||
mongoUrl: secrets.db,
|
||||
autoReconnect: true,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 240,
|
||||
touchAfter: 24 * 3600 // time period in seconds
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
const passport = require("passport");
|
||||
const User = require("../models/user");
|
||||
const Mailjet = require('node-mailjet');
|
||||
|
||||
const mailjet = require("node-mailjet").connect(
|
||||
process.env['MAILJET_KEY'],
|
||||
process.env['MAILJET_SECRET']
|
||||
);
|
||||
const crypto = require("crypto");
|
||||
|
||||
var admin_email = process.env['REACT_APP_ADMIN_EMAIL'] != null ? process.env['REACT_APP_ADMIN_EMAIL'] : "luke@openmhz.com";
|
||||
@@ -12,6 +9,14 @@ var site_name = process.env['REACT_APP_SITE_NAME'] != null ? process.env['REACT_
|
||||
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : "https://account.openmhz.com";
|
||||
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
|
||||
|
||||
|
||||
const mailjet = new Mailjet({
|
||||
apiKey: process.env['MAILJET_KEY'],
|
||||
apiSecret: process.env['MAILJET_SECRET']
|
||||
});
|
||||
|
||||
|
||||
|
||||
exports.isLoggedIn = function(req, res, next) {
|
||||
if (req.isAuthenticated()) return next();
|
||||
res.redirect("/login");
|
||||
@@ -19,6 +24,7 @@ exports.isLoggedIn = function(req, res, next) {
|
||||
|
||||
// -------------------------------------------
|
||||
|
||||
|
||||
exports.authenticated = function(req, res, next) {
|
||||
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
||||
console.log("account/server/controllers/users.js - exports.authenticated() Auth check for user: " + req.user + " is: " + req.isAuthenticated());
|
||||
|
||||
@@ -1,15 +1,39 @@
|
||||
import React, { Component } from "react"
|
||||
import NavigationContainer from "./NavigationContainer"
|
||||
import React, { Component } from "react"
|
||||
import Navigation from "./Navigation"
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return(
|
||||
import Restricted from "./Restricted"
|
||||
|
||||
import Login from "../User/Login"
|
||||
import Register from "../User/Register"
|
||||
import Profile from "../User/Profile"
|
||||
import ConfirmEmail from "../User/ConfirmEmail"
|
||||
import WaitConfirmEmail from "../User/WaitConfirmEmail"
|
||||
import SentConfirmEmail from "../User/SentConfirmEmail"
|
||||
import SendResetPassword from "../User/SendResetPassword"
|
||||
import ResetPassword from "../User/ResetPassword"
|
||||
import Terms from "../User/Terms"
|
||||
|
||||
const App = (params) => {
|
||||
const {store} = params;
|
||||
return (
|
||||
<div>
|
||||
<NavigationContainer />
|
||||
{this.props.children}
|
||||
<Navigation/>
|
||||
<Routes>
|
||||
<Route exact path="/" element={ <Restricted><Profile/></Restricted>} />
|
||||
<Route path="/login" element={ <Login/>} />
|
||||
<Route path="/register" element={ <Register/> } />
|
||||
<Route path="/wait-confirm-email" element={ <WaitConfirmEmail/> } />
|
||||
<Route path="/sent-confirm-email" element={ <SentConfirmEmail/> } />
|
||||
<Route path="/send-reset-password" element={ <SendResetPassword/> } />
|
||||
<Route path="/reset-password/:userId/:token" element={ <ResetPassword/> } />
|
||||
<Route path="/confirm-email/:userId/:token" element={ <ConfirmEmail/> } />
|
||||
<Route path="/terms" element={ <Restricted><Terms/></Restricted>} />
|
||||
<Route path="/profile" element={ <Restricted><Profile/></Restricted>} />
|
||||
<Route render={() => (<div>Miss</div>)} />
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default App
|
||||
|
||||
@@ -1,53 +1,44 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Menu, Dropdown } from "semantic-ui-react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { logoutUser } from "../features/user/userSlice";
|
||||
|
||||
const navStyle = {
|
||||
marginBottom: "30px"
|
||||
};
|
||||
const blankStyle = {
|
||||
marginBottom: "50px"
|
||||
};
|
||||
|
||||
class Navigation extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._logout = this._logout.bind(this);
|
||||
}
|
||||
const Navigation = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const { email } = useSelector((state) => state.user);
|
||||
|
||||
_logout = event => {
|
||||
const logout = event => {
|
||||
event.preventDefault();
|
||||
this.props.manualLogout();
|
||||
dispatch(logoutUser({}));
|
||||
};
|
||||
|
||||
render() {
|
||||
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
|
||||
return (
|
||||
<div>
|
||||
{this.props.user.authenticated ? (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
{/* <Link to="/update-plans">
|
||||
<Menu.Item name="systems">Update Plans</Menu.Item>
|
||||
</Link> */}
|
||||
<Dropdown item text={this.props.user.email}>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
{/* <Dropdown.Item href={billingLink}>Billing</Dropdown.Item> */}
|
||||
<Dropdown.Item onClick={this._logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
) : (
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
OpenMHz
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
|
||||
return (
|
||||
<div>
|
||||
<Menu style={navStyle}>
|
||||
<Menu.Item name="systems" header>
|
||||
{process.env.REACT_APP_SITE_NAME}
|
||||
</Menu.Item>
|
||||
<Menu.Menu position="right">
|
||||
<Dropdown item text={ email }>
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
|
||||
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</Menu.Menu>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Navigation;
|
||||
|
||||
@@ -1,55 +1,32 @@
|
||||
// in src/restricted.js
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import * as types from "../User/user-constants";
|
||||
import axios from "axios";
|
||||
import React, { useEffect} from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { authenticateUser } from "../features/user/userSlice";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
/**
|
||||
* Higher-order component (HOC) to wrap restricted pages
|
||||
*/
|
||||
const restricted = (BaseComponent, store) => {
|
||||
class Restricted extends Component {
|
||||
componentWillMount() {
|
||||
this.checkAuthentication(this.props);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.location !== this.props.location) {
|
||||
this.checkAuthentication(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
checkAuthentication(params) {
|
||||
const { history } = params;
|
||||
const authenticated = store.getState().user.authenticated;
|
||||
if (!authenticated) {
|
||||
axios
|
||||
.get("/authenticated")
|
||||
.then(response => {
|
||||
if (response.data.success) {
|
||||
const user = response.data.user
|
||||
store.dispatch({
|
||||
type: types.LOGIN_SUCCESS_USER,
|
||||
data: user
|
||||
});
|
||||
} else {
|
||||
history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });
|
||||
}
|
||||
})
|
||||
.catch(response => {
|
||||
if (response instanceof Error) {
|
||||
// Something happened during logout that triggered an Error
|
||||
history.replace({ pathname: '/login',
|
||||
state: { nextPathname: params.location.pathname } });
|
||||
console.log("Error", response.message);
|
||||
}
|
||||
});
|
||||
const Restricted = ({children }) => {
|
||||
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(authenticateUser({}));
|
||||
},[]);
|
||||
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <BaseComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
return withRouter(Restricted);
|
||||
if (hasAuthenticated) {
|
||||
if (authenticated) {
|
||||
return children;;
|
||||
}
|
||||
else {
|
||||
|
||||
navigate("/list-systems")
|
||||
}
|
||||
}
|
||||
export default restricted
|
||||
return <div/>
|
||||
|
||||
};
|
||||
|
||||
export default Restricted
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import React, { useEffect, useState, } from "react";
|
||||
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
|
||||
import { loginUser } from "../features/user/userSlice";
|
||||
|
||||
import {
|
||||
Container,
|
||||
@@ -26,31 +28,26 @@ const forgotStyle = {
|
||||
// ----------------------------------------------------
|
||||
|
||||
|
||||
class Login extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._onLoginSubmit = this._onLoginSubmit.bind(this);
|
||||
this.handleInputChange = this.handleInputChange.bind(this);
|
||||
this.handleRegisterClick = this.handleRegisterClick.bind(this);
|
||||
}
|
||||
const Login = (props) => {
|
||||
const [loginMessage, setLoginMessage] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const navigate = useNavigate();
|
||||
let [searchParams, setSearchParams] = useSearchParams();
|
||||
const nextLocation = searchParams.get("nextLocation");
|
||||
|
||||
state = {
|
||||
loginMessage: "",
|
||||
email: "",
|
||||
password: ""
|
||||
};
|
||||
handleRegisterClick = () => {
|
||||
this.props.changeUrl("/register");
|
||||
};
|
||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
_onLoginSubmit(event) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
|
||||
const loginSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
const { email, password } = this.state;
|
||||
/*
|
||||
var success = { type: "path", nextPathname: "/" };
|
||||
if (this.props.nextPathname) {
|
||||
success.nextPathname = this.props.nextPathname;
|
||||
}
|
||||
if (this.props.nextLocation) {
|
||||
if (nextLocation) {
|
||||
success.type = "location";
|
||||
switch (this.props.nextLocation) {
|
||||
case "frontend":
|
||||
@@ -81,11 +78,10 @@ class Login extends Component {
|
||||
loginMessage
|
||||
});
|
||||
}
|
||||
});
|
||||
});*/
|
||||
}
|
||||
|
||||
render() {
|
||||
var loginMessage = "";
|
||||
/*
|
||||
|
||||
if (this.state.loginMessage) {
|
||||
loginMessage = (
|
||||
@@ -98,7 +94,7 @@ class Login extends Component {
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
*/
|
||||
return (
|
||||
<Container>
|
||||
<Header as="h1">{process.env.REACT_APP_SITE_NAME}</Header>
|
||||
@@ -147,12 +143,12 @@ class Login extends Component {
|
||||
<Button
|
||||
size="large"
|
||||
content="Register"
|
||||
onClick={this.handleRegisterClick}
|
||||
onClick={()=>navigate("/register")}
|
||||
fluid
|
||||
/>
|
||||
<Divider style={dividerStyle} horizontal>Or</Divider>
|
||||
<Header as="h3">Login</Header>
|
||||
<Form onSubmit={this._onLoginSubmit}>
|
||||
<Form onSubmit={loginSubmit}>
|
||||
<Segment padded>
|
||||
<Form.Field>
|
||||
<Form.Input
|
||||
@@ -160,7 +156,8 @@ class Login extends Component {
|
||||
iconPosition="left"
|
||||
type="text"
|
||||
name="email"
|
||||
onChange={this.handleInputChange}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
value={email}
|
||||
placeholder="E-mail address"
|
||||
/>
|
||||
</Form.Field>
|
||||
@@ -170,7 +167,8 @@ class Login extends Component {
|
||||
iconPosition="left"
|
||||
type="password"
|
||||
name="password"
|
||||
onChange={this.handleInputChange}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
placeholder="Password"
|
||||
/>
|
||||
</Form.Field>
|
||||
@@ -190,6 +188,6 @@ class Login extends Component {
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Login;
|
||||
|
||||
202
account/src/features/api/apiSlice.js
Normal file
202
account/src/features/api/apiSlice.js
Normal file
@@ -0,0 +1,202 @@
|
||||
// Import the RTK Query methods from the React-specific entry point
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||
import { createSelector } from '@reduxjs/toolkit'
|
||||
|
||||
/*
|
||||
const axiosBaseQuery =
|
||||
({ baseUrl } = { baseUrl: '' }) =>
|
||||
async ({ url, method, data, params }) => {
|
||||
try {
|
||||
const result = await axios({ url: baseUrl + url, method, data, params })
|
||||
return { data: result.data }
|
||||
} catch (axiosError) {
|
||||
let err = axiosError
|
||||
return {
|
||||
error: {
|
||||
status: err.response?.status,
|
||||
data: err.response?.data || err.message,
|
||||
},
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// Define our single API slice object
|
||||
export const apiSlice = createApi({
|
||||
// The cache reducer expects to be added at `state.api` (already default - this is optional)
|
||||
reducerPath: 'api',
|
||||
// All of our requests will have URLs starting with '/fakeApi'
|
||||
baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_ADMIN_SERVER }),
|
||||
// The "endpoints" represent operations and requests for this server
|
||||
endpoints: builder => ({
|
||||
// The `getPosts` endpoint is a "query" operation that returns data
|
||||
getTalkgroups: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `/talkgroups/${shortName}`, credentials: "include" })
|
||||
}),
|
||||
getGroups: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `/groups/${shortName}`, credentials: "include" }),
|
||||
providesTags: (result, error, arg) =>
|
||||
result
|
||||
? [...result.map(({ _id }) => ({ type: 'Group', _id })), 'Group']
|
||||
: ['Group'],
|
||||
}),
|
||||
|
||||
getErrors: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: (shortName) => ({ url: `${process.env.REACT_APP_BACKEND_SERVER}/${shortName}/errors` })
|
||||
}),
|
||||
updateGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}/${group._id}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: group,
|
||||
}),
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: updatedGroup } = await queryFulfilled
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', updatedGroup.shortName, (groups) => {
|
||||
const groupIndex = groups.findIndex((obj => obj.groupId == updatedGroup.groupId));
|
||||
groups[groupIndex] = updatedGroup;
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
createGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: group,
|
||||
}),
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newGroup } = await queryFulfilled;
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', newGroup.shortName, (groups) => {
|
||||
groups.push(newGroup);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
deleteGroup: builder.mutation({
|
||||
query: (group) => ({
|
||||
url: `/groups/${group.shortName}/${group._id}`,
|
||||
method: 'DELETE',
|
||||
credentials: "include"
|
||||
}),
|
||||
|
||||
async onQueryStarted(group, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getGroups', group.shortName, (groups) => {
|
||||
const groupIndex = groups.findIndex((obj => obj.groupId == group.groupId));
|
||||
groups.splice(groupIndex,1);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
}),
|
||||
saveGroupOrder: builder.mutation({
|
||||
query: ({shortName, order}) => ({
|
||||
url: `/groups/${shortName}/reorder`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: order,
|
||||
}),
|
||||
|
||||
//invalidatesTags: (result, error, group) => ['Group'],
|
||||
}),
|
||||
importTalkgroups: builder.mutation({
|
||||
query: ({shortName,file}) => ({
|
||||
url: `${process.env.REACT_APP_ADMIN_SERVER}/talkgroups/${shortName}/import`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: file,
|
||||
}),
|
||||
|
||||
async onQueryStarted({shortName,file}, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newTalkgroups } = await queryFulfilled;
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getTalkgroups', shortName, (talkgroups) => {
|
||||
talkgroups.length = 0; // Clear your array
|
||||
talkgroups.push(...newTalkgroups);
|
||||
})
|
||||
)
|
||||
} catch { }
|
||||
},
|
||||
//invalidatesTags: (result, error, group) => ['Group'],
|
||||
}),
|
||||
getSystems: builder.query({
|
||||
// The URL for the request is '/fakeApi/posts'
|
||||
query: () => ({ url: `/systems`, credentials: "include" })
|
||||
}),
|
||||
createSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: system,
|
||||
}),
|
||||
async onQueryStarted(system, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: newSystem } = await queryFulfilled
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
systems.push(newSystem);
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
updateSystem: builder.mutation({
|
||||
query: (system) => ({
|
||||
url: `/systems/${system.shortName}`,
|
||||
method: 'POST',
|
||||
credentials: "include",
|
||||
body: system,
|
||||
}),
|
||||
async onQueryStarted(system, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: updatedSystem } = await queryFulfilled
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
const systemIndex = systems.findIndex((obj => obj.shortName == updatedSystem.shortName));
|
||||
systems[systemIndex] = updatedSystem;
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
deleteSystem: builder.mutation({
|
||||
query: (shortName) => ({
|
||||
url: `/systems/${shortName}`,
|
||||
method: 'DELETE',
|
||||
credentials: "include"
|
||||
}),
|
||||
async onQueryStarted(shortName, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled
|
||||
const patchResult = dispatch(
|
||||
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
|
||||
const systems = systemsData.systems;
|
||||
const systemIndex = systems.findIndex((obj => obj.shortName == shortName));
|
||||
systems.splice(systemIndex, 1);
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// Export the auto-generated hook for the `getPosts` query endpoint
|
||||
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice
|
||||
107
account/src/features/user/userSlice.js
Normal file
107
account/src/features/user/userSlice.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
createSlice,
|
||||
createEntityAdapter,
|
||||
createSelector,
|
||||
createAsyncThunk
|
||||
} from '@reduxjs/toolkit'
|
||||
|
||||
|
||||
export const authenticateUser = createAsyncThunk(
|
||||
'user/authenticate',
|
||||
async ({ getState }) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/authenticated"
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: "include"
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const loginUser = createAsyncThunk(
|
||||
'user/login',
|
||||
async ({ getState }) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/login"
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: "include"
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
export const logoutUser = createAsyncThunk(
|
||||
'user/logout',
|
||||
async ({ getState }) => {
|
||||
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/logout"
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: "include"
|
||||
}).then(
|
||||
(data) => data.json()
|
||||
)
|
||||
return res;
|
||||
}
|
||||
)
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState: {
|
||||
isWaiting: false,
|
||||
authenticated: false,
|
||||
hasAuthenticated: false,
|
||||
userId: "",
|
||||
admin: false,
|
||||
email: "",
|
||||
},
|
||||
reducers: {
|
||||
increment: (state) => {
|
||||
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
||||
// doesn't actually mutate the state because it uses the Immer library,
|
||||
// which detects changes to a "draft state" and produces a brand new
|
||||
// immutable state based off those changes
|
||||
state.value += 1
|
||||
},
|
||||
},
|
||||
extraReducers: {
|
||||
[authenticateUser.fulfilled]: (state, data) => {
|
||||
const {payload} = data;
|
||||
if (payload.success) {
|
||||
state.authenticated = true;
|
||||
state.hasAuthenticated = true;
|
||||
state.userId = payload.user.userId;
|
||||
state.admin = payload.user.admin;
|
||||
state.email = payload.user.email;
|
||||
} else {
|
||||
state.authenticated = false;
|
||||
state.hasAuthenticated = true;
|
||||
}
|
||||
},
|
||||
[authenticateUser.rejected]: (state, { payload }) => {
|
||||
state.authenticated = false;
|
||||
state.hasAuthenticated = true;
|
||||
|
||||
},
|
||||
[logoutUser.fulfilled]: (state, { payload }) => {
|
||||
if (payload.success) {
|
||||
state.authenticated = false;
|
||||
state.userId = null;
|
||||
}
|
||||
},
|
||||
[loginUser.fulfilled]: (state, { payload }) => {
|
||||
if (payload.success) {
|
||||
state.authenticated = false;
|
||||
state.userId = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Extract the action creators object and the reducer
|
||||
export const userReducer = userSlice.reducer;
|
||||
@@ -1,48 +1,22 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux'
|
||||
import { Route, Switch } from 'react-router' // react-router v4/v5
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import configureStore, { history } from './redux-router/configureStore'
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import setupStore from './redux-router/configureStore'
|
||||
import 'semantic-ui-css/semantic.min.css'
|
||||
import App from "./App/App"
|
||||
import Restricted from "./App/Restricted"
|
||||
|
||||
import LoginContainer from "./User/LoginContainer"
|
||||
import RegisterContainer from "./User/RegisterContainer"
|
||||
import ProfileContainer from "./User/ProfileContainer"
|
||||
import ConfirmEmailContainer from "./User/ConfirmEmailContainer"
|
||||
import WaitConfirmEmailContainer from "./User/WaitConfirmEmailContainer"
|
||||
import SentConfirmEmailContainer from "./User/SentConfirmEmailContainer"
|
||||
import SendResetPasswordContainer from "./User/SendResetPasswordContainer"
|
||||
import ResetPasswordContainer from "./User/ResetPasswordContainer"
|
||||
import TermsContainer from "./User/TermsContainer"
|
||||
|
||||
const store = configureStore(/* provide initial state if any */)
|
||||
const store = setupStore(/* provide initial state if any */)
|
||||
|
||||
console.log(process.env);
|
||||
//This thssi
|
||||
ReactDOM.render(
|
||||
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
|
||||
<>
|
||||
<App>
|
||||
<Switch>
|
||||
<Route exact path="/" component={Restricted(ProfileContainer, store)} />
|
||||
<Route path="/login" component={LoginContainer} />
|
||||
<Route path="/register" component={RegisterContainer} />
|
||||
<Route path="/wait-confirm-email" component={WaitConfirmEmailContainer} />
|
||||
<Route path="/sent-confirm-email" component={SentConfirmEmailContainer} />
|
||||
<Route path="/send-reset-password" component={SendResetPasswordContainer} />
|
||||
<Route path="/reset-password/:userId/:token" component={ResetPasswordContainer} />
|
||||
<Route path="/confirm-email/:userId/:token" component={ConfirmEmailContainer} />
|
||||
<Route path="/terms" component={Restricted(TermsContainer, store)} />
|
||||
<Route path="/profile" component={Restricted(ProfileContainer, store)} />
|
||||
|
||||
</Switch>
|
||||
</App>
|
||||
</>
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
)
|
||||
@@ -1,26 +1,20 @@
|
||||
// https://github.com/supasate/connected-react-router
|
||||
// configureStore.js
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { userReducer } from '../features/user/userSlice'
|
||||
import { apiSlice } from "../features/api/apiSlice"
|
||||
|
||||
import { createBrowserHistory } from 'history'
|
||||
import { applyMiddleware, compose, createStore } from 'redux'
|
||||
import { routerMiddleware } from 'connected-react-router'
|
||||
import createRootReducer from './reducers'
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
export const history = createBrowserHistory()
|
||||
|
||||
export default function configureStore(preloadedState) {
|
||||
const store = createStore(
|
||||
createRootReducer(history), // root reducer with router state
|
||||
const setupStore = (preloadedState) => {
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
user: userReducer,
|
||||
[apiSlice.reducerPath]: apiSlice.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
|
||||
getDefaultMiddleware().concat(apiSlice.middleware),
|
||||
preloadedState,
|
||||
compose(
|
||||
applyMiddleware(
|
||||
routerMiddleware(history), // for dispatching history actions
|
||||
thunk,
|
||||
// ... other middlewares ...
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
export default setupStore;
|
||||
13754
account/yarn.lock
13754
account/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -27,22 +27,6 @@ const App = (params) => {
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
/*
|
||||
return (
|
||||
<div>
|
||||
<NavigationContainer />
|
||||
<Routes>
|
||||
<Route path="/about" element={<About />} component={Restricted(About, store)} />
|
||||
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
|
||||
<Route path="/update-system/:shortName" component={Restricted(UpdateSystemContainer, store)} />
|
||||
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
|
||||
<Route path="/list-systems" component={Restricted(ListSystemsContainer, store)} />
|
||||
<Route path="/" component={Restricted(ListSystemsContainer, store)} />
|
||||
<Route render={() => (<div>Miss</div>)} />
|
||||
</Routes>
|
||||
</div>
|
||||
)*/
|
||||
|
||||
};
|
||||
|
||||
export default App
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import React, { useEffect} from "react";
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import { authenticateUser } from "../features/user/userSlice";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
/**
|
||||
* Higher-order component (HOC) to wrap restricted pages
|
||||
*/
|
||||
@@ -9,14 +10,21 @@ import { authenticateUser } from "../features/user/userSlice";
|
||||
const Restricted = ({children }) => {
|
||||
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(authenticateUser({}));
|
||||
},[]);
|
||||
|
||||
if (authenticated) {
|
||||
return children;;
|
||||
}
|
||||
if (hasAuthenticated) {
|
||||
if (authenticated) {
|
||||
return children;;
|
||||
}
|
||||
else {
|
||||
|
||||
navigate("/list-systems")
|
||||
}
|
||||
}
|
||||
return <div/>
|
||||
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from "react";
|
||||
import React, { useEffect, useState, } from "react";
|
||||
import { useGetSystemsQuery, useGetTalkgroupsQuery, useGetGroupsQuery, useUpdateGroupMutation, useCreateGroupMutation, useDeleteGroupMutation, useSaveGroupOrderMutation} from '../features/api/apiSlice'
|
||||
|
||||
import {
|
||||
@@ -105,7 +105,6 @@ const GroupModal = (props) => {
|
||||
setTalkgroups(talkgroups);
|
||||
}
|
||||
|
||||
const handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||
|
||||
const addTalkgroup = (event, index) => {
|
||||
const item = {
|
||||
|
||||
Reference in New Issue
Block a user