This commit is contained in:
diced-tomato 2020-10-26 13:52:05 -07:00
parent e6efff28dd
commit d7c7c9da18
27 changed files with 244 additions and 232 deletions

View file

@ -1,25 +1,25 @@
module.exports = { module.exports = {
env: { env: {
es2021: true, es2021: true,
node: true, node: true
}, },
settings: { settings: {
react: { react: {
version: 'detect', version: 'detect'
}, }
}, },
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:react/recommended', 'plugin:react/recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended'
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true
}, },
ecmaVersion: 12, ecmaVersion: 12,
sourceType: 'module', sourceType: 'module'
}, },
plugins: ['react', '@typescript-eslint'], plugins: ['react', '@typescript-eslint'],
rules: { rules: {
@ -29,5 +29,6 @@ module.exports = {
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'], quotes: ['error', 'single'],
semi: ['error', 'always'], semi: ['error', 'always'],
}, 'comma-dangle': ['error', 'never']
}
}; };

View file

@ -2,5 +2,7 @@
"$schema": "http://json.schemastore.org/prettierrc", "$schema": "http://json.schemastore.org/prettierrc",
"jsxSingleQuote": true, "jsxSingleQuote": true,
"singleQuote": true, "singleQuote": true,
"arrowParens": "avoid" "arrowParens": "avoid",
"trailingComma": "none",
"endOfLine": "lf"
} }

View file

@ -1,16 +1,18 @@
![](https://raw.githubusercontent.com/ZiplineProject/zipline/next/public/zipline.png) ## ![](https://raw.githubusercontent.com/ZiplineProject/zipline/next/public/zipline.png)
---
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/29a3d02f0df447acadd721d93229d072)](https://app.codacy.com/gh/ZiplineProject/zipline?utm_source=github.com&utm_medium=referral&utm_content=ZiplineProject/zipline&utm_campaign=Badge_Grade) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/29a3d02f0df447acadd721d93229d072)](https://app.codacy.com/gh/ZiplineProject/zipline?utm_source=github.com&utm_medium=referral&utm_content=ZiplineProject/zipline&utm_campaign=Badge_Grade)
# ZiplineNext # ZiplineNext
Speed & reliable Speed & reliable
- Configurable - Configurable
- Fast (API) - Fast (API)
- Built with Next.js & React - Built with Next.js & React
- Support for multible database types (mongo soon) - Support for multible database types (mongo soon)
# Documentation # Documentation
You can view current documentation [here](https://zipline.diced.wtf/) You can view current documentation [here](https://zipline.diced.wtf/)
*Note: Topics like Migrations are not implemented, but will soon exist.* _Note: Topics like Migrations are not implemented, but will soon exist._

View file

@ -10,7 +10,7 @@ const base = {
description: 'My Zipline Server', description: 'My Zipline Server',
thumbnail: thumbnail:
'https://github.githubassets.com/images/modules/open_graph/github-mark.png', 'https://github.githubassets.com/images/modules/open_graph/github-mark.png',
color: '#128377', color: '#128377'
}, },
core: { secret: 'my-secret', port: 3000 }, core: { secret: 'my-secret', port: 3000 },
uploader: { uploader: {
@ -18,9 +18,9 @@ const base = {
route: '/u', route: '/u',
length: 6, length: 6,
original: false, original: false,
blacklisted: [], blacklisted: []
}, },
urls: { route: '/s', length: 4, vanity: false }, urls: { route: '/s', length: 4, vanity: false }
}; };
(async () => { (async () => {
@ -37,34 +37,34 @@ const base = {
{ name: 'mssql' }, { name: 'mssql' },
{ name: 'sqlite' }, { name: 'sqlite' },
{ name: 'sqlite3' }, { name: 'sqlite3' },
{ name: 'mongodb', extra: 'No support yet' }, { name: 'mongodb', extra: 'No support yet' }
], ]
}, },
{ {
type: 'input', type: 'input',
name: 'host', name: 'host',
message: 'Database Host', message: 'Database Host'
}, },
{ {
type: 'number', type: 'number',
name: 'port', name: 'port',
message: 'Database Port', message: 'Database Port'
}, },
{ {
type: 'input', type: 'input',
name: 'database', name: 'database',
message: 'Database Name', message: 'Database Name'
}, },
{ {
type: 'input', type: 'input',
name: 'username', name: 'username',
message: 'Database User', message: 'Database User'
}, },
{ {
type: 'password', type: 'password',
name: 'password', name: 'password',
message: 'Database Password', message: 'Database Password'
}, }
]); ]);
console.log('\nCore\n'); console.log('\nCore\n');
@ -73,13 +73,13 @@ const base = {
{ {
type: 'input', type: 'input',
name: 'secret', name: 'secret',
message: 'Secret (this must be secure)', message: 'Secret (this must be secure)'
}, },
{ {
type: 'number', type: 'number',
name: 'port', name: 'port',
message: 'Serve on Port', message: 'Serve on Port'
}, }
]); ]);
console.log('\nUploader\n'); console.log('\nUploader\n');
@ -88,13 +88,13 @@ const base = {
{ {
type: 'input', type: 'input',
name: 'directory', name: 'directory',
message: 'Uploads Directory', message: 'Uploads Directory'
}, },
{ {
type: 'confirm', type: 'confirm',
name: 'original', name: 'original',
message: 'Keep Original?', message: 'Keep Original?'
}, }
]); ]);
console.log('\nURLs\n'); console.log('\nURLs\n');
@ -103,8 +103,8 @@ const base = {
{ {
type: 'confirm', type: 'confirm',
name: 'vanity', name: 'vanity',
message: 'Allow vanity URLs', message: 'Allow vanity URLs'
}, }
]); ]);
const config = { const config = {
@ -112,7 +112,7 @@ const base = {
meta: { ...base.meta }, meta: { ...base.meta },
core: { ...base.core, ...core }, core: { ...base.core, ...core },
uploader: { ...base.uploader, ...uploader }, uploader: { ...base.uploader, ...uploader },
urls: { ...base.urls, ...urls }, urls: { ...base.urls, ...urls }
}; };
writeFileSync('Zipline.toml', stringify(config)); writeFileSync('Zipline.toml', stringify(config));
})(); })();

View file

@ -14,11 +14,11 @@ import { useDispatch } from 'react-redux';
const useStyles = makeStyles({ const useStyles = makeStyles({
field: { field: {
width: '100%', width: '100%'
}, },
padding: { padding: {
padding: '10px', padding: '10px'
}, }
}); });
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@ -40,7 +40,7 @@ export default function Login() {
await fetch('/api/user/login', { await fetch('/api/user/login', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }), body: JSON.stringify({ username, password })
}) })
).json(); ).json();
if (!d.error) { if (!d.error) {
@ -55,7 +55,7 @@ export default function Login() {
<Snackbar <Snackbar
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'center', horizontal: 'center'
}} }}
open={open} open={open}
autoHideDuration={6000} autoHideDuration={6000}

View file

@ -14,17 +14,17 @@ import { useDispatch } from 'react-redux';
const useStyles = makeStyles({ const useStyles = makeStyles({
margin: { margin: {
margin: '5px', margin: '5px'
}, },
padding: { padding: {
padding: '10px', padding: '10px'
}, },
field: { field: {
width: '100%', width: '100%'
}, },
button: { button: {
marginLeft: 'auto', marginLeft: 'auto'
}, }
}); });
export default function ManageUser() { export default function ManageUser() {
@ -40,7 +40,7 @@ export default function ManageUser() {
await fetch('/api/user', { await fetch('/api/user', {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }), body: JSON.stringify({ username, password })
}) })
).json(); ).json();
if (!d.error) { if (!d.error) {
@ -53,7 +53,7 @@ export default function ManageUser() {
<Snackbar <Snackbar
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'center', horizontal: 'center'
}} }}
open={alertOpen} open={alertOpen}
autoHideDuration={6000} autoHideDuration={6000}
@ -96,4 +96,4 @@ export default function ManageUser() {
</Card> </Card>
</React.Fragment> </React.Fragment>
); );
} }

View file

@ -41,14 +41,14 @@ const drawerWidth = 240;
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
display: 'flex', display: 'flex',
flexGrow: 1, flexGrow: 1
}, },
drawer: { drawer: {
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
width: drawerWidth, width: drawerWidth,
flexShrink: 0, flexShrink: 0
}, },
outlineColor: '#fff', outlineColor: '#fff'
}, },
appBar: { appBar: {
display: 'flex', display: 'flex',
@ -56,31 +56,31 @@ const useStyles = makeStyles(theme => ({
color: '#fff', color: '#fff',
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
width: 'calc(100%)', width: 'calc(100%)',
marginLeft: drawerWidth, marginLeft: drawerWidth
}, },
borderBottom: '1px solid #1f1f1f', borderBottom: '1px solid #1f1f1f'
}, },
menuButton: { menuButton: {
marginRight: theme.spacing(2), marginRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
display: 'none', display: 'none'
}, }
}, },
rightButton: { rightButton: {
marginLeft: 'auto', marginLeft: 'auto'
}, },
// necessary for content to be below app bar // necessary for content to be below app bar
toolbar: theme.mixins.toolbar, toolbar: theme.mixins.toolbar,
drawerPaper: { drawerPaper: {
width: drawerWidth, width: drawerWidth
}, },
content: { content: {
flexGrow: 1, flexGrow: 1,
padding: theme.spacing(1), padding: theme.spacing(1)
}, },
menuIcon: { menuIcon: {
marginRight: '10px', marginRight: '10px'
}, }
})); }));
export default function UI({ children }) { export default function UI({ children }) {
@ -175,12 +175,12 @@ export default function UI({ children }) {
anchorEl={anchorEl} anchorEl={anchorEl}
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'right', horizontal: 'right'
}} }}
keepMounted keepMounted
transformOrigin={{ transformOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'right', horizontal: 'right'
}} }}
open={open} open={open}
onClose={() => setAnchorEl(null)} onClose={() => setAnchorEl(null)}
@ -271,7 +271,7 @@ export default function UI({ children }) {
<Snackbar <Snackbar
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'center', horizontal: 'center'
}} }}
open={alertOpen} open={alertOpen}
autoHideDuration={6000} autoHideDuration={6000}
@ -349,10 +349,10 @@ export default function UI({ children }) {
open={mobileOpen} open={mobileOpen}
onClose={handleDrawerToggle} onClose={handleDrawerToggle}
classes={{ classes={{
paper: classes.drawerPaper, paper: classes.drawerPaper
}} }}
ModalProps={{ ModalProps={{
keepMounted: true, // Better open performance on mobile. keepMounted: true // Better open performance on mobile.
}} }}
> >
{drawer} {drawer}
@ -361,7 +361,7 @@ export default function UI({ children }) {
<Hidden xsDown implementation='css'> <Hidden xsDown implementation='css'>
<Drawer <Drawer
classes={{ classes={{
paper: classes.drawerPaper, paper: classes.drawerPaper
}} }}
variant='permanent' variant='permanent'
open open

View file

@ -15,41 +15,41 @@ import { makeStyles, useTheme } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
display: 'flex', display: 'flex'
}, },
drawer: { drawer: {
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
width: 240, width: 240,
flexShrink: 0, flexShrink: 0
}, }
}, },
appBar: { appBar: {
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
width: 'calc(100%)', width: 'calc(100%)',
marginLeft: 240, marginLeft: 240
}, }
}, },
menuButton: { menuButton: {
marginRight: theme.spacing(2), marginRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: { [theme.breakpoints.up('sm')]: {
display: 'none', display: 'none'
}, }
}, },
rightButton: { rightButton: {
marginLeft: 'auto', marginLeft: 'auto'
}, },
// necessary for content to be below app bar // necessary for content to be below app bar
toolbar: theme.mixins.toolbar, toolbar: theme.mixins.toolbar,
drawerPaper: { drawerPaper: {
width: 240, width: 240
}, },
content: { content: {
flexGrow: 1, flexGrow: 1,
padding: theme.spacing(3), padding: theme.spacing(3)
}, },
fullWidth: { fullWidth: {
width: '100%', width: '100%'
}, }
})); }));
export default function UIPlaceholder() { export default function UIPlaceholder() {
@ -132,10 +132,10 @@ export default function UIPlaceholder() {
open={mobileOpen} open={mobileOpen}
onClose={handleDrawerToggle} onClose={handleDrawerToggle}
classes={{ classes={{
paper: classes.drawerPaper, paper: classes.drawerPaper
}} }}
ModalProps={{ ModalProps={{
keepMounted: true, // Better open performance on mobile. keepMounted: true // Better open performance on mobile.
}} }}
> >
{drawer} {drawer}
@ -144,7 +144,7 @@ export default function UIPlaceholder() {
<Hidden xsDown implementation='css'> <Hidden xsDown implementation='css'>
<Drawer <Drawer
classes={{ classes={{
paper: classes.drawerPaper, paper: classes.drawerPaper
}} }}
variant='permanent' variant='permanent'
open open

View file

@ -4,7 +4,7 @@ import {
FastifyInstanceToken, FastifyInstanceToken,
Inject, Inject,
GET, GET,
DELETE, DELETE
} from 'fastify-decorators'; } from 'fastify-decorators';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { Image } from '../entities/Image'; import { Image } from '../entities/Image';
@ -30,8 +30,8 @@ export class ImagesController {
const images = await this.images.find({ const images = await this.images.find({
where: { where: {
user: readBaseCookie(req.cookies.zipline), user: readBaseCookie(req.cookies.zipline)
}, }
}); });
return reply.send(images); return reply.send(images);
@ -47,21 +47,21 @@ export class ImagesController {
const image = await this.images.findOne({ const image = await this.images.findOne({
where: { where: {
user: readBaseCookie(req.cookies.zipline), user: readBaseCookie(req.cookies.zipline),
id: req.params.id, id: req.params.id
}, }
}); });
if (!image) throw new Error('No image'); if (!image) throw new Error('No image');
this.images.delete({ this.images.delete({
id: req.params.id, id: req.params.id
}); });
Console.logger(Image).info(`image ${image.id} was deleted`); Console.logger(Image).info(`image ${image.id} was deleted`);
if (this.webhooks.events.includes(WebhookType.DELETE_IMAGE)) if (this.webhooks.events.includes(WebhookType.DELETE_IMAGE))
WebhookHelper.sendWebhook(this.webhooks.upload.content, { WebhookHelper.sendWebhook(this.webhooks.upload.content, {
image, image,
host: `${req.protocol}://${req.hostname}${config.uploader.route}/`, host: `${req.protocol}://${req.hostname}${config.uploader.route}/`
}); });
return reply.send(image); return reply.send(image);
@ -73,8 +73,8 @@ export class ImagesController {
const images = await this.images.find({ const images = await this.images.find({
where: { where: {
user: readBaseCookie(req.cookies.zipline), user: readBaseCookie(req.cookies.zipline)
}, }
}); });
return reply.send(images.slice(1).slice(-3).reverse()); return reply.send(images.slice(1).slice(-3).reverse());
@ -86,8 +86,8 @@ export class ImagesController {
const images = await this.images.find({ const images = await this.images.find({
where: { where: {
user: readBaseCookie(req.cookies.zipline), user: readBaseCookie(req.cookies.zipline)
}, }
}); });
function chunk(array: Image[], size: number) { function chunk(array: Image[], size: number) {

View file

@ -4,7 +4,7 @@ import {
POST, POST,
FastifyInstanceToken, FastifyInstanceToken,
Inject, Inject,
GET, GET
} from 'fastify-decorators'; } from 'fastify-decorators';
import { Multipart } from 'fastify-multipart'; import { Multipart } from 'fastify-multipart';
import { createWriteStream, existsSync, mkdirSync } from 'fs'; import { createWriteStream, existsSync, mkdirSync } from 'fs';
@ -62,7 +62,7 @@ export class RootController {
for (const user of users) { for (const user of users) {
const usersImages = await this.images.find({ const usersImages = await this.images.find({
where: { user: user.id }, where: { user: user.id }
}); });
lb.push({ lb.push({
@ -70,14 +70,14 @@ export class RootController {
images: usersImages.length, images: usersImages.length,
views: usersImages views: usersImages
.map(x => x.views) .map(x => x.views)
.reduce((a, b) => Number(a) + Number(b), 0), .reduce((a, b) => Number(a) + Number(b), 0)
}); });
} }
return reply.send({ return reply.send({
images: images.length, images: images.length,
totalViews, totalViews,
leaderboard: lb.sort((a, b) => b.images - a.images), leaderboard: lb.sort((a, b) => b.images - a.images)
}); });
} }
@ -88,8 +88,8 @@ export class RootController {
const user = await this.users.findOne({ const user = await this.users.findOne({
where: { where: {
token: req.headers.authorization, token: req.headers.authorization
}, }
}); });
if (!user) return new AuthError('Incorrect token!'); if (!user) return new AuthError('Incorrect token!');
@ -125,7 +125,7 @@ export class RootController {
if (this.webhooks.events.includes(WebhookType.UPLOAD)) if (this.webhooks.events.includes(WebhookType.UPLOAD))
WebhookHelper.sendWebhook(this.webhooks.upload.content, { WebhookHelper.sendWebhook(this.webhooks.upload.content, {
image, image,
host: `${req.protocol}://${req.hostname}${config.uploader.route}/`, host: `${req.protocol}://${req.hostname}${config.uploader.route}/`
}); });
reply.send( reply.send(

View file

@ -5,7 +5,7 @@ import {
Inject, Inject,
GET, GET,
DELETE, DELETE,
POST, POST
} from 'fastify-decorators'; } from 'fastify-decorators';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { URL } from '../entities/URL'; import { URL } from '../entities/URL';
@ -34,8 +34,8 @@ export class URLSController {
const all = await this.urls.find({ const all = await this.urls.find({
where: { where: {
user: readBaseCookie(req.cookies.zipline), user: readBaseCookie(req.cookies.zipline)
}, }
}); });
return reply.send(all); return reply.send(all);
@ -51,22 +51,22 @@ export class URLSController {
const url = await this.urls.findOne({ const url = await this.urls.findOne({
where: { where: {
user: readBaseCookie(req.cookies.zipline), user: readBaseCookie(req.cookies.zipline),
id: req.params.id, id: req.params.id
}, }
}); });
if (!url) throw new Error('No url'); if (!url) throw new Error('No url');
this.logger.verbose(`attempting to delete url ${url.id}`); this.logger.verbose(`attempting to delete url ${url.id}`);
this.urls.delete({ this.urls.delete({
id: req.params.id, id: req.params.id
}); });
this.logger.info(`url ${url.id} was deleted`); this.logger.info(`url ${url.id} was deleted`);
if (this.webhooks.events.includes(WebhookType.DELETE_URL)) if (this.webhooks.events.includes(WebhookType.DELETE_URL))
WebhookHelper.sendWebhook(this.webhooks.delete_url.content, { WebhookHelper.sendWebhook(this.webhooks.delete_url.content, {
url, url,
host: `${req.protocol}://${req.hostname}${config.urls.route}/`, host: `${req.protocol}://${req.hostname}${config.urls.route}/`
}); });
return reply.send(url); return reply.send(url);
@ -82,16 +82,16 @@ export class URLSController {
if (config.urls.vanity && req.body.vanity) { if (config.urls.vanity && req.body.vanity) {
const existingVanity = await this.urls.findOne({ const existingVanity = await this.urls.findOne({
where: { where: {
vanity: req.body.vanity, vanity: req.body.vanity
}, }
}); });
if (existingVanity) throw new Error('There is an existing vanity!'); if (existingVanity) throw new Error('There is an existing vanity!');
} }
const user = await this.users.findOne({ const user = await this.users.findOne({
where: { where: {
id: readBaseCookie(req.cookies.zipline), id: readBaseCookie(req.cookies.zipline)
}, }
}); });
if (!user) throw new LoginError('No user'); if (!user) throw new LoginError('No user');
@ -107,7 +107,7 @@ export class URLSController {
if (this.webhooks.events.includes(WebhookType.SHORTEN)) if (this.webhooks.events.includes(WebhookType.SHORTEN))
WebhookHelper.sendWebhook(this.webhooks.shorten.content, { WebhookHelper.sendWebhook(this.webhooks.shorten.content, {
url, url,
host: `${req.protocol}://${req.hostname}${config.urls.route}/`, host: `${req.protocol}://${req.hostname}${config.urls.route}/`
}); });
return reply.send(url); return reply.send(url);

View file

@ -6,7 +6,7 @@ import {
PATCH, PATCH,
FastifyInstanceToken, FastifyInstanceToken,
Inject, Inject,
DELETE, DELETE
} from 'fastify-decorators'; } from 'fastify-decorators';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { User } from '../entities/User'; import { User } from '../entities/User';
@ -14,7 +14,7 @@ import {
UserNotFoundError, UserNotFoundError,
MissingBodyData, MissingBodyData,
LoginError, LoginError,
UserExistsError, UserExistsError
} from '../lib/api/APIErrors'; } from '../lib/api/APIErrors';
import { Configuration, ConfigWebhooks } from '../lib/Config'; import { Configuration, ConfigWebhooks } from '../lib/Config';
import { Console } from '../lib/logger'; import { Console } from '../lib/logger';
@ -23,7 +23,7 @@ import {
createBaseCookie, createBaseCookie,
createToken, createToken,
encryptPassword, encryptPassword,
readBaseCookie, readBaseCookie
} from '../lib/Util'; } from '../lib/Util';
import { WebhookType, WebhookHelper } from '../lib/Webhooks'; import { WebhookType, WebhookHelper } from '../lib/Webhooks';
@ -41,7 +41,7 @@ export class UserController {
@GET('/login-status') @GET('/login-status')
async loginStatus(req: FastifyRequest, reply: FastifyReply) { async loginStatus(req: FastifyRequest, reply: FastifyReply) {
return reply.send({ return reply.send({
user: !!req.cookies.zipline, user: !!req.cookies.zipline
}); });
} }
@ -50,10 +50,11 @@ export class UserController {
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({ const user = await this.users.findOne({
where: { where: {
id: readBaseCookie(req.cookies.zipline), id: readBaseCookie(req.cookies.zipline)
}, }
}); });
if (!user) throw new UserExistsError('User doesn\'t exist'); // eslint-disable-next-line quotes
if (!user) throw new UserExistsError("User doesn't exist");
delete user.password; delete user.password;
return reply.send(user); return reply.send(user);
} }
@ -67,10 +68,11 @@ export class UserController {
const user = await this.users.findOne({ const user = await this.users.findOne({
where: { where: {
id: readBaseCookie(req.cookies.zipline), id: readBaseCookie(req.cookies.zipline)
}, }
}); });
if (!user) throw new UserExistsError('User doesn\'t exist'); // eslint-disable-next-line quotes
if (!user) throw new UserExistsError("User doesn't exist");
this.logger.verbose(`attempting to save ${user.username} (${user.id})`); this.logger.verbose(`attempting to save ${user.username} (${user.id})`);
user.username = req.body.username; user.username = req.body.username;
@ -80,7 +82,7 @@ export class UserController {
this.logger.info(`saved ${user.username} (${user.id})`); this.logger.info(`saved ${user.username} (${user.id})`);
if (this.webhooks.events.includes(WebhookType.USER_EDIT)) if (this.webhooks.events.includes(WebhookType.USER_EDIT))
WebhookHelper.sendWebhook(this.webhooks.user_edit.content, { WebhookHelper.sendWebhook(this.webhooks.user_edit.content, {
user, user
}); });
delete user.password; delete user.password;
@ -98,8 +100,8 @@ export class UserController {
const user = await this.users.findOne({ const user = await this.users.findOne({
where: { where: {
username: req.body.username, username: req.body.username
}, }
}); });
if (!user) if (!user)
@ -115,13 +117,13 @@ export class UserController {
this.logger.verbose(`set cookie for ${user.username} (${user.id})`); this.logger.verbose(`set cookie for ${user.username} (${user.id})`);
reply.setCookie('zipline', createBaseCookie(user.id), { reply.setCookie('zipline', createBaseCookie(user.id), {
path: '/', path: '/',
maxAge: 1036800000, maxAge: 1036800000
}); });
this.logger.info(`${user.username} (${user.id}) logged in`); this.logger.info(`${user.username} (${user.id}) logged in`);
if (this.webhooks.events.includes(WebhookType.LOGIN)) if (this.webhooks.events.includes(WebhookType.LOGIN))
WebhookHelper.sendWebhook(this.webhooks.login.content, { WebhookHelper.sendWebhook(this.webhooks.login.content, {
user, user
}); });
return reply.send(user); return reply.send(user);
@ -144,8 +146,8 @@ export class UserController {
const user = await this.users.findOne({ const user = await this.users.findOne({
where: { where: {
id: readBaseCookie(req.cookies.zipline), id: readBaseCookie(req.cookies.zipline)
}, }
}); });
if (!user) throw new UserNotFoundError('User was not found.'); if (!user) throw new UserNotFoundError('User was not found.');
@ -159,7 +161,7 @@ export class UserController {
this.logger.info(`reset token ${user.username} (${user.id})`); this.logger.info(`reset token ${user.username} (${user.id})`);
if (this.webhooks.events.includes(WebhookType.TOKEN_RESET)) if (this.webhooks.events.includes(WebhookType.TOKEN_RESET))
WebhookHelper.sendWebhook(this.webhooks.token_reset.content, { WebhookHelper.sendWebhook(this.webhooks.token_reset.content, {
user, user
}); });
return reply.send({ updated: true }); return reply.send({ updated: true });
@ -176,7 +178,7 @@ export class UserController {
if (!req.body.password) throw new MissingBodyData('Missing uassword.'); if (!req.body.password) throw new MissingBodyData('Missing uassword.');
const existing = await this.users.findOne({ const existing = await this.users.findOne({
where: { username: req.body.username }, where: { username: req.body.username }
}); });
if (existing) throw new UserExistsError('User exists already'); if (existing) throw new UserExistsError('User exists already');
@ -193,7 +195,7 @@ export class UserController {
this.logger.info(`created user ${user.username} (${user.id})`); this.logger.info(`created user ${user.username} (${user.id})`);
if (this.webhooks.events.includes(WebhookType.CREATE_USER)) if (this.webhooks.events.includes(WebhookType.CREATE_USER))
WebhookHelper.sendWebhook(this.webhooks.create_user.content, { WebhookHelper.sendWebhook(this.webhooks.create_user.content, {
user, user
}); });
delete user.password; delete user.password;
@ -213,7 +215,7 @@ export class UserController {
reply: FastifyReply reply: FastifyReply
) { ) {
const existing = await this.users.findOne({ const existing = await this.users.findOne({
where: { id: req.params.id }, where: { id: req.params.id }
}); });
if (!existing) throw new UserExistsError('User doesnt exist'); if (!existing) throw new UserExistsError('User doesnt exist');
@ -222,13 +224,13 @@ export class UserController {
`attempting to delete ${existing.username} (${existing.id})` `attempting to delete ${existing.username} (${existing.id})`
); );
await this.users.delete({ await this.users.delete({
id: existing.id, id: existing.id
}); });
this.logger.info(`deleted ${existing.username} (${existing.id})`); this.logger.info(`deleted ${existing.username} (${existing.id})`);
if (this.webhooks.events.includes(WebhookType.USER_DELETE)) if (this.webhooks.events.includes(WebhookType.USER_DELETE))
WebhookHelper.sendWebhook(this.webhooks.user_delete.content, { WebhookHelper.sendWebhook(this.webhooks.user_delete.content, {
user: existing, user: existing
}); });
return reply.send({ ok: true }); return reply.send({ ok: true });

View file

@ -29,7 +29,6 @@ Mode : ${bold(dev ? red('dev') : green('production'))}
Verbose : ${bold(process.env.VERBOSE ? red('yes') : green('no'))} Verbose : ${bold(process.env.VERBOSE ? red('yes') : green('no'))}
`); `);
Console.logger(Configuration).verbose('searching for config...'); Console.logger(Configuration).verbose('searching for config...');
const config = Configuration.readConfig(); const config = Configuration.readConfig();
if (!config) { if (!config) {
@ -71,14 +70,14 @@ server.get(`${config.urls.route}/:id`, async function (
const urlId = await urls.findOne({ const urlId = await urls.findOne({
where: { where: {
id: req.params.id, id: req.params.id
}, }
}); });
const urlVanity = await urls.findOne({ const urlVanity = await urls.findOne({
where: { where: {
vanity: req.params.id, vanity: req.params.id
}, }
}); });
if (config.urls.vanity && urlVanity) return reply.redirect(urlVanity.url); if (config.urls.vanity && urlVanity) return reply.redirect(urlVanity.url);
@ -95,7 +94,7 @@ server.register(fastifyTypeorm, {
...config.database, ...config.database,
entities: [dev ? './src/entities/**/*.ts' : './dist/entities/**/*.js'], entities: [dev ? './src/entities/**/*.ts' : './dist/entities/**/*.js'],
synchronize: true, synchronize: true,
logging: false, logging: false
}); });
server.register(bootstrap, { server.register(bootstrap, {
@ -103,23 +102,23 @@ server.register(bootstrap, {
UserController, UserController,
RootController, RootController,
ImagesController, ImagesController,
URLSController, URLSController
], ]
}); });
server.register(fastifyCookies, { server.register(fastifyCookies, {
secret: config.core.secret, secret: config.core.secret
}); });
server.register(fastifyStatic, { server.register(fastifyStatic, {
root: join(process.cwd(), config.uploader.directory), root: join(process.cwd(), config.uploader.directory),
prefix: config.uploader.route, prefix: config.uploader.route
}); });
server.register(fastifyStatic, { server.register(fastifyStatic, {
root: join(process.cwd(), 'public'), root: join(process.cwd(), 'public'),
prefix: '/public', prefix: '/public',
decorateReply: false, decorateReply: false
}); });
server.register(fastifyFavicon); server.register(fastifyFavicon);
@ -136,8 +135,11 @@ server.listen(config.core.port, err => {
}); });
server.addHook('preHandler', async (req, reply) => { server.addHook('preHandler', async (req, reply) => {
if (config.core.blacklisted_ips && config.core.blacklisted_ips.includes(req.ip)) { if (
config.core.blacklisted_ips &&
config.core.blacklisted_ips.includes(req.ip)
) {
await app.render404(req.raw, reply.raw); await app.render404(req.raw, reply.raw);
return (reply.sent = true); return (reply.sent = true);
} }
}); });

View file

@ -67,7 +67,8 @@ export class Configuration {
try { try {
const data = readFileSync(resolve(process.cwd(), 'Zipline.toml'), 'utf8'); const data = readFileSync(resolve(process.cwd(), 'Zipline.toml'), 'utf8');
const parsed = parse(data); const parsed = parse(data);
if (parsed.webhooks) parsed.webhooks.events = WebhookHelper.convert(parsed.webhooks.events); if (parsed.webhooks)
parsed.webhooks.events = WebhookHelper.convert(parsed.webhooks.events);
return parsed; return parsed;
} catch (e) { } catch (e) {
return null; return null;

View file

@ -25,7 +25,7 @@ export enum WebhookParseTokens {
USER_ADMIN = '{user_admin}', USER_ADMIN = '{user_admin}',
URL_ID = '{url_id}', URL_ID = '{url_id}',
URL_URL = '{url}', URL_URL = '{url}',
URL_VANITY = '{url_vanity}', URL_VANITY = '{url_vanity}'
} }
export interface WebhookData { export interface WebhookData {
@ -77,12 +77,12 @@ export class WebhookHelper {
await fetch(config.webhooks.url, { await fetch(config.webhooks.url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
username: config.webhooks.username, username: config.webhooks.username,
content: WebhookHelper.parseContent(content, data), content: WebhookHelper.parseContent(content, data)
}), })
}); });
} catch (e) { } catch (e) {
Console.logger(WebhookHelper).error(e); Console.logger(WebhookHelper).error(e);

View file

@ -16,7 +16,7 @@ export enum ConsoleLevel {
ERROR, ERROR,
INFO, INFO,
TRACE, TRACE,
VERBOSE, VERBOSE
} }
export class Console { export class Console {

View file

@ -1,12 +1,5 @@
import { ConsoleLevel } from '.'; import { ConsoleLevel } from '.';
import { import { blue, red, reset, white, yellow } from '@dicedtomato/colors';
blue,
red,
reset,
white,
yellow
} from '@dicedtomato/colors';
export interface Formatter { export interface Formatter {
format( format(
@ -24,7 +17,7 @@ export class DefaultFormatter implements Formatter {
1: red('error') + ':', 1: red('error') + ':',
2: blue('info') + ':', 2: blue('info') + ':',
3: white('trace') + ':', 3: white('trace') + ':',
4: yellow('verbose') + ':', 4: yellow('verbose') + ':'
}[level]; }[level];
} }
@ -38,4 +31,4 @@ export class DefaultFormatter implements Formatter {
level level
)} ${reset(message)}`; )} ${reset(message)}`;
} }
} }

View file

@ -4,30 +4,30 @@ const darkTheme = createMuiTheme({
palette: { palette: {
type: 'dark', type: 'dark',
primary: { primary: {
main: '#fff', main: '#fff'
}, },
secondary: { secondary: {
main: '#4a5bb0', main: '#4a5bb0'
}, },
background: { background: {
default: '#111111', default: '#111111',
paper: '#000000', paper: '#000000'
}, }
}, },
overrides: { overrides: {
MuiListItem: { MuiListItem: {
root: { root: {
'&$selected': { '&$selected': {
backgroundColor: '#1F1F1F', backgroundColor: '#1F1F1F'
}, }
}, }
}, },
MuiCard: { MuiCard: {
root: { root: {
backgroundColor: '#080808', backgroundColor: '#080808'
}, }
}, }
}, }
}); });
export default darkTheme; export default darkTheme;

View file

@ -36,7 +36,7 @@ function MyApp({ Component, pageProps }) {
MyApp.propTypes = { MyApp.propTypes = {
Component: PropTypes.elementType.isRequired, Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired, pageProps: PropTypes.object.isRequired
}; };
export default MyApp; export default MyApp;

View file

@ -44,9 +44,10 @@ MyDocument.getInitialProps = async ctx => {
const sheets = new ServerStyleSheets(); const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage; const originalRenderPage = ctx.renderPage;
ctx.renderPage = () => originalRenderPage({ ctx.renderPage = () =>
enhanceApp: App => props => sheets.collect(<App {...props} />), originalRenderPage({
}); enhanceApp: App => props => sheets.collect(<App {...props} />)
});
const initialProps = await Document.getInitialProps(ctx); const initialProps = await Document.getInitialProps(ctx);
return { return {
@ -54,7 +55,7 @@ MyDocument.getInitialProps = async ctx => {
config: Configuration.readConfig(), config: Configuration.readConfig(),
styles: [ styles: [
...React.Children.toArray(initialProps.styles), ...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(), sheets.getStyleElement()
], ]
}; };
}; };

View file

@ -20,24 +20,22 @@ import { ConfigUploader } from '../lib/Config';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
margin: { margin: {
margin: '5px', margin: '5px'
}, },
padding: { padding: {
border: '1px solid #1f1f1f', border: '1px solid #1f1f1f',
padding: '10px', padding: '10px'
}, },
backdrop: { backdrop: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
color: '#fff', color: '#fff'
}, },
gridList: { gridList: {
width: theme.zIndex.drawer + 1, width: theme.zIndex.drawer + 1,
height: 450, height: 450
}, }
})); }));
export default function Images({ config }: { config: ConfigUploader }) { export default function Images({ config }: { config: ConfigUploader }) {
const classes = useStyles(); const classes = useStyles();
const router = useRouter(); const router = useRouter();
@ -89,7 +87,7 @@ export default function Images({ config }: { config: ConfigUploader }) {
if (!selectedImage) return; if (!selectedImage) return;
const d = await ( const d = await (
await fetch(`/api/images/${selectedImage.id}`, { await fetch(`/api/images/${selectedImage.id}`, {
method: 'DELETE', method: 'DELETE'
}) })
).json(); ).json();
if (!d.error) { if (!d.error) {
@ -138,11 +136,11 @@ export default function Images({ config }: { config: ConfigUploader }) {
anchorEl={anchorEl} anchorEl={anchorEl}
anchorOrigin={{ anchorOrigin={{
vertical: 'center', vertical: 'center',
horizontal: 'center', horizontal: 'center'
}} }}
transformOrigin={{ transformOrigin={{
vertical: 'center', vertical: 'center',
horizontal: 'center', horizontal: 'center'
}} }}
onClose={() => setAnchorEl(null)} onClose={() => setAnchorEl(null)}
disableRestoreFocus disableRestoreFocus

View file

@ -16,16 +16,16 @@ import { ConfigUploader } from '../lib/Config';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
margin: { margin: {
margin: '5px', margin: '5px'
}, },
padding: { padding: {
border: '1px solid #1f1f1f', border: '1px solid #1f1f1f',
padding: '10px', padding: '10px'
}, },
backdrop: { backdrop: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
color: '#fff', color: '#fff'
}, }
})); }));
export default function Index({ config }: { config: ConfigUploader }) { export default function Index({ config }: { config: ConfigUploader }) {
@ -86,9 +86,8 @@ export default function Index({ config }: { config: ConfigUploader }) {
})} })}
</Grid> </Grid>
</Paper> </Paper>
) : null ) : null}
} </UI>
</UI >
); );
} }
return <UIPlaceholder />; return <UIPlaceholder />;

View file

@ -17,19 +17,19 @@ import { store } from '../store';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
margin: { margin: {
margin: '5px', margin: '5px'
}, },
padding: { padding: {
border: '1px solid #1f1f1f', border: '1px solid #1f1f1f',
padding: '10px', padding: '10px'
}, },
backdrop: { backdrop: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
color: '#fff', color: '#fff'
}, },
tableBorder: { tableBorder: {
borderColor: '#130929', borderColor: '#130929'
}, }
})); }));
export default function Index() { export default function Index() {
@ -68,16 +68,10 @@ export default function Index() {
<TableCell className={classes.tableBorder}> <TableCell className={classes.tableBorder}>
User User
</TableCell> </TableCell>
<TableCell <TableCell className={classes.tableBorder} align='right'>
className={classes.tableBorder}
align='right'
>
Images Images
</TableCell> </TableCell>
<TableCell <TableCell className={classes.tableBorder} align='right'>
className={classes.tableBorder}
align='right'
>
Views Views
</TableCell> </TableCell>
</TableRow> </TableRow>

View file

@ -20,16 +20,16 @@ import { ConfigUploader } from '../lib/Config';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
margin: { margin: {
margin: '5px', margin: '5px'
}, },
padding: { padding: {
border: '1px solid #1f1f1f', border: '1px solid #1f1f1f',
padding: '10px', padding: '10px'
}, },
backdrop: { backdrop: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
color: '#fff', color: '#fff'
}, }
})); }));
export default function Urls({ config }: { config: ConfigUploader }) { export default function Urls({ config }: { config: ConfigUploader }) {
@ -67,7 +67,7 @@ export default function Urls({ config }: { config: ConfigUploader }) {
<Snackbar <Snackbar
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'center', horizontal: 'center'
}} }}
open={alertOpen} open={alertOpen}
autoHideDuration={6000} autoHideDuration={6000}

View file

@ -27,19 +27,19 @@ import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
margin: { margin: {
margin: '5px', margin: '5px'
}, },
padding: { padding: {
border: '1px solid #1f1f1f', border: '1px solid #1f1f1f',
padding: '10px', padding: '10px'
}, },
field: { field: {
width: '100%', width: '100%'
}, },
backdrop: { backdrop: {
zIndex: theme.zIndex.drawer + 1, zIndex: theme.zIndex.drawer + 1,
color: '#fff', color: '#fff'
}, }
})); }));
export default function Index() { export default function Index() {
@ -82,7 +82,9 @@ export default function Index() {
}; };
const deleteUserThenClose = async () => { const deleteUserThenClose = async () => {
const d = await (await fetch('/api/user/' + user.id, { method: 'DELETE' })).json(); const d = await (
await fetch('/api/user/' + user.id, { method: 'DELETE' })
).json();
if (!d.error) { if (!d.error) {
setDeleteOpen(false); setDeleteOpen(false);
setAlertOpen(true); setAlertOpen(true);
@ -92,13 +94,17 @@ export default function Index() {
}; };
const createUserThenClose = async () => { const createUserThenClose = async () => {
const d = await (await fetch('/api/user/create', { const d = await (
headers: { 'Content-Type': 'application/json' }, await fetch('/api/user/create', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method: 'POST',
username, password, administrator body: JSON.stringify({
username,
password,
administrator
})
}) })
})).json(); ).json();
if (!d.error) { if (!d.error) {
setCreateOpen(false); setCreateOpen(false);
setAlertOpen(true); setAlertOpen(true);
@ -115,7 +121,7 @@ export default function Index() {
<Snackbar <Snackbar
anchorOrigin={{ anchorOrigin={{
vertical: 'top', vertical: 'top',
horizontal: 'center', horizontal: 'center'
}} }}
open={alertOpen} open={alertOpen}
autoHideDuration={6000} autoHideDuration={6000}
@ -167,8 +173,14 @@ export default function Index() {
onChange={e => setPassword(e.target.value)} onChange={e => setPassword(e.target.value)}
/> />
<FormControlLabel <FormControlLabel
control={<Switch checked={administrator} onChange={() => setAdministrator(!administrator)} name="admin" />} control={
label="Administrator" <Switch
checked={administrator}
onChange={() => setAdministrator(!administrator)}
name='admin'
/>
}
label='Administrator'
/> />
</DialogContentText> </DialogContentText>
</DialogContent> </DialogContent>
@ -185,7 +197,10 @@ export default function Index() {
<Paper elevation={3} className={classes.padding}> <Paper elevation={3} className={classes.padding}>
<Typography variant='h5'> <Typography variant='h5'>
User User
<IconButton aria-label='Create User' onClick={() => setCreateOpen(true)}> <IconButton
aria-label='Create User'
onClick={() => setCreateOpen(true)}
>
<AddIcon /> <AddIcon />
</IconButton> </IconButton>
</Typography> </Typography>
@ -203,7 +218,9 @@ export default function Index() {
</IconButton> </IconButton>
} }
title={`${u.username} (${u.id})`} title={`${u.username} (${u.id})`}
subheader={`${u.administrator ? 'Administrator' : 'User'}`} subheader={`${
u.administrator ? 'Administrator' : 'User'
}`}
/> />
</Card> </Card>
</Grid> </Grid>

View file

@ -16,7 +16,7 @@ export interface State {
const initialState: State = { const initialState: State = {
loggedIn: false, loggedIn: false,
user: null, user: null,
loading: true, loading: true
}; };
export function reducer(state: State = initialState, action) { export function reducer(state: State = initialState, action) {

View file

@ -7,7 +7,7 @@ import { reducer } from './reducer';
const persistedReducer = persistReducer( const persistedReducer = persistReducer(
{ {
key: 'root', key: 'root',
storage, storage
}, },
reducer reducer
); );