mirror of
https://github.com/diced/zipline.git
synced 2026-01-05 01:07:43 -08:00
eslinting
This commit is contained in:
@@ -1,48 +1,44 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import PropTypes from "prop-types";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import Hidden from "@material-ui/core/Hidden";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import HomeIcon from "@material-ui/icons/Home";
|
||||
import DataUsageIcon from "@material-ui/icons/DataUsage";
|
||||
import PhotoIcon from "@material-ui/icons/Photo";
|
||||
import LinkIcon from "@material-ui/icons/Link";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import MailIcon from "@material-ui/icons/Mail";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import DataUsageIcon from '@material-ui/icons/DataUsage';
|
||||
import PhotoIcon from '@material-ui/icons/Photo';
|
||||
import LinkIcon from '@material-ui/icons/Link';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
},
|
||||
drawer: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
},
|
||||
},
|
||||
appBar: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: `calc(100%)`,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'calc(100%)',
|
||||
marginLeft: drawerWidth,
|
||||
},
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
display: "none",
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
// necessary for content to be below app bar
|
||||
@@ -56,8 +52,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
export default function UI(props) {
|
||||
const { window } = props;
|
||||
export default function UI({ children }) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
||||
@@ -69,18 +64,18 @@ export default function UI(props) {
|
||||
const drawer = (
|
||||
<div>
|
||||
<Toolbar>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<AppBar position='fixed' className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
color='inherit'
|
||||
aria-label='open drawer'
|
||||
edge='start'
|
||||
onClick={handleDrawerToggle}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
<Typography variant='h6' noWrap>
|
||||
Zipline
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
@@ -88,36 +83,36 @@ export default function UI(props) {
|
||||
</Toolbar>
|
||||
|
||||
<List>
|
||||
<Link href="/">
|
||||
<ListItem button key="Home">
|
||||
<Link href='/'>
|
||||
<ListItem button key='Home'>
|
||||
<ListItemIcon>
|
||||
<HomeIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Home" />
|
||||
<ListItemText primary='Home' />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link href="/statistics">
|
||||
<ListItem button key="Statistics">
|
||||
<Link href='/statistics'>
|
||||
<ListItem button key='Statistics'>
|
||||
<ListItemIcon>
|
||||
<DataUsageIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Statistics" />
|
||||
<ListItemText primary='Statistics' />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link href="/images">
|
||||
<ListItem button key="Images">
|
||||
<Link href='/images'>
|
||||
<ListItem button key='Images'>
|
||||
<ListItemIcon>
|
||||
<PhotoIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Images" />
|
||||
<ListItemText primary='Images' />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link href="/urls">
|
||||
<ListItem button key="URLs">
|
||||
<Link href='/urls'>
|
||||
<ListItem button key='URLs'>
|
||||
<ListItemIcon>
|
||||
<LinkIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="URLs" />
|
||||
<ListItemText primary='URLs' />
|
||||
</ListItem>
|
||||
</Link>
|
||||
</List>
|
||||
@@ -125,32 +120,32 @@ export default function UI(props) {
|
||||
);
|
||||
|
||||
const container =
|
||||
window !== undefined ? () => window().document.body : undefined;
|
||||
window !== undefined ? () => window.document.body : undefined;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<AppBar position='fixed' className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
color='inherit'
|
||||
aria-label='open drawer'
|
||||
edge='start'
|
||||
onClick={handleDrawerToggle}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
<Typography variant='h6' noWrap>
|
||||
Zipline
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<nav className={classes.drawer} aria-label="mailbox folders">
|
||||
<Hidden smUp implementation="css">
|
||||
<nav className={classes.drawer} aria-label='mailbox folders'>
|
||||
<Hidden smUp implementation='css'>
|
||||
<Drawer
|
||||
container={container}
|
||||
variant="temporary"
|
||||
anchor={theme.direction === "rtl" ? "right" : "left"}
|
||||
variant='temporary'
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
classes={{
|
||||
@@ -163,12 +158,12 @@ export default function UI(props) {
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Hidden>
|
||||
<Hidden xsDown implementation="css">
|
||||
<Hidden xsDown implementation='css'>
|
||||
<Drawer
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
variant="permanent"
|
||||
variant='permanent'
|
||||
open
|
||||
>
|
||||
{drawer}
|
||||
@@ -178,7 +173,7 @@ export default function UI(props) {
|
||||
|
||||
<main className={classes.content}>
|
||||
<div className={classes.toolbar} />
|
||||
{props.children}
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
import AppBar from "@material-ui/core/AppBar";
|
||||
import Drawer from "@material-ui/core/Drawer";
|
||||
import Hidden from "@material-ui/core/Hidden";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import Box from "@material-ui/core/Box";
|
||||
import MenuIcon from "@material-ui/icons/Menu";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Skeleton from "@material-ui/lab/Skeleton";
|
||||
import { makeStyles, useTheme } from "@material-ui/core/styles";
|
||||
import React from 'react';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Skeleton from '@material-ui/lab/Skeleton';
|
||||
import { makeStyles, useTheme } from '@material-ui/core/styles';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
},
|
||||
drawer: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
},
|
||||
},
|
||||
appBar: {
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: `calc(100%)`,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'calc(100%)',
|
||||
marginLeft: drawerWidth,
|
||||
},
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
display: "none",
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
// necessary for content to be below app bar
|
||||
@@ -49,12 +47,11 @@ const useStyles = makeStyles((theme) => ({
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
fullWidth: {
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function UIPlaceholder(props) {
|
||||
const { window } = props;
|
||||
export default function UIPlaceholder() {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
||||
@@ -66,18 +63,18 @@ export default function UIPlaceholder(props) {
|
||||
const drawer = (
|
||||
<div>
|
||||
<Toolbar>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<AppBar position='fixed' className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
color='inherit'
|
||||
aria-label='open drawer'
|
||||
edge='start'
|
||||
onClick={handleDrawerToggle}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
<Typography variant='h6' noWrap>
|
||||
Zipline
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
@@ -85,49 +82,49 @@ export default function UIPlaceholder(props) {
|
||||
</Toolbar>
|
||||
|
||||
<List>
|
||||
<ListItem button key="Home">
|
||||
<Skeleton animation="wave" className={classes.fullWidth} />
|
||||
<ListItem button key='Home'>
|
||||
<Skeleton animation='wave' className={classes.fullWidth} />
|
||||
</ListItem>
|
||||
<ListItem button key="Statistics">
|
||||
<Skeleton animation="wave" className={classes.fullWidth} />
|
||||
<ListItem button key='Statistics'>
|
||||
<Skeleton animation='wave' className={classes.fullWidth} />
|
||||
</ListItem>
|
||||
<ListItem button key="Images">
|
||||
<Skeleton animation="wave" className={classes.fullWidth} />
|
||||
<ListItem button key='Images'>
|
||||
<Skeleton animation='wave' className={classes.fullWidth} />
|
||||
</ListItem>
|
||||
<ListItem button key="URLs">
|
||||
<Skeleton animation="wave" className={classes.fullWidth} />
|
||||
<ListItem button key='URLs'>
|
||||
<Skeleton animation='wave' className={classes.fullWidth} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
|
||||
const container =
|
||||
window !== undefined ? () => window().document.body : undefined;
|
||||
window !== undefined ? () => window.document.body : undefined;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<AppBar position='fixed' className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
color='inherit'
|
||||
aria-label='open drawer'
|
||||
edge='start'
|
||||
onClick={handleDrawerToggle}
|
||||
className={classes.menuButton}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
<Typography variant='h6' noWrap>
|
||||
Zipline
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<nav className={classes.drawer} aria-label="mailbox folders">
|
||||
<Hidden smUp implementation="css">
|
||||
<nav className={classes.drawer} aria-label='mailbox folders'>
|
||||
<Hidden smUp implementation='css'>
|
||||
<Drawer
|
||||
container={container}
|
||||
variant="temporary"
|
||||
anchor={theme.direction === "rtl" ? "right" : "left"}
|
||||
variant='temporary'
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
classes={{
|
||||
@@ -140,12 +137,12 @@ export default function UIPlaceholder(props) {
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Hidden>
|
||||
<Hidden xsDown implementation="css">
|
||||
<Hidden xsDown implementation='css'>
|
||||
<Drawer
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
}}
|
||||
variant="permanent"
|
||||
variant='permanent'
|
||||
open
|
||||
>
|
||||
{drawer}
|
||||
@@ -158,10 +155,10 @@ export default function UIPlaceholder(props) {
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
style={{ minHeight: "80vh" }}
|
||||
direction='column'
|
||||
alignItems='center'
|
||||
justify='center'
|
||||
style={{ minHeight: '80vh' }}
|
||||
>
|
||||
<Grid item xs={3}>
|
||||
<CircularProgress size={100} />
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { FastifyReply, FastifyRequest, FastifyInstance } from "fastify";
|
||||
import {
|
||||
Controller,
|
||||
GET,
|
||||
POST,
|
||||
FastifyInstanceToken,
|
||||
Inject,
|
||||
Hook,
|
||||
} from "fastify-decorators";
|
||||
import { User } from "../entities/User";
|
||||
|
||||
@Controller("/api")
|
||||
export class RootController {
|
||||
@Inject(FastifyInstanceToken)
|
||||
private instance!: FastifyInstance;
|
||||
|
||||
@POST("/upload")
|
||||
async loginStatus(req: FastifyRequest, reply: FastifyReply) {}
|
||||
}
|
||||
@@ -1,64 +1,61 @@
|
||||
import { FastifyReply, FastifyRequest, FastifyInstance } from "fastify";
|
||||
import { FastifyReply, FastifyRequest, FastifyInstance } from 'fastify';
|
||||
import {
|
||||
Controller,
|
||||
GET,
|
||||
POST,
|
||||
FastifyInstanceToken,
|
||||
Inject,
|
||||
Hook,
|
||||
} from "fastify-decorators";
|
||||
import { Repository } from "typeorm";
|
||||
import { User } from "../entities/User";
|
||||
} from 'fastify-decorators';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from '../entities/User';
|
||||
import {
|
||||
UserNotFoundError,
|
||||
MissingBodyData,
|
||||
LoginError,
|
||||
UserExistsError,
|
||||
NotAdministratorError,
|
||||
} from "../lib/api/APIErrors";
|
||||
} from '../lib/api/APIErrors';
|
||||
import {
|
||||
checkPassword,
|
||||
createBaseCookie,
|
||||
createToken,
|
||||
encryptPassword,
|
||||
readBaseCookie,
|
||||
} from "../lib/Encryption";
|
||||
} from '../lib/Encryption';
|
||||
|
||||
@Controller("/api/user")
|
||||
@Controller('/api/user')
|
||||
export class UserController {
|
||||
@Inject(FastifyInstanceToken)
|
||||
private instance!: FastifyInstance;
|
||||
|
||||
private users: Repository<User> = this.instance.orm.getRepository(User);
|
||||
|
||||
@GET("/login-status")
|
||||
@GET('/login-status')
|
||||
async loginStatus(req: FastifyRequest, reply: FastifyReply) {
|
||||
return reply.send({
|
||||
user: !!req.cookies.zipline,
|
||||
});
|
||||
}
|
||||
|
||||
@GET("/current")
|
||||
@GET('/current')
|
||||
async currentUser(req: FastifyRequest, reply: FastifyReply) {
|
||||
if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
if (!req.cookies.zipline) throw new LoginError('Not logged in.');
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
id: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
});
|
||||
if (!user) throw new UserExistsError(`User doesn't exist`);
|
||||
if (!user) throw new UserExistsError('User doesn\'t exist');
|
||||
delete user.password;
|
||||
return reply.send(user);
|
||||
}
|
||||
|
||||
@POST("/login")
|
||||
@POST('/login')
|
||||
async login(
|
||||
req: FastifyRequest<{ Body: { username: string; password: string } }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
if (req.cookies.zipline) throw new LoginError(`Already logged in.`);
|
||||
if (!req.body.username) throw new MissingBodyData(`Missing username.`);
|
||||
if (!req.body.password) throw new MissingBodyData(`Missing uassword.`);
|
||||
if (req.cookies.zipline) throw new LoginError('Already logged in.');
|
||||
if (!req.body.username) throw new MissingBodyData('Missing username.');
|
||||
if (!req.body.password) throw new MissingBodyData('Missing uassword.');
|
||||
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
@@ -69,27 +66,27 @@ export class UserController {
|
||||
if (!user)
|
||||
throw new UserNotFoundError(`User "${req.body.username}" was not found.`);
|
||||
if (!checkPassword(req.body.password, user.password))
|
||||
throw new LoginError(`Wrong credentials!`);
|
||||
throw new LoginError('Wrong credentials!');
|
||||
delete user.password;
|
||||
|
||||
return reply
|
||||
.setCookie("zipline", createBaseCookie(user.id), { path: "/" })
|
||||
.setCookie('zipline', createBaseCookie(user.id), { path: '/' })
|
||||
.send(user);
|
||||
}
|
||||
|
||||
@POST("/logout")
|
||||
@POST('/logout')
|
||||
async logout(req: FastifyRequest, reply: FastifyReply) {
|
||||
if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
if (!req.cookies.zipline) throw new LoginError('Not logged in.');
|
||||
try {
|
||||
reply.clearCookie("zipline", { path: "/" }).send({ clearStore: true });
|
||||
reply.clearCookie('zipline', { path: '/' }).send({ clearStore: true });
|
||||
} catch (e) {
|
||||
reply.send({ clearStore: false });
|
||||
}
|
||||
}
|
||||
|
||||
@POST("/reset-token")
|
||||
@POST('/reset-token')
|
||||
async resetToken(req: FastifyRequest, reply: FastifyReply) {
|
||||
if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
if (!req.cookies.zipline) throw new LoginError('Not logged in.');
|
||||
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
@@ -97,7 +94,7 @@ export class UserController {
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) throw new UserNotFoundError(`User was not found.`);
|
||||
if (!user) throw new UserNotFoundError('User was not found.');
|
||||
|
||||
user.token = createToken();
|
||||
this.users.save(user);
|
||||
@@ -105,20 +102,20 @@ export class UserController {
|
||||
return reply.send({ updated: true });
|
||||
}
|
||||
|
||||
@POST("/create")
|
||||
@POST('/create')
|
||||
async create(
|
||||
req: FastifyRequest<{
|
||||
Body: { username: string; password: string; administrator: boolean };
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
if (!req.body.username) throw new MissingBodyData(`Missing username.`);
|
||||
if (!req.body.password) throw new MissingBodyData(`Missing uassword.`);
|
||||
if (!req.body.username) throw new MissingBodyData('Missing username.');
|
||||
if (!req.body.password) throw new MissingBodyData('Missing uassword.');
|
||||
|
||||
const existing = await this.users.findOne({
|
||||
where: { username: req.body.username },
|
||||
});
|
||||
if (existing) throw new UserExistsError("User exists already");
|
||||
if (existing) throw new UserExistsError('User exists already');
|
||||
|
||||
try {
|
||||
const user = await this.users.save(
|
||||
@@ -136,15 +133,15 @@ export class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
@Hook("preValidation")
|
||||
public async preValidation(req: FastifyRequest, reply: FastifyReply) {
|
||||
// const adminRoutes = ['/api/user/create'];
|
||||
// if (adminRoutes.includes(req.routerPath)) {
|
||||
// if (!req.cookies.zipline) return reply.send({ error: "You are not logged in" });
|
||||
// const admin = await this.instance.mongo.db.collection('zipline_users').findOne({ _id: req.cookies.zipline });
|
||||
// if (!admin) return reply.send({ error: "You are not an administrator" });
|
||||
// return;
|
||||
// }
|
||||
// return;
|
||||
}
|
||||
// @Hook('preValidation')
|
||||
// public async preValidation(req: FastifyRequest, reply: FastifyReply) {
|
||||
// // const adminRoutes = ['/api/user/create'];
|
||||
// // if (adminRoutes.includes(req.routerPath)) {
|
||||
// // if (!req.cookies.zipline) return reply.send({ error: "You are not logged in" });
|
||||
// // const admin = await this.instance.mongo.db.collection('zipline_users').findOne({ _id: req.cookies.zipline });
|
||||
// // if (!admin) return reply.send({ error: "You are not an administrator" });
|
||||
// // return;
|
||||
// // }
|
||||
// // return;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column("text")
|
||||
@Column('text')
|
||||
public username: string;
|
||||
|
||||
@Column("text")
|
||||
@Column('text')
|
||||
public password: string;
|
||||
|
||||
@Column("boolean", { default: false })
|
||||
@Column('boolean', { default: false })
|
||||
public administrator: boolean;
|
||||
|
||||
@Column("text")
|
||||
@Column('text')
|
||||
public token: string;
|
||||
|
||||
public constructor(
|
||||
username: string,
|
||||
password: string,
|
||||
token: string,
|
||||
administrator: boolean = false
|
||||
administrator = false
|
||||
) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
||||
52
src/index.ts
52
src/index.ts
@@ -1,35 +1,34 @@
|
||||
import next from "next";
|
||||
import fastify from "fastify";
|
||||
import fastifyTypeorm from "fastify-typeorm-plugin";
|
||||
import fastifyCookies from "fastify-cookie";
|
||||
import fastifyMultipart from "fastify-multipart";
|
||||
import { bootstrap } from "fastify-decorators";
|
||||
import { RootController } from "./controllers/RootController";
|
||||
import { Console } from "./lib/logger";
|
||||
import { AddressInfo } from "net";
|
||||
import { ConsoleFormatter } from "./lib/ConsoleFormatter";
|
||||
import { bold, green, reset } from "@dicedtomato/colors";
|
||||
import { Configuration } from "./lib/Config";
|
||||
// import { UserController } from './controllers/UserController';
|
||||
import next from 'next';
|
||||
import fastify from 'fastify';
|
||||
import fastifyTypeorm from 'fastify-typeorm-plugin';
|
||||
import fastifyCookies from 'fastify-cookie';
|
||||
import fastifyMultipart from 'fastify-multipart';
|
||||
import { bootstrap } from 'fastify-decorators';
|
||||
import { Console } from './lib/logger';
|
||||
import { AddressInfo } from 'net';
|
||||
import { ConsoleFormatter } from './lib/ConsoleFormatter';
|
||||
import { bold, green, reset } from '@dicedtomato/colors';
|
||||
import { Configuration } from './lib/Config';
|
||||
import { UserController } from './controllers/UserController';
|
||||
|
||||
Console.setFormatter(new ConsoleFormatter());
|
||||
|
||||
const config = Configuration.readConfig();
|
||||
if (!config) process.exit(0);
|
||||
const server = fastify({});
|
||||
const dev = process.env.NODE_ENV !== "production";
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const app = next({ dev, quiet: dev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
Console.logger("Next").info(`Preparing app`);
|
||||
Console.logger('Next').info('Preparing app');
|
||||
app.prepare();
|
||||
|
||||
if (dev)
|
||||
server.get("/_next/*", (req, reply) => {
|
||||
server.get('/_next/*', (req, reply) => {
|
||||
return handle(req.raw, reply.raw).then(() => (reply.sent = true));
|
||||
});
|
||||
|
||||
server.all("/*", (req, reply) => {
|
||||
server.all('/*', (req, reply) => {
|
||||
return handle(req.raw, reply.raw).then(() => (reply.sent = true));
|
||||
});
|
||||
|
||||
@@ -41,34 +40,25 @@ server.register(fastifyMultipart);
|
||||
|
||||
server.register(fastifyTypeorm, {
|
||||
...config.database,
|
||||
entities: [dev ? "./src/entities/**/*.ts" : "./dist/entities/**/*.js"],
|
||||
entities: [dev ? './src/entities/**/*.ts' : './dist/entities/**/*.js'],
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
});
|
||||
|
||||
server.register(bootstrap, {
|
||||
controllers: [
|
||||
// UserController,
|
||||
RootController,
|
||||
],
|
||||
controllers: [UserController],
|
||||
});
|
||||
|
||||
// server.register(fastifyMongodb, {
|
||||
// forceClose: true,
|
||||
// url: config.mongo.url,
|
||||
// database: config.mongo.database
|
||||
// });
|
||||
|
||||
server.register(fastifyCookies, {
|
||||
secret: config.core.secret,
|
||||
});
|
||||
|
||||
server.listen(config.core.port, (err) => {
|
||||
server.listen(config.core.port, err => {
|
||||
if (err) throw err;
|
||||
const info = server.server.address() as AddressInfo;
|
||||
Console.logger("Server").info(
|
||||
Console.logger('Server').info(
|
||||
`server listening on ${bold(
|
||||
`${green(info.address)}${reset(":")}${bold(green(info.port.toString()))}`
|
||||
`${green(info.address)}${reset(':')}${bold(green(info.port.toString()))}`
|
||||
)}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { parse } from "toml";
|
||||
import { ConnectionOptions } from "typeorm";
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { parse } from 'toml';
|
||||
import { ConnectionOptions } from 'typeorm';
|
||||
|
||||
export interface Config {
|
||||
database: ConnectionOptions;
|
||||
@@ -21,7 +21,7 @@ export interface ConfigCore {
|
||||
export class Configuration {
|
||||
static readConfig(): Config {
|
||||
try {
|
||||
const data = readFileSync(resolve(process.cwd(), "Zipline.toml"), "utf8");
|
||||
const data = readFileSync(resolve(process.cwd(), 'Zipline.toml'), 'utf8');
|
||||
return parse(data) as Config;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { bold, magenta, reset } from "@dicedtomato/colors";
|
||||
import { ConsoleLevel, Formatter } from "./logger";
|
||||
import { bold, magenta, reset } from '@dicedtomato/colors';
|
||||
import { Formatter } from './logger';
|
||||
|
||||
export class ConsoleFormatter implements Formatter {
|
||||
format(
|
||||
message: string,
|
||||
origin: string,
|
||||
level: ConsoleLevel,
|
||||
time: Date
|
||||
): string {
|
||||
return `${bold(magenta(origin))} ${bold(">")} ${reset(message)}`;
|
||||
format(message: string, origin: string): string {
|
||||
return `${bold(magenta(origin))} ${bold('>')} ${reset(message)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
export interface User {
|
||||
username: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
administrator: boolean;
|
||||
_id?: any;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: string;
|
||||
user: any;
|
||||
views: number;
|
||||
_id?: any;
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import aes from "crypto-js/aes";
|
||||
import { compareSync, hashSync } from "bcrypt";
|
||||
import { Configuration } from "./Config";
|
||||
import aes from 'crypto-js/aes';
|
||||
import { compareSync, hashSync } from 'bcrypt';
|
||||
import { Configuration } from './Config';
|
||||
|
||||
const config = Configuration.readConfig();
|
||||
if (!config) process.exit(0);
|
||||
|
||||
export function createRandomId(
|
||||
length: number,
|
||||
charset: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
) {
|
||||
let result = "";
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++)
|
||||
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||
return result;
|
||||
@@ -21,18 +21,18 @@ export function createToken() {
|
||||
.toString();
|
||||
}
|
||||
|
||||
export function encryptPassword(pass: string) {
|
||||
export function encryptPassword(pass) {
|
||||
return hashSync(pass, 10);
|
||||
}
|
||||
|
||||
export function checkPassword(will: string, equal: string) {
|
||||
export function checkPassword(will, equal) {
|
||||
return compareSync(will, equal);
|
||||
}
|
||||
|
||||
export function createBaseCookie(id: number) {
|
||||
return Buffer.from(id.toString()).toString("base64");
|
||||
return Buffer.from(id.toString()).toString('base64');
|
||||
}
|
||||
|
||||
export function readBaseCookie(data: string) {
|
||||
return Buffer.from(data, "base64").toString();
|
||||
export function readBaseCookie(data) {
|
||||
return Buffer.from(data, 'base64').toString();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Formatter, DefaultFormatter } from "./Formatter";
|
||||
import { Formatter, DefaultFormatter } from './Formatter';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line
|
||||
module NodeJS {
|
||||
interface Global {
|
||||
logr: { formatter: Formatter };
|
||||
@@ -25,36 +26,37 @@ export class Console {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public debug(message: string) {
|
||||
public debug(message: string): string {
|
||||
return this.log(ConsoleLevel.DEBUG, message);
|
||||
}
|
||||
|
||||
public error(message: string) {
|
||||
public error(message: string): string {
|
||||
return this.log(ConsoleLevel.ERROR, message);
|
||||
}
|
||||
|
||||
public info(message: string) {
|
||||
public info(message: string): string {
|
||||
return this.log(ConsoleLevel.INFO, message);
|
||||
}
|
||||
|
||||
public trace(message: string) {
|
||||
public trace(message: string): string {
|
||||
return this.log(ConsoleLevel.TRACE, message);
|
||||
}
|
||||
|
||||
public warn(message: string) {
|
||||
public warn(message: string): string {
|
||||
return this.log(ConsoleLevel.WARN, message);
|
||||
}
|
||||
|
||||
public log(level: ConsoleLevel, message: string) {
|
||||
public log(level: ConsoleLevel, message: string): string {
|
||||
const formatter = global.logr.formatter || new DefaultFormatter();
|
||||
console.log(formatter.format(message, this.name, level, new Date()));
|
||||
return formatter.format(message, this.name, level, new Date());
|
||||
}
|
||||
|
||||
public static setFormatter(formatter: Formatter) {
|
||||
public static setFormatter(formatter: Formatter): void {
|
||||
global.logr.formatter = formatter;
|
||||
}
|
||||
public static logger(o: string | Function) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
public static logger(o: string | Function): Console {
|
||||
const name = o instanceof Function ? o.name : o;
|
||||
return new Console(name);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConsoleLevel } from "./Console";
|
||||
import { brightGreen, blue } from "@dicedtomato/colors";
|
||||
import { ConsoleLevel } from './Console';
|
||||
import { brightGreen, blue } from '@dicedtomato/colors';
|
||||
|
||||
export interface Formatter {
|
||||
format(
|
||||
@@ -16,7 +16,7 @@ export class DefaultFormatter implements Formatter {
|
||||
origin: string,
|
||||
level: ConsoleLevel,
|
||||
time: Date
|
||||
) {
|
||||
): string {
|
||||
return `[${time.toLocaleString()}] ${brightGreen(origin)} - ${blue(
|
||||
ConsoleLevel[level]
|
||||
)}: ${message}`;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./Console";
|
||||
export * from "./Formatter";
|
||||
export * from './Console';
|
||||
export * from './Formatter';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { REHYDRATE } from "redux-persist";
|
||||
import { User } from "./Data";
|
||||
/* eslint-disable indent */
|
||||
import { User } from '../entities/User';
|
||||
|
||||
export const LOGIN = "LOGIN";
|
||||
export const LOGOUT = "LOGOUT";
|
||||
export const UPDATE_USER = "UPDATE_USER";
|
||||
export const LOGIN = 'LOGIN';
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
export const UPDATE_USER = 'UPDATE_USER';
|
||||
export interface State {
|
||||
loggedIn: boolean;
|
||||
user: User;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { createStore } from "redux";
|
||||
import { persistStore, persistReducer } from "redux-persist";
|
||||
import storage from "redux-persist/lib/storage";
|
||||
import { createStore } from 'redux';
|
||||
import { persistStore, persistReducer } from 'redux-persist';
|
||||
import storage from 'redux-persist/lib/storage';
|
||||
|
||||
import { reducer } from "./reducer";
|
||||
import { reducer } from './reducer';
|
||||
|
||||
const persistedReducer = persistReducer(
|
||||
{
|
||||
key: "root",
|
||||
key: 'root',
|
||||
storage,
|
||||
},
|
||||
reducer
|
||||
);
|
||||
|
||||
let store = createStore(persistedReducer);
|
||||
let persistor = persistStore(store);
|
||||
const store = createStore(persistedReducer);
|
||||
const persistor = persistStore(store);
|
||||
|
||||
export { store, persistor };
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { createMuiTheme } from "@material-ui/core/styles";
|
||||
import { red } from "@material-ui/core/colors";
|
||||
import { createMuiTheme } from '@material-ui/core/styles';
|
||||
import { red } from '@material-ui/core/colors';
|
||||
|
||||
// Create a theme instance.
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
type: "dark",
|
||||
type: 'dark',
|
||||
primary: {
|
||||
main: "#556cd6",
|
||||
main: '#556cd6',
|
||||
},
|
||||
secondary: {
|
||||
main: "#19857b",
|
||||
main: '#19857b',
|
||||
},
|
||||
error: {
|
||||
main: red.A100,
|
||||
},
|
||||
background: {
|
||||
default: "#121212",
|
||||
default: '#121212',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Head from "next/head";
|
||||
import { ThemeProvider } from "@material-ui/core/styles";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import { Provider } from "react-redux";
|
||||
import { PersistGate } from "redux-persist/integration/react";
|
||||
import theme from "../lib/theme";
|
||||
import { store, persistor } from "../lib/store";
|
||||
import UIPlaceholder from "../components/UIPlaceholder";
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Head from 'next/head';
|
||||
import { ThemeProvider } from '@material-ui/core/styles';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import { Provider } from 'react-redux';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
import theme from '../lib/theme';
|
||||
import { store, persistor } from '../lib/store';
|
||||
import UIPlaceholder from '../components/UIPlaceholder';
|
||||
|
||||
export default function MyApp(props) {
|
||||
const { Component, pageProps } = props;
|
||||
useEffect(() => {
|
||||
const jssStyles = document.querySelector("#jss-server-side");
|
||||
const jssStyles = document.querySelector('#jss-server-side');
|
||||
if (jssStyles) {
|
||||
jssStyles.parentElement.removeChild(jssStyles);
|
||||
}
|
||||
@@ -25,8 +25,8 @@ export default function MyApp(props) {
|
||||
<Head>
|
||||
<title>Zipline</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width"
|
||||
name='viewport'
|
||||
content='minimum-scale=1, initial-scale=1, width=device-width'
|
||||
/>
|
||||
</Head>
|
||||
<ThemeProvider theme={theme}>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import React from "react";
|
||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
import { ServerStyleSheets } from "@material-ui/core/styles";
|
||||
import theme from "../lib/theme";
|
||||
import React from 'react';
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||
import { ServerStyleSheets } from '@material-ui/core/styles';
|
||||
import theme from '../lib/theme';
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Html lang='en'>
|
||||
<Head>
|
||||
{/* PWA primary color */}
|
||||
<meta name="theme-color" content={theme.palette.primary.main} />
|
||||
<meta name='theme-color' content={theme.palette.primary.main} />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||
rel='stylesheet'
|
||||
href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
@@ -26,7 +27,7 @@ export default class MyDocument extends Document {
|
||||
|
||||
// `getInitialProps` belongs to `_document` (instead of `_app`),
|
||||
// it's compatible with server-side generation (SSG).
|
||||
MyDocument.getInitialProps = async (ctx) => {
|
||||
MyDocument.getInitialProps = async ctx => {
|
||||
// Resolution order
|
||||
//
|
||||
// On the server:
|
||||
@@ -55,7 +56,7 @@ MyDocument.getInitialProps = async (ctx) => {
|
||||
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
|
||||
enhanceApp: App => props => sheets.collect(<App {...props} />),
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import UI from "../components/UI";
|
||||
import UIPlaceholder from "../components/UIPlaceholder";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Divider from "@material-ui/core/Divider";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import Snackbar from "@material-ui/core/Snackbar";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { LOGOUT, UPDATE_USER } from "../lib/reducer";
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
import { store } from "../lib/store";
|
||||
import { useDispatch } from "react-redux";
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import UI from '../components/UI';
|
||||
import UIPlaceholder from '../components/UIPlaceholder';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { LOGOUT, UPDATE_USER } from '../lib/reducer';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { store } from '../lib/store';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
margin: {
|
||||
margin: "5px",
|
||||
margin: '5px',
|
||||
},
|
||||
padding: {
|
||||
padding: "10px",
|
||||
padding: '10px',
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export default function IndexPage() {
|
||||
const classes = useStyles();
|
||||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
const state = store.getState();
|
||||
const [alertMessage, setAlertMessage] = useState("Copied token!");
|
||||
const [alertMessage, setAlertMessage] = useState('Copied token!');
|
||||
const [tokenOpen, setTokenOpen] = useState(false);
|
||||
const [resetToken, setResetToken] = useState(false);
|
||||
const [alertOpen, setAlertOpen] = useState(false);
|
||||
|
||||
const handleCopyTokenThenClose = async () => {
|
||||
const data = await (await fetch("/api/user/current")).json();
|
||||
const data = await (await fetch('/api/user/current')).json();
|
||||
if (!data.error) {
|
||||
copy(data.token);
|
||||
setAlertMessage("Copied token!");
|
||||
setAlertMessage('Copied token!');
|
||||
setTokenOpen(false);
|
||||
setAlertOpen(true);
|
||||
}
|
||||
@@ -52,10 +53,10 @@ export default function IndexPage() {
|
||||
|
||||
const handleResetTokenThenClose = async () => {
|
||||
const data = await (
|
||||
await fetch("/api/user/reset-token", { method: "POST" })
|
||||
await fetch('/api/user/reset-token', { method: 'POST' })
|
||||
).json();
|
||||
if (!data.error && data.updated) {
|
||||
setAlertMessage("Reset token!");
|
||||
setAlertMessage('Reset token!');
|
||||
setResetToken(false);
|
||||
setAlertOpen(true);
|
||||
}
|
||||
@@ -63,46 +64,46 @@ export default function IndexPage() {
|
||||
|
||||
const handleLogout = async () => {
|
||||
const data = await (
|
||||
await fetch("/api/user/logout", { method: "POST" })
|
||||
await fetch('/api/user/logout', { method: 'POST' })
|
||||
).json();
|
||||
if (!data.error && data.clearStore) {
|
||||
dispatch({ type: LOGOUT });
|
||||
dispatch({ type: UPDATE_USER, payload: null });
|
||||
setAlertMessage("Logged out!");
|
||||
setAlertMessage('Logged out!');
|
||||
setAlertOpen(true);
|
||||
router.push("/login");
|
||||
router.push('/login');
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined" && !state.loggedIn) router.push("/login");
|
||||
if (typeof window !== 'undefined' && !state.loggedIn) router.push('/login');
|
||||
else {
|
||||
return (
|
||||
<UI>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setAlertOpen(false)}
|
||||
>
|
||||
<Alert severity="success" variant="filled">
|
||||
<Alert severity='success' variant='filled'>
|
||||
{alertMessage}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Paper elevation={3} className={classes.padding}>
|
||||
<Typography variant="h5">
|
||||
<Typography variant='h5'>
|
||||
Welcome back, {state.user.username}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
<Typography color='textSecondary'>
|
||||
You have <b>2</b> images
|
||||
</Typography>
|
||||
<div className={classes.margin}>
|
||||
<Typography variant="h5">Token</Typography>
|
||||
<Typography variant='h5'>Token</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
variant='contained'
|
||||
color='primary'
|
||||
className={classes.margin}
|
||||
onClick={() => setTokenOpen(true)}
|
||||
>
|
||||
@@ -111,23 +112,23 @@ export default function IndexPage() {
|
||||
<Dialog
|
||||
open={tokenOpen}
|
||||
onClose={() => setTokenOpen(true)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogTitle id='alert-dialog-title'>Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<DialogContentText id='alert-dialog-description'>
|
||||
This token is used to upload images to Zipline, and should not
|
||||
be shared!
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTokenOpen(true)} color="primary">
|
||||
<Button onClick={() => setTokenOpen(true)} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCopyTokenThenClose}
|
||||
color="primary"
|
||||
color='primary'
|
||||
autoFocus
|
||||
>
|
||||
Yes, copy!
|
||||
@@ -135,34 +136,34 @@ export default function IndexPage() {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Button
|
||||
variant="contained"
|
||||
variant='contained'
|
||||
className={classes.margin}
|
||||
onClick={() => setResetToken(true)}
|
||||
style={{ backgroundColor: "#d6381c", color: "white" }}
|
||||
style={{ backgroundColor: '#d6381c', color: 'white' }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Dialog
|
||||
open={resetToken}
|
||||
onClose={() => setResetToken(true)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogTitle id='alert-dialog-title'>Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
<DialogContentText id='alert-dialog-description'>
|
||||
This token is used to upload images to Zipline, resetting your
|
||||
token will cause any uploading actions to not work until you
|
||||
update them your self.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setResetToken(true)} color="primary">
|
||||
<Button onClick={() => setResetToken(true)} color='primary'>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleResetTokenThenClose}
|
||||
color="primary"
|
||||
color='primary'
|
||||
autoFocus
|
||||
>
|
||||
Yes, reset!
|
||||
@@ -172,11 +173,11 @@ export default function IndexPage() {
|
||||
</div>
|
||||
<Divider />
|
||||
<div className={classes.margin}>
|
||||
<Typography variant="h5">User</Typography>
|
||||
<TextField label="Username" className={classes.margin} fullWidth />
|
||||
<Typography variant='h5'>User</Typography>
|
||||
<TextField label='Username' className={classes.margin} fullWidth />
|
||||
<TextField
|
||||
label="Password"
|
||||
type="password"
|
||||
label='Password'
|
||||
type='password'
|
||||
className={classes.margin}
|
||||
fullWidth
|
||||
/>
|
||||
@@ -185,14 +186,14 @@ export default function IndexPage() {
|
||||
<div className={classes.margin}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="contained" color="primary" fullWidth>
|
||||
<Button variant='contained' color='primary' fullWidth>
|
||||
Update
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button
|
||||
variant="contained"
|
||||
style={{ backgroundColor: "#d6381c", color: "white" }}
|
||||
variant='contained'
|
||||
style={{ backgroundColor: '#d6381c', color: 'white' }}
|
||||
fullWidth
|
||||
onClick={handleLogout}
|
||||
>
|
||||
|
||||
@@ -1,109 +1,106 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import CardActions from "@material-ui/core/CardActions";
|
||||
import Snackbar from "@material-ui/core/Snackbar";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
import { useDispatch } from "react-redux";
|
||||
import Router from "next/router";
|
||||
import { store, persistor } from "../lib/store";
|
||||
import { UPDATE_USER, LOGIN } from "../lib/reducer";
|
||||
import UIPlaceholder from "../components/UIPlaceholder";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import React, { useState } from 'react';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Router from 'next/router';
|
||||
import { store } from '../lib/store';
|
||||
import { UPDATE_USER, LOGIN } from '../lib/reducer';
|
||||
import UIPlaceholder from '../components/UIPlaceholder';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
const useStyles = makeStyles({
|
||||
field: {
|
||||
width: "100%",
|
||||
width: '100%',
|
||||
},
|
||||
padding: {
|
||||
padding: "10px",
|
||||
padding: '10px',
|
||||
},
|
||||
});
|
||||
|
||||
export default function Index() {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export default function LoginPage() {
|
||||
const classes = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const state = store.getState();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = (event, reason) => {
|
||||
if (reason === "clickaway") return;
|
||||
if (reason === 'clickaway') return;
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleLogin = async () => {
|
||||
const d = await (
|
||||
await fetch("/api/user/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
await fetch('/api/user/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
})
|
||||
).json();
|
||||
if (!d.error) {
|
||||
dispatch({ type: UPDATE_USER, payload: d });
|
||||
dispatch({ type: LOGIN });
|
||||
Router.push("/");
|
||||
Router.push('/');
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (state.loggedIn) Router.push("/");
|
||||
if (state.loggedIn) Router.push('/');
|
||||
else
|
||||
return (
|
||||
<div>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert severity="error" variant="filled">
|
||||
<Alert severity='error' variant='filled'>
|
||||
Could not login!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
style={{ minHeight: "100vh" }}
|
||||
direction='column'
|
||||
alignItems='center'
|
||||
justify='center'
|
||||
style={{ minHeight: '100vh' }}
|
||||
>
|
||||
<Grid item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary" variant="h3" gutterBottom>
|
||||
<Typography color='textSecondary' variant='h3' gutterBottom>
|
||||
Login
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Username"
|
||||
label='Username'
|
||||
className={classes.field}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Password"
|
||||
type="password"
|
||||
label='Password'
|
||||
type='password'
|
||||
className={classes.field}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
color='primary'
|
||||
variant='contained'
|
||||
className={classes.field}
|
||||
onClick={handleLogin}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import UI from "../components/UI";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import React from 'react';
|
||||
import UI from '../components/UI';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
export default function MyApp(props) {
|
||||
export default function MyApp() {
|
||||
return (
|
||||
<div>
|
||||
<UI>
|
||||
|
||||
Reference in New Issue
Block a user