v0.0.4 User Management UI
This commit is contained in:
parent
df0dd49261
commit
863a9a061c
|
@ -1,13 +1,34 @@
|
||||||
// project import
|
// project import
|
||||||
import Routes from './routes';
|
import Routes from './routes';
|
||||||
|
import * as React from 'react';
|
||||||
import ThemeCustomization from './themes';
|
import ThemeCustomization from './themes';
|
||||||
import ScrollTop from './components/ScrollTop';
|
import ScrollTop from './components/ScrollTop';
|
||||||
|
import Snackbar from '@mui/material/Snackbar';
|
||||||
|
import {Alert} from '@mui/material';
|
||||||
|
|
||||||
|
import { setSnackit } from './api/wrap';
|
||||||
|
|
||||||
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== //
|
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== //
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [message, setMessage] = React.useState('');
|
||||||
|
setSnackit((message) => {
|
||||||
|
setMessage(message);
|
||||||
|
setOpen(true);
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<ThemeCustomization>
|
<ThemeCustomization>
|
||||||
|
<Snackbar
|
||||||
|
open={open}
|
||||||
|
autoHideDuration={5000}
|
||||||
|
onClose={() => {setOpen(false)}}
|
||||||
|
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert className={open ? 'shake' : ''} severity="error" sx={{ width: '100%' }}>
|
||||||
|
{message}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
<ScrollTop>
|
<ScrollTop>
|
||||||
<Routes />
|
<Routes />
|
||||||
</ScrollTop>
|
</ScrollTop>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import wrap from './wrap';
|
||||||
|
|
||||||
function list() {
|
function list() {
|
||||||
return fetch('/cosmos/api/users', {
|
return fetch('/cosmos/api/users', {
|
||||||
|
@ -9,6 +10,77 @@ function list() {
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function create(values) {
|
||||||
|
alert(JSON.stringify(values))
|
||||||
|
return wrap(fetch('/cosmos/api/users', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(values) {
|
||||||
|
return fetch('/cosmos/api/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
function invite(values) {
|
||||||
|
return wrap(fetch('/cosmos/api/invite', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit(nickname, values) {
|
||||||
|
return fetch('/cosmos/api/users/'+nickname, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(nickname) {
|
||||||
|
return fetch('/cosmos/api/users/'+nickname, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteUser(nickname) {
|
||||||
|
return fetch('/cosmos/api/users/'+nickname, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
list,
|
list,
|
||||||
|
create,
|
||||||
|
register,
|
||||||
|
invite,
|
||||||
|
edit,
|
||||||
|
get,
|
||||||
|
deleteUser,
|
||||||
};
|
};
|
16
client/src/api/wrap.js
Normal file
16
client/src/api/wrap.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
let snackit;
|
||||||
|
|
||||||
|
export default function wrap(apicall) {
|
||||||
|
return apicall.then(async (response) => {
|
||||||
|
const rep = await response.json();
|
||||||
|
if (response.status == 200) {
|
||||||
|
return rep;
|
||||||
|
}
|
||||||
|
snackit(rep.message);
|
||||||
|
throw new Error(rep);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSnackit(snack) {
|
||||||
|
snackit = snack;
|
||||||
|
}
|
43
client/src/index.css
Normal file
43
client/src/index.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
@keyframes shiny-btn1 {
|
||||||
|
0% { -webkit-transform: scale(0) rotate(45deg); opacity: 0; }
|
||||||
|
80% { -webkit-transform: scale(0) rotate(45deg); opacity: 0.5; }
|
||||||
|
81% { -webkit-transform: scale(4) rotate(45deg); opacity: 1; }
|
||||||
|
100% { -webkit-transform: scale(50) rotate(45deg); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0% { -webkit-transform: translateX(0); }
|
||||||
|
10% { -webkit-transform: translateX(-10px); }
|
||||||
|
20% { -webkit-transform: translateX(10px); }
|
||||||
|
30% { -webkit-transform: translateX(-10px); }
|
||||||
|
40% { -webkit-transform: translateX(10px); }
|
||||||
|
50% { -webkit-transform: translateX(-10px); }
|
||||||
|
60% { -webkit-transform: translateX(10px); }
|
||||||
|
70% { -webkit-transform: translateX(-10px); }
|
||||||
|
80% { -webkit-transform: translateX(10px); }
|
||||||
|
90% { -webkit-transform: translateX(-10px); }
|
||||||
|
100% { -webkit-transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.shinyButton {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shinyButton:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
top: -180px;
|
||||||
|
left: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
animation: shiny-btn1 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shake {
|
||||||
|
animation: shake 1s;
|
||||||
|
animation-iteration-count: 1;
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ import { Provider as ReduxProvider } from 'react-redux';
|
||||||
// apex-chart
|
// apex-chart
|
||||||
import './assets/third-party/apex-chart.css';
|
import './assets/third-party/apex-chart.css';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
// project import
|
// project import
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { store } from './store';
|
import { store } from './store';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// material-ui
|
// material-ui
|
||||||
|
import * as React from 'react';
|
||||||
import { Button, Typography } from '@mui/material';
|
import { Button, Typography } from '@mui/material';
|
||||||
|
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
|
||||||
import Table from '@mui/material/Table';
|
import Table from '@mui/material/Table';
|
||||||
import TableBody from '@mui/material/TableBody';
|
import TableBody from '@mui/material/TableBody';
|
||||||
import TableCell from '@mui/material/TableCell';
|
import TableCell from '@mui/material/TableCell';
|
||||||
|
@ -8,6 +9,15 @@ import TableContainer from '@mui/material/TableContainer';
|
||||||
import TableHead from '@mui/material/TableHead';
|
import TableHead from '@mui/material/TableHead';
|
||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
|
||||||
import * as API from '../../../api';
|
import * as API from '../../../api';
|
||||||
|
|
||||||
|
@ -16,54 +26,219 @@ import MainCard from '../../../components/MainCard';
|
||||||
import isLoggedIn from '../../../isLoggedIn';
|
import isLoggedIn from '../../../isLoggedIn';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
// ==============================|| SAMPLE PAGE ||============================== //
|
|
||||||
|
|
||||||
const UserManagement = () => {
|
const UserManagement = () => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [openCreateForm, setOpenCreateForm] = React.useState(false);
|
||||||
|
const [openDeleteForm, setOpenDeleteForm] = React.useState(false);
|
||||||
|
const [openInviteForm, setOpenInviteForm] = React.useState(false);
|
||||||
|
const [toAction, setToAction] = React.useState(null);
|
||||||
|
|
||||||
const [rows, setRows] = useState([
|
const roles = ['Guest', 'User', 'Admin']
|
||||||
{ id: 0, nickname: '12313', reset:'123132' },
|
|
||||||
{ id: 1, nickname: '354345', reset:'345345' },
|
const [rows, setRows] = useState([]);
|
||||||
]);
|
|
||||||
|
|
||||||
isLoggedIn();
|
isLoggedIn();
|
||||||
|
|
||||||
useEffect(() => {
|
function refresh() {
|
||||||
|
setIsLoading(true);
|
||||||
API.users.list()
|
API.users.list()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
setRows(data.data);
|
setRows(data.data);
|
||||||
|
setIsLoading(false);
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refresh();
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return <MainCard title="Users">
|
function sendlink(nickname) {
|
||||||
<TableContainer component={Paper}>
|
API.users.invite({
|
||||||
|
nickname
|
||||||
|
})
|
||||||
|
.then((values) => {
|
||||||
|
let sendLink = window.location.origin + '/register?nickname='+nickname+'&key=' + values.data.registerKey;
|
||||||
|
setToAction({...values.data, nickname, sendLink});
|
||||||
|
setOpenInviteForm(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{openInviteForm ? <Dialog open={openInviteForm} onClose={() => setOpenInviteForm(false)}>
|
||||||
|
<DialogTitle>Invite User</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Send this link to {toAction.nickname} to invite them to the system:
|
||||||
|
<div>
|
||||||
|
<IconButton size="large" style={{float: 'left'}} aria-label="copy" onClick={
|
||||||
|
() => {
|
||||||
|
navigator.clipboard.writeText(toAction.sendLink);
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
<CopyOutlined />
|
||||||
|
</IconButton><div style={{float: 'left', width: '300px', padding: '5px', background:'rgba(0,0,0,0.15)', whiteSpace: 'nowrap', wordBreak: 'keep-all', overflow: 'auto', fontStyle: 'italic'}}>{toAction.sendLink}</div>
|
||||||
|
</div>
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => {
|
||||||
|
setOpenInviteForm(false);
|
||||||
|
refresh();
|
||||||
|
}}>Close</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>: ''}
|
||||||
|
|
||||||
|
<Dialog open={openDeleteForm} onClose={() => setOpenDeleteForm(false)}>
|
||||||
|
<DialogTitle>Delete User</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Are you sure you want to delete user {toAction} ?
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpenDeleteForm(false)}>Cancel</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
API.users.deleteUser(toAction)
|
||||||
|
.then(() => {
|
||||||
|
refresh();
|
||||||
|
setOpenDeleteForm(false);
|
||||||
|
})
|
||||||
|
}}>Delete</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog open={openCreateForm} onClose={() => setOpenCreateForm(false)}>
|
||||||
|
<DialogTitle>Create User</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Use this form to invite a new user to the system.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="c-nickname"
|
||||||
|
label="Nickname"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="c-email"
|
||||||
|
label="Email Address (Optional)"
|
||||||
|
type="email"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpenCreateForm(false)}>Cancel</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
API.users.create({
|
||||||
|
nickname: document.getElementById('c-nickname').value,
|
||||||
|
email: document.getElementById('c-email').value,
|
||||||
|
}).then(() => {
|
||||||
|
setOpenCreateForm(false);
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
}}>Create</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<MainCard title="Users">
|
||||||
|
<Button variant="contained" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||||
|
refresh();
|
||||||
|
}}>Refresh</Button>
|
||||||
|
<Button variant="contained" color="primary" startIcon={<PlusCircleOutlined />} onClick={() => {
|
||||||
|
setOpenCreateForm(true)
|
||||||
|
}}>Create</Button><br /><br />
|
||||||
|
{isLoading ? <center><br /><CircularProgress color="inherit" /></center>
|
||||||
|
: <TableContainer component={Paper}>
|
||||||
<Table aria-label="simple table">
|
<Table aria-label="simple table">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Nickname</TableCell>
|
<TableCell>Nickname</TableCell>
|
||||||
<TableCell>Role</TableCell>
|
<TableCell>Status</TableCell>
|
||||||
<TableCell>Password</TableCell>
|
<TableCell>Created At</TableCell>
|
||||||
|
<TableCell>Last Login</TableCell>
|
||||||
<TableCell>Actions</TableCell>
|
<TableCell>Actions</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{rows.map((row) => (
|
{rows.map((row) => {
|
||||||
|
const isRegistered = new Date(row.registeredAt).getTime() > 0;
|
||||||
|
const inviteExpired = new Date(row.registerKeyExp).getTime() < new Date().getTime();
|
||||||
|
|
||||||
|
const hasLastLogin = new Date(row.lastLogin).getTime() > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={row.nickname}
|
key={row.nickname}
|
||||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
>
|
>
|
||||||
<TableCell component="th" scope="row">
|
<TableCell component="th" scope="row">
|
||||||
{row.nickname}
|
<strong>{row.nickname}</strong>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>User</TableCell>
|
<TableCell>
|
||||||
<TableCell><Button variant="contained" color="primary">Send Password Link</Button></TableCell>
|
{isRegistered ? (row.role > 1 ? <Chip
|
||||||
<TableCell><Button variant="contained" color="error">Delete</Button></TableCell>
|
icon={<KeyOutlined />}
|
||||||
|
label="Admin"
|
||||||
|
variant="outlined"
|
||||||
|
/> : <Chip
|
||||||
|
icon={<UserOutlined />}
|
||||||
|
label="User"
|
||||||
|
variant="outlined"
|
||||||
|
/>) : (
|
||||||
|
inviteExpired ? <Chip
|
||||||
|
icon={<ExclamationCircleOutlined />}
|
||||||
|
label="Invite Expired"
|
||||||
|
color="error"
|
||||||
|
/> : <Chip
|
||||||
|
icon={<WarningOutlined />}
|
||||||
|
label="Invite Pending"
|
||||||
|
color="warning"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(row.createdAt).toLocaleDateString()} -
|
||||||
|
{new Date(row.createdAt).toLocaleTimeString()}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{hasLastLogin ? <span>
|
||||||
|
{new Date(row.lastLogin).toLocaleDateString()} -
|
||||||
|
{new Date(row.lastLogin).toLocaleTimeString()}
|
||||||
|
</span> : '-'}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{isRegistered ?
|
||||||
|
(<Button variant="contained" color="primary" onClick={
|
||||||
|
() => {
|
||||||
|
sendlink(row.nickname);
|
||||||
|
}
|
||||||
|
}>Send password reset</Button>) :
|
||||||
|
(<Button variant="contained" className={inviteExpired ? 'shinyButton' : ''} onClick={
|
||||||
|
() => {
|
||||||
|
sendlink(row.nickname);
|
||||||
|
}
|
||||||
|
} color="primary">Re-Send Invite</Button>)
|
||||||
|
}
|
||||||
|
<Button variant="contained" color="error" onClick={
|
||||||
|
() => {
|
||||||
|
setToAction(row.nickname);
|
||||||
|
setOpenDeleteForm(true);
|
||||||
|
}
|
||||||
|
}>Delete</Button></TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>}
|
||||||
</MainCard>
|
</MainCard>
|
||||||
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserManagement;
|
export default UserManagement;
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default function Button(theme) {
|
||||||
},
|
},
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
fontWeight: 400
|
fontWeight: 500
|
||||||
},
|
},
|
||||||
contained: {
|
contained: {
|
||||||
...disabledStyle
|
...disabledStyle
|
||||||
|
|
|
@ -5,7 +5,9 @@ export default function InputLabel(theme) {
|
||||||
MuiInputLabel: {
|
MuiInputLabel: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
color: theme.palette.grey[600]
|
color: theme.palette.mode === 'dark' ?
|
||||||
|
theme.palette.grey[500] :
|
||||||
|
theme.palette.grey[600]
|
||||||
},
|
},
|
||||||
outlined: {
|
outlined: {
|
||||||
lineHeight: '0.8em',
|
lineHeight: '0.8em',
|
||||||
|
|
|
@ -33,7 +33,7 @@ const Typography = (fontFamily) => ({
|
||||||
lineHeight: 1.5
|
lineHeight: 1.5
|
||||||
},
|
},
|
||||||
h6: {
|
h6: {
|
||||||
fontWeight: 400,
|
fontWeight: 600,
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
lineHeight: 1.57
|
lineHeight: 1.57
|
||||||
},
|
},
|
||||||
|
|
|
@ -86,6 +86,6 @@
|
||||||
},
|
},
|
||||||
"description": "Cosmos Server",
|
"description": "Cosmos Server",
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"wrapInstallFolder": "src"
|
"wrapInstallFolder": "src"
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
type CreateRequestJSON struct {
|
type CreateRequestJSON struct {
|
||||||
Nickname string `validate:"required,min=3,max=32,alphanum"`
|
Nickname string `validate:"required,min=3,max=32,alphanum"`
|
||||||
|
Email string `validate:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserCreate(w http.ResponseWriter, req *http.Request) {
|
func UserCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -40,6 +41,7 @@ func UserCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
nickname := utils.Sanitize(request.Nickname)
|
nickname := utils.Sanitize(request.Nickname)
|
||||||
|
email := utils.Sanitize(request.Email)
|
||||||
|
|
||||||
c := utils.GetCollection(utils.GetRootAppId(), "users")
|
c := utils.GetCollection(utils.GetRootAppId(), "users")
|
||||||
|
|
||||||
|
@ -57,6 +59,7 @@ func UserCreate(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
_, err3 := c.InsertOne(nil, map[string]interface{}{
|
_, err3 := c.InsertOne(nil, map[string]interface{}{
|
||||||
"Nickname": nickname,
|
"Nickname": nickname,
|
||||||
|
"Email": email,
|
||||||
"Password": "",
|
"Password": "",
|
||||||
"RegisterKey": RegisterKey,
|
"RegisterKey": RegisterKey,
|
||||||
"RegisterKeyExp": RegisterKeyExp,
|
"RegisterKeyExp": RegisterKeyExp,
|
||||||
|
|
|
@ -69,6 +69,18 @@ func UserLogin(w http.ResponseWriter, req *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
_, errE := c.UpdateOne(nil, map[string]interface{}{
|
||||||
|
"Nickname": nickname,
|
||||||
|
}, map[string]interface{}{
|
||||||
|
"$set": map[string]interface{}{
|
||||||
|
"LastLogin": time.Now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if errE != nil {
|
||||||
|
utils.Error("UserLogin: Error while updating user last login", errE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
utils.Error("UserLogin: Method not allowed" + req.Method, nil)
|
utils.Error("UserLogin: Method not allowed" + req.Method, nil)
|
||||||
|
|
|
@ -72,6 +72,10 @@ func UserRegister(w http.ResponseWriter, req *http.Request) {
|
||||||
utils.HTTPError(w, "User Register Error", http.StatusInternalServerError, "UR001")
|
utils.HTTPError(w, "User Register Error", http.StatusInternalServerError, "UR001")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
RegisteredAt := user.RegisteredAt
|
||||||
|
if RegisteredAt.IsZero() {
|
||||||
|
RegisteredAt = time.Now()
|
||||||
|
}
|
||||||
_, err4 := c.UpdateOne(nil, map[string]interface{}{
|
_, err4 := c.UpdateOne(nil, map[string]interface{}{
|
||||||
"Nickname": nickname,
|
"Nickname": nickname,
|
||||||
"RegisterKey": registerKey,
|
"RegisterKey": registerKey,
|
||||||
|
@ -81,7 +85,8 @@ func UserRegister(w http.ResponseWriter, req *http.Request) {
|
||||||
"Password": hashedPassword,
|
"Password": hashedPassword,
|
||||||
"RegisterKey": "",
|
"RegisterKey": "",
|
||||||
"RegisterKeyExp": time.Time{},
|
"RegisterKeyExp": time.Time{},
|
||||||
"RegisteredAt": time.Now(),
|
"RegisteredAt": RegisteredAt,
|
||||||
|
"LastPasswordChangedAt": time.Now(),
|
||||||
"PassowrdCycle": user.PasswordCycle + 1,
|
"PassowrdCycle": user.PasswordCycle + 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -51,10 +51,13 @@ func UserResendInviteLink(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
RegisterKeyExp := time.Now().Add(time.Hour * 24 * 7)
|
RegisterKeyExp := time.Now().Add(time.Hour * 24 * 7)
|
||||||
RegisterKey := utils.GenerateRandomString(24)
|
RegisterKey := utils.GenerateRandomString(48)
|
||||||
|
|
||||||
|
utils.Debug(RegisterKey)
|
||||||
|
utils.Debug(RegisterKeyExp.String())
|
||||||
|
|
||||||
_, err := c.UpdateOne(nil, map[string]interface{}{
|
_, err := c.UpdateOne(nil, map[string]interface{}{
|
||||||
"nickname": nickname,
|
"Nickname": nickname,
|
||||||
}, map[string]interface{}{
|
}, map[string]interface{}{
|
||||||
"$set": map[string]interface{}{
|
"$set": map[string]interface{}{
|
||||||
"RegisterKeyExp": RegisterKeyExp,
|
"RegisterKeyExp": RegisterKeyExp,
|
||||||
|
@ -73,7 +76,7 @@ func UserResendInviteLink(w http.ResponseWriter, req *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"registerKey": user.RegisterKey,
|
"registerKey": RegisterKey,
|
||||||
"registerKeyExp": RegisterKeyExp,
|
"registerKeyExp": RegisterKeyExp,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -47,6 +47,11 @@ type User struct {
|
||||||
Role Role `validate:"required" json:"role"`
|
Role Role `validate:"required" json:"role"`
|
||||||
PasswordCycle int `json:"-"`
|
PasswordCycle int `json:"-"`
|
||||||
Link string `json:"link"`
|
Link string `json:"link"`
|
||||||
|
Email string `validate:"email" json:"email"`
|
||||||
|
RegisteredAt time.Time `json:"registeredAt"`
|
||||||
|
LastPasswordChangedAt time.Time `json:"lastPasswordChangedAt"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
LastLogin time.Time `json:"lastLogin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
Loading…
Reference in a new issue