typeorm & formatting

This commit is contained in:
dicedtomatoreal 2020-10-06 20:01:55 -07:00
parent 21bc4eca48
commit e028afeb3f
28 changed files with 577 additions and 396 deletions

4
.prettierignore Normal file
View file

@ -0,0 +1,4 @@
.next
dist
node_modules
Zipline.toml

1
.prettierrc.json Normal file
View file

@ -0,0 +1 @@
{}

2
next-env.d.ts vendored
View file

@ -1,2 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/types/global" />

6
package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -11,4 +11,4 @@ export interface Image {
user: any;
views: number;
_id?: any;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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