mirror of
https://github.com/diced/zipline.git
synced 2025-05-10 18:05:54 +02:00
typeorm & formatting
This commit is contained in:
parent
21bc4eca48
commit
e028afeb3f
28 changed files with 577 additions and 396 deletions
4
.prettierignore
Normal file
4
.prettierignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
.next
|
||||
dist
|
||||
node_modules
|
||||
Zipline.toml
|
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
|
@ -1,2 +1,2 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/types/global" />
|
||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -5761,6 +5761,12 @@
|
|||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz",
|
||||
"integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"@types/react-redux": "^7.1.9",
|
||||
"mongodb": "^3.6.2",
|
||||
"pg": "^8.4.0",
|
||||
"prettier": "2.1.2",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
|
|
|
@ -1,49 +1,48 @@
|
|||
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 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";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
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')]: {
|
||||
[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
|
||||
|
@ -83,7 +82,7 @@ export default function UI(props) {
|
|||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
Zipline
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</Toolbar>
|
||||
|
@ -91,36 +90,44 @@ export default function UI(props) {
|
|||
<List>
|
||||
<Link href="/">
|
||||
<ListItem button key="Home">
|
||||
<ListItemIcon><HomeIcon /></ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<HomeIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Home" />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link href="/statistics">
|
||||
<ListItem button key="Statistics">
|
||||
<ListItemIcon><DataUsageIcon /></ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<DataUsageIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Statistics" />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link href="/images">
|
||||
<ListItem button key="Images">
|
||||
<ListItemIcon><PhotoIcon /></ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<PhotoIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Images" />
|
||||
</ListItem>
|
||||
</Link>
|
||||
<Link href="/urls">
|
||||
<ListItem button key="URLs">
|
||||
<ListItemIcon><LinkIcon /></ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<LinkIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="URLs" />
|
||||
</ListItem>
|
||||
</Link>
|
||||
</List>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
|
||||
const container = window !== undefined ? () => window().document.body : undefined;
|
||||
const container =
|
||||
window !== undefined ? () => window().document.body : undefined;
|
||||
|
||||
return (
|
||||
|
||||
<div className={classes.root}>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
|
@ -143,7 +150,7 @@ export default function UI(props) {
|
|||
<Drawer
|
||||
container={container}
|
||||
variant="temporary"
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
anchor={theme.direction === "rtl" ? "right" : "left"}
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
classes={{
|
||||
|
@ -175,4 +182,4 @@ export default function UI(props) {
|
|||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +1,42 @@
|
|||
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 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";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
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')]: {
|
||||
[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
|
||||
|
@ -50,8 +49,8 @@ const useStyles = makeStyles((theme) => ({
|
|||
padding: theme.spacing(3),
|
||||
},
|
||||
fullWidth: {
|
||||
width: '100%'
|
||||
}
|
||||
width: "100%",
|
||||
},
|
||||
}));
|
||||
|
||||
export default function UIPlaceholder(props) {
|
||||
|
@ -80,7 +79,7 @@ export default function UIPlaceholder(props) {
|
|||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
Zipline
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</Toolbar>
|
||||
|
@ -99,13 +98,13 @@ export default function UIPlaceholder(props) {
|
|||
<Skeleton animation="wave" className={classes.fullWidth} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
|
||||
const container = window !== undefined ? () => window().document.body : undefined;
|
||||
const container =
|
||||
window !== undefined ? () => window().document.body : undefined;
|
||||
|
||||
return (
|
||||
|
||||
<div className={classes.root}>
|
||||
<AppBar position="fixed" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
|
@ -128,7 +127,7 @@ export default function UIPlaceholder(props) {
|
|||
<Drawer
|
||||
container={container}
|
||||
variant="temporary"
|
||||
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||
anchor={theme.direction === "rtl" ? "right" : "left"}
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
classes={{
|
||||
|
@ -156,7 +155,14 @@ export default function UIPlaceholder(props) {
|
|||
|
||||
<main className={classes.content}>
|
||||
<div className={classes.toolbar} />
|
||||
<Grid container spacing={0} direction="column" alignItems="center" justify="center" style={{ minHeight: '80vh' }}>
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
style={{ minHeight: "80vh" }}
|
||||
>
|
||||
<Grid item xs={3}>
|
||||
<CircularProgress size={100} />
|
||||
</Grid>
|
||||
|
@ -164,4 +170,4 @@ export default function UIPlaceholder(props) {
|
|||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import { FastifyReply, FastifyRequest, FastifyInstance } from 'fastify';
|
||||
import { Controller, GET, POST, FastifyInstanceToken, Inject, Hook } from 'fastify-decorators';
|
||||
import { User } from '../entities/User';
|
||||
import { FastifyReply, FastifyRequest, FastifyInstance } from "fastify";
|
||||
import {
|
||||
Controller,
|
||||
GET,
|
||||
POST,
|
||||
FastifyInstanceToken,
|
||||
Inject,
|
||||
Hook,
|
||||
} from "fastify-decorators";
|
||||
import { User } from "../entities/User";
|
||||
|
||||
@Controller('/api')
|
||||
@Controller("/api")
|
||||
export class RootController {
|
||||
@Inject(FastifyInstanceToken)
|
||||
private instance!: FastifyInstance;
|
||||
|
||||
@POST('/upload')
|
||||
async loginStatus(req: FastifyRequest, reply: FastifyReply) {
|
||||
|
||||
}
|
||||
}
|
||||
@POST("/upload")
|
||||
async loginStatus(req: FastifyRequest, reply: FastifyReply) {}
|
||||
}
|
||||
|
|
|
@ -1,105 +1,150 @@
|
|||
// import { FastifyReply, FastifyRequest, FastifyInstance } from 'fastify';
|
||||
// import { Controller, GET, POST, FastifyInstanceToken, Inject, Hook } from 'fastify-decorators';
|
||||
// import { UserNotFoundError, MissingBodyData, LoginError, UserExistsError, NotAdministratorError } from '../lib/api/APIErrors';
|
||||
// import { User } from '../lib/Data';
|
||||
// import { checkPassword, createToken, encryptPassword } from '../lib/Encryption';
|
||||
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";
|
||||
import {
|
||||
UserNotFoundError,
|
||||
MissingBodyData,
|
||||
LoginError,
|
||||
UserExistsError,
|
||||
NotAdministratorError,
|
||||
} from "../lib/api/APIErrors";
|
||||
import {
|
||||
checkPassword,
|
||||
createBaseCookie,
|
||||
createToken,
|
||||
encryptPassword,
|
||||
readBaseCookie,
|
||||
} from "../lib/Encryption";
|
||||
|
||||
// @Controller('/api/user')
|
||||
// export class UserController {
|
||||
// @Inject(FastifyInstanceToken)
|
||||
// private instance!: FastifyInstance;
|
||||
@Controller("/api/user")
|
||||
export class UserController {
|
||||
@Inject(FastifyInstanceToken)
|
||||
private instance!: FastifyInstance;
|
||||
|
||||
// @GET('/login-status')
|
||||
// async loginStatus(req: FastifyRequest, reply: FastifyReply) {
|
||||
// return reply.send({ user: !!req.cookies.zipline });
|
||||
// }
|
||||
private users: Repository<User> = this.instance.orm.getRepository(User);
|
||||
|
||||
// @GET('/current')
|
||||
// async currentUser(req: FastifyRequest, reply: FastifyReply) {
|
||||
// if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
// const user = await this.instance.mongo.db.collection('zipline_users').findOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) });
|
||||
// if (!user) throw new UserExistsError(`User doesn't exist`);
|
||||
// delete user.password;
|
||||
// return reply.send(user);
|
||||
// }
|
||||
@GET("/login-status")
|
||||
async loginStatus(req: FastifyRequest, reply: FastifyReply) {
|
||||
return reply.send({
|
||||
user: !!req.cookies.zipline,
|
||||
});
|
||||
}
|
||||
|
||||
// @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.`);
|
||||
@GET("/current")
|
||||
async currentUser(req: FastifyRequest, reply: FastifyReply) {
|
||||
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`);
|
||||
delete user.password;
|
||||
return reply.send(user);
|
||||
}
|
||||
|
||||
// const user: User = await this.instance.mongo.db.collection('zipline_users').findOne({
|
||||
// username: req.body.username
|
||||
// });
|
||||
@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 (!user) throw new UserNotFoundError(`User "${req.body.username}" was not found.`);
|
||||
// if (!checkPassword(req.body.password, user.password)) throw new LoginError(`Wrong credentials!`);
|
||||
// delete user.password;
|
||||
// return reply
|
||||
// .setCookie("zipline", user._id, { path: '/' })
|
||||
// .send(user);
|
||||
// }
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
id: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
});
|
||||
|
||||
// @POST('/logout')
|
||||
// async logout(req: FastifyRequest, reply: FastifyReply) {
|
||||
// if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
// try {
|
||||
// reply.clearCookie('zipline', { path: '/' }).send({ clearStore: true })
|
||||
// } catch (e) {
|
||||
// reply.send({ clearStore: false });
|
||||
// }
|
||||
// }
|
||||
if (!user)
|
||||
throw new UserNotFoundError(`User "${req.body.username}" was not found.`);
|
||||
if (!checkPassword(req.body.password, user.password))
|
||||
throw new LoginError(`Wrong credentials!`);
|
||||
delete user.password;
|
||||
|
||||
// @POST('/reset-token')
|
||||
// async resetToken(req: FastifyRequest, reply: FastifyReply) {
|
||||
// if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
return reply
|
||||
.setCookie("zipline", createBaseCookie(user.id), { path: "/" })
|
||||
.send(user);
|
||||
}
|
||||
|
||||
// const users = this.instance.mongo.db.collection('zipline_users');
|
||||
// const user: User = await users.findOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) });
|
||||
// if (!user) throw new UserNotFoundError(`User was not found.`);
|
||||
@POST("/logout")
|
||||
async logout(req: FastifyRequest, reply: FastifyReply) {
|
||||
if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
try {
|
||||
reply.clearCookie("zipline", { path: "/" }).send({ clearStore: true });
|
||||
} catch (e) {
|
||||
reply.send({ clearStore: false });
|
||||
}
|
||||
}
|
||||
|
||||
// users.updateOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) }, { $set: { token: createToken() } });
|
||||
// return reply.send({ updated: true });
|
||||
// }
|
||||
@POST("/reset-token")
|
||||
async resetToken(req: FastifyRequest, reply: FastifyReply) {
|
||||
if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
|
||||
// @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.`);
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
id: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
});
|
||||
|
||||
// const users = this.instance.mongo.db.collection('zipline_users');
|
||||
if (!user) throw new UserNotFoundError(`User was not found.`);
|
||||
|
||||
// const existingUser = await users.findOne({ username: req.body.username });
|
||||
// if (existingUser) throw new UserExistsError('User exists already');
|
||||
user.token = createToken();
|
||||
this.users.save(user);
|
||||
|
||||
// const newUser: User = {
|
||||
// username: req.body.username,
|
||||
// password: encryptPassword(req.body.password),
|
||||
// token: createToken(),
|
||||
// administrator: req.body.administrator
|
||||
// };
|
||||
return reply.send({ updated: true });
|
||||
}
|
||||
|
||||
// try {
|
||||
// users.insertOne(newUser);
|
||||
// } catch (e) {
|
||||
// throw new Error(`Could not create user: ${e.message}`);
|
||||
// }
|
||||
@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.`);
|
||||
|
||||
// return reply.send(newUser);
|
||||
// }
|
||||
const existing = await this.users.findOne({
|
||||
where: { username: req.body.username },
|
||||
});
|
||||
if (existing) throw new UserExistsError("User exists already");
|
||||
|
||||
// @Hook('preValidation')
|
||||
// public async preValidation(req: FastifyRequest, reply: FastifyReply) {
|
||||
// const adminRoutes = ['/api/user/create'];
|
||||
try {
|
||||
const user = await this.users.save(
|
||||
new User(
|
||||
req.body.username,
|
||||
req.body.password,
|
||||
createToken(),
|
||||
req.body.administrator || false
|
||||
)
|
||||
);
|
||||
delete user.password;
|
||||
return reply.send(user);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not create user: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
|
|||
|
||||
@Entity()
|
||||
export class User {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
|
@ -18,10 +17,15 @@ export class User {
|
|||
@Column("text")
|
||||
public token: string;
|
||||
|
||||
public constructor(username: string, password: string, token: string, administrator: boolean = false) {
|
||||
public constructor(
|
||||
username: string,
|
||||
password: string,
|
||||
token: string,
|
||||
administrator: boolean = false
|
||||
) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.administrator = administrator;
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
60
src/index.ts
60
src/index.ts
|
@ -1,55 +1,55 @@
|
|||
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 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';
|
||||
|
||||
|
||||
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`);
|
||||
app.prepare();
|
||||
|
||||
if (dev) server.get('/_next/*', (req, reply) => {
|
||||
return handle(req.raw, reply.raw).then(() => reply.sent = true);
|
||||
});
|
||||
if (dev)
|
||||
server.get("/_next/*", (req, reply) => {
|
||||
return handle(req.raw, reply.raw).then(() => (reply.sent = true));
|
||||
});
|
||||
|
||||
server.all('/*', (req, reply) => {
|
||||
return handle(req.raw, reply.raw).then(() => reply.sent = true);
|
||||
server.all("/*", (req, reply) => {
|
||||
return handle(req.raw, reply.raw).then(() => (reply.sent = true));
|
||||
});
|
||||
|
||||
server.setNotFoundHandler((req, reply) => {
|
||||
return app.render404(req.raw, reply.raw).then(() => reply.sent = true);
|
||||
})
|
||||
return app.render404(req.raw, reply.raw).then(() => (reply.sent = true));
|
||||
});
|
||||
|
||||
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
|
||||
logging: false,
|
||||
});
|
||||
|
||||
server.register(bootstrap, {
|
||||
controllers: [
|
||||
// UserController,
|
||||
RootController
|
||||
RootController,
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -60,11 +60,15 @@ server.register(bootstrap, {
|
|||
// });
|
||||
|
||||
server.register(fastifyCookies, {
|
||||
secret: config.core.secret
|
||||
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(`server listening on ${bold(`${green(info.address)}${reset(":")}${bold(green(info.port.toString()))}`)}`)
|
||||
})
|
||||
Console.logger("Server").info(
|
||||
`server listening on ${bold(
|
||||
`${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,11 +21,11 @@ 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);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,12 @@ import { bold, magenta, reset } from "@dicedtomato/colors";
|
|||
import { ConsoleLevel, 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,
|
||||
level: ConsoleLevel,
|
||||
time: Date
|
||||
): string {
|
||||
return `${bold(magenta(origin))} ${bold(">")} ${reset(message)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,4 @@ export interface Image {
|
|||
user: any;
|
||||
views: number;
|
||||
_id?: any;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
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') {
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) result += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||
export function createRandomId(
|
||||
length: number,
|
||||
charset: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
) {
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++)
|
||||
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createToken() {
|
||||
return aes.encrypt(`${createRandomId(10)}.${Date.now()}`, config.core.secret).toString();
|
||||
return aes
|
||||
.encrypt(`${createRandomId(10)}.${Date.now()}`, config.core.secret)
|
||||
.toString();
|
||||
}
|
||||
|
||||
export function encryptPassword(pass: string) {
|
||||
|
@ -21,4 +27,12 @@ export function encryptPassword(pass: string) {
|
|||
|
||||
export function checkPassword(will: string, equal: string) {
|
||||
return compareSync(will, equal);
|
||||
}
|
||||
}
|
||||
|
||||
export function createBaseCookie(id: number) {
|
||||
return Buffer.from(id.toString()).toString("base64");
|
||||
}
|
||||
|
||||
export function readBaseCookie(data: string) {
|
||||
return Buffer.from(data, "base64").toString();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export class MissingBodyData extends Error { };
|
||||
export class LoginError extends Error { };
|
||||
export class NotAdministratorError extends Error { };
|
||||
export class UserExistsError extends Error { };
|
||||
export class UserNotFoundError extends Error { };
|
||||
export class UserCredentialsError extends Error { };
|
||||
export class MissingBodyData extends Error {}
|
||||
export class LoginError extends Error {}
|
||||
export class NotAdministratorError extends Error {}
|
||||
export class UserExistsError extends Error {}
|
||||
export class UserNotFoundError extends Error {}
|
||||
export class UserCredentialsError extends Error {}
|
||||
|
|
|
@ -15,10 +15,9 @@ export enum ConsoleLevel {
|
|||
ERROR,
|
||||
INFO,
|
||||
TRACE,
|
||||
WARN
|
||||
WARN,
|
||||
}
|
||||
|
||||
|
||||
export class Console {
|
||||
public name: string;
|
||||
|
||||
|
@ -59,4 +58,4 @@ export class Console {
|
|||
const name = o instanceof Function ? o.name : o;
|
||||
return new Console(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
import { ConsoleLevel } from "./Console";
|
||||
import { brightGreen, blue } from '@dicedtomato/colors';
|
||||
|
||||
import { brightGreen, blue } from "@dicedtomato/colors";
|
||||
|
||||
export interface Formatter {
|
||||
format(message: string, origin: string, level: ConsoleLevel, time: Date): string;
|
||||
format(
|
||||
message: string,
|
||||
origin: string,
|
||||
level: ConsoleLevel,
|
||||
time: Date
|
||||
): string;
|
||||
}
|
||||
|
||||
export class DefaultFormatter implements Formatter {
|
||||
public format(message: string, origin: string, level: ConsoleLevel, time: Date) {
|
||||
return `[${time.toLocaleString()}] ${brightGreen(origin)} - ${blue(ConsoleLevel[level])}: ${message}`
|
||||
public format(
|
||||
message: string,
|
||||
origin: string,
|
||||
level: ConsoleLevel,
|
||||
time: Date
|
||||
) {
|
||||
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";
|
||||
|
|
|
@ -6,13 +6,12 @@ export const LOGOUT = "LOGOUT";
|
|||
export const UPDATE_USER = "UPDATE_USER";
|
||||
export interface State {
|
||||
loggedIn: boolean;
|
||||
user: User
|
||||
user: User;
|
||||
}
|
||||
|
||||
|
||||
const initialState: State = {
|
||||
loggedIn: false,
|
||||
user: null
|
||||
user: null,
|
||||
};
|
||||
|
||||
export function reducer(state: State = initialState, action) {
|
||||
|
@ -26,4 +25,4 @@ export function reducer(state: State = initialState, action) {
|
|||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +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',
|
||||
storage,
|
||||
}, reducer);
|
||||
const persistedReducer = persistReducer(
|
||||
{
|
||||
key: "root",
|
||||
storage,
|
||||
},
|
||||
reducer
|
||||
);
|
||||
|
||||
let store = createStore(persistedReducer)
|
||||
let persistor = persistStore(store)
|
||||
let store = createStore(persistedReducer);
|
||||
let persistor = persistStore(store);
|
||||
|
||||
export { store, persistor };
|
||||
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);
|
||||
}
|
||||
|
@ -24,7 +24,10 @@ export default function MyApp(props) {
|
|||
<PersistGate loading={<UIPlaceholder />} persistor={persistor}>
|
||||
<Head>
|
||||
<title>Zipline</title>
|
||||
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width"
|
||||
/>
|
||||
</Head>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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 {
|
||||
render() {
|
||||
|
@ -63,6 +63,9 @@ MyDocument.getInitialProps = async (ctx) => {
|
|||
return {
|
||||
...initialProps,
|
||||
// Styles fragment is rendered after the app and page rendering finish.
|
||||
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
|
||||
styles: [
|
||||
...React.Children.toArray(initialProps.styles),
|
||||
sheets.getStyleElement(),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
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, 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";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
margin: {
|
||||
margin: '5px'
|
||||
margin: "5px",
|
||||
},
|
||||
padding: {
|
||||
padding: '10px'
|
||||
}
|
||||
padding: "10px",
|
||||
},
|
||||
});
|
||||
|
||||
export default function IndexPage() {
|
||||
|
@ -36,49 +35,53 @@ export default function IndexPage() {
|
|||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetTokenThenClose = async () => {
|
||||
const data = await (await fetch('/api/user/reset-token', { method: 'POST' })).json();
|
||||
const data = await (
|
||||
await fetch("/api/user/reset-token", { method: "POST" })
|
||||
).json();
|
||||
if (!data.error && data.updated) {
|
||||
setAlertMessage('Reset token!');
|
||||
setAlertMessage("Reset token!");
|
||||
setResetToken(false);
|
||||
setAlertOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
const data = await (await fetch('/api/user/logout', { method: 'POST' })).json();
|
||||
const data = await (
|
||||
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}
|
||||
|
@ -89,11 +92,20 @@ export default function IndexPage() {
|
|||
</Alert>
|
||||
</Snackbar>
|
||||
<Paper elevation={3} className={classes.padding}>
|
||||
<Typography variant="h5">Welcome back, {state.user.username}</Typography>
|
||||
<Typography color="textSecondary">You have <b>2</b> images</Typography>
|
||||
<Typography variant="h5">
|
||||
Welcome back, {state.user.username}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
You have <b>2</b> images
|
||||
</Typography>
|
||||
<div className={classes.margin}>
|
||||
<Typography variant="h5">Token</Typography>
|
||||
<Button variant="contained" color="primary" className={classes.margin} onClick={() => setTokenOpen(true)}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.margin}
|
||||
onClick={() => setTokenOpen(true)}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
<Dialog
|
||||
|
@ -105,19 +117,29 @@ export default function IndexPage() {
|
|||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
This token is used to upload images to Zipline, and should not be shared!
|
||||
This token is used to upload images to Zipline, and should not
|
||||
be shared!
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTokenOpen(true)} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleCopyTokenThenClose} color="primary" autoFocus>
|
||||
<Button
|
||||
onClick={handleCopyTokenThenClose}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Yes, copy!
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Button variant="contained" className={classes.margin} onClick={() => setResetToken(true)} style={{ backgroundColor: "#d6381c", color: "white" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
className={classes.margin}
|
||||
onClick={() => setResetToken(true)}
|
||||
style={{ backgroundColor: "#d6381c", color: "white" }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Dialog
|
||||
|
@ -129,14 +151,20 @@ export default function IndexPage() {
|
|||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<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.
|
||||
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">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleResetTokenThenClose} color="primary" autoFocus>
|
||||
<Button
|
||||
onClick={handleResetTokenThenClose}
|
||||
color="primary"
|
||||
autoFocus
|
||||
>
|
||||
Yes, reset!
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
@ -146,23 +174,36 @@ export default function IndexPage() {
|
|||
<div className={classes.margin}>
|
||||
<Typography variant="h5">User</Typography>
|
||||
<TextField label="Username" className={classes.margin} fullWidth />
|
||||
<TextField label="Password" type="password" className={classes.margin} fullWidth />
|
||||
<TextField
|
||||
label="Password"
|
||||
type="password"
|
||||
className={classes.margin}
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className={classes.margin}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="contained" color="primary" fullWidth>Update</Button>
|
||||
<Button variant="contained" color="primary" fullWidth>
|
||||
Update
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="contained" style={{ backgroundColor: "#d6381c", color: "white" }} fullWidth onClick={handleLogout}>Logout</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
style={{ backgroundColor: "#d6381c", color: "white" }}
|
||||
fullWidth
|
||||
onClick={handleLogout}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
</Paper>
|
||||
</UI>
|
||||
);
|
||||
}
|
||||
return <UIPlaceholder />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
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 { 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";
|
||||
const useStyles = makeStyles({
|
||||
field: {
|
||||
width: '100%'
|
||||
width: "100%",
|
||||
},
|
||||
padding: {
|
||||
padding: '10px'
|
||||
padding: "10px",
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -36,54 +36,84 @@ export default function Index() {
|
|||
};
|
||||
|
||||
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' }, body: JSON.stringify({ username, password }) })).json()
|
||||
const d = await (
|
||||
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('/');
|
||||
dispatch({ type: LOGIN });
|
||||
Router.push("/");
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (state.loggedIn) Router.push('/');
|
||||
else return (
|
||||
<div>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert severity="error" variant="filled">
|
||||
Could not login!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Grid container spacing={0} direction="column" alignItems="center" justify="center" style={{ minHeight: '100vh' }}>
|
||||
<Grid item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary" variant="h3" gutterBottom>
|
||||
Login
|
||||
</Typography>
|
||||
<TextField label="Username" className={classes.field} onChange={(e) => setUsername(e.target.value)} />
|
||||
<TextField label="Password" type="password" className={classes.field} onChange={(e) => setPassword(e.target.value)} />
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button color="primary" variant="contained" className={classes.field} onClick={handleLogin}>Login</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
if (state.loggedIn) Router.push("/");
|
||||
else
|
||||
return (
|
||||
<div>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert severity="error" variant="filled">
|
||||
Could not login!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justify="center"
|
||||
style={{ minHeight: "100vh" }}
|
||||
>
|
||||
<Grid item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary" variant="h3" gutterBottom>
|
||||
Login
|
||||
</Typography>
|
||||
<TextField
|
||||
label="Username"
|
||||
className={classes.field}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
label="Password"
|
||||
type="password"
|
||||
className={classes.field}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.field}
|
||||
onClick={handleLogin}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div >
|
||||
)
|
||||
return <UIPlaceholder />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
return <UIPlaceholder />;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import UI from '../components/UI';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import UI from "../components/UI";
|
||||
import { Typography } from "@material-ui/core";
|
||||
|
||||
export default function MyApp(props) {
|
||||
return (
|
||||
|
@ -8,5 +8,5 @@ export default function MyApp(props) {
|
|||
<Typography>stater</Typography>
|
||||
</UI>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"outDir": "./dist",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
@ -20,12 +16,6 @@
|
|||
"experimentalDecorators": true,
|
||||
"noEmit": false
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".next"
|
||||
]
|
||||
}
|
||||
"include": ["next-env.d.ts", "src"],
|
||||
"exclude": ["node_modules", ".next"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue