eslinting

This commit is contained in:
dicedtomatoreal
2020-10-06 21:30:31 -07:00
parent e028afeb3f
commit bc54e00a14
26 changed files with 1564 additions and 420 deletions

View File

@@ -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>
);

View File

@@ -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} />

View File

@@ -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) {}
}

View File

@@ -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;
// }
}

View File

@@ -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;

View File

@@ -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()))}`
)}`
);
});

View File

@@ -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);

View File

@@ -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)}`;
}
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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}`;

View File

@@ -1,2 +1,2 @@
export * from "./Console";
export * from "./Formatter";
export * from './Console';
export * from './Formatter';

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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',
},
},
});

View File

@@ -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}>

View File

@@ -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);

View File

@@ -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}
>

View File

@@ -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}
>

View File

@@ -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>