mirror of
https://github.com/diced/zipline.git
synced 2025-05-10 18:05:54 +02:00
nice
This commit is contained in:
parent
e6efff28dd
commit
d7c7c9da18
27 changed files with 244 additions and 232 deletions
15
.eslintrc.js
15
.eslintrc.js
|
@ -1,25 +1,25 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
es2021: true,
|
||||
node: true,
|
||||
node: true
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
version: 'detect'
|
||||
}
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
jsx: true
|
||||
},
|
||||
ecmaVersion: 12,
|
||||
sourceType: 'module',
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint'],
|
||||
rules: {
|
||||
|
@ -29,5 +29,6 @@ module.exports = {
|
|||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: ['error', 'single'],
|
||||
semi: ['error', 'always'],
|
||||
},
|
||||
'comma-dangle': ['error', 'never']
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,5 +2,7 @@
|
|||
"$schema": "http://json.schemastore.org/prettierrc",
|
||||
"jsxSingleQuote": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
"arrowParens": "avoid",
|
||||
"trailingComma": "none",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
|
|
10
README.md
10
README.md
|
@ -1,16 +1,18 @@
|
|||

|
||||
---
|
||||
## 
|
||||
|
||||
[](https://app.codacy.com/gh/ZiplineProject/zipline?utm_source=github.com&utm_medium=referral&utm_content=ZiplineProject/zipline&utm_campaign=Badge_Grade)
|
||||
|
||||
# ZiplineNext
|
||||
|
||||
Speed & reliable
|
||||
|
||||
- Configurable
|
||||
- Fast (API)
|
||||
- Built with Next.js & React
|
||||
- Support for multible database types (mongo soon)
|
||||
- Support for multible database types (mongo soon)
|
||||
|
||||
# Documentation
|
||||
|
||||
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._
|
||||
|
|
40
setup.js
40
setup.js
|
@ -10,7 +10,7 @@ const base = {
|
|||
description: 'My Zipline Server',
|
||||
thumbnail:
|
||||
'https://github.githubassets.com/images/modules/open_graph/github-mark.png',
|
||||
color: '#128377',
|
||||
color: '#128377'
|
||||
},
|
||||
core: { secret: 'my-secret', port: 3000 },
|
||||
uploader: {
|
||||
|
@ -18,9 +18,9 @@ const base = {
|
|||
route: '/u',
|
||||
length: 6,
|
||||
original: false,
|
||||
blacklisted: [],
|
||||
blacklisted: []
|
||||
},
|
||||
urls: { route: '/s', length: 4, vanity: false },
|
||||
urls: { route: '/s', length: 4, vanity: false }
|
||||
};
|
||||
|
||||
(async () => {
|
||||
|
@ -37,34 +37,34 @@ const base = {
|
|||
{ name: 'mssql' },
|
||||
{ name: 'sqlite' },
|
||||
{ name: 'sqlite3' },
|
||||
{ name: 'mongodb', extra: 'No support yet' },
|
||||
],
|
||||
{ name: 'mongodb', extra: 'No support yet' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'host',
|
||||
message: 'Database Host',
|
||||
message: 'Database Host'
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'port',
|
||||
message: 'Database Port',
|
||||
message: 'Database Port'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'database',
|
||||
message: 'Database Name',
|
||||
message: 'Database Name'
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'username',
|
||||
message: 'Database User',
|
||||
message: 'Database User'
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
message: 'Database Password',
|
||||
},
|
||||
message: 'Database Password'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\nCore\n');
|
||||
|
@ -73,13 +73,13 @@ const base = {
|
|||
{
|
||||
type: 'input',
|
||||
name: 'secret',
|
||||
message: 'Secret (this must be secure)',
|
||||
message: 'Secret (this must be secure)'
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'port',
|
||||
message: 'Serve on Port',
|
||||
},
|
||||
message: 'Serve on Port'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\nUploader\n');
|
||||
|
@ -88,13 +88,13 @@ const base = {
|
|||
{
|
||||
type: 'input',
|
||||
name: 'directory',
|
||||
message: 'Uploads Directory',
|
||||
message: 'Uploads Directory'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'original',
|
||||
message: 'Keep Original?',
|
||||
},
|
||||
message: 'Keep Original?'
|
||||
}
|
||||
]);
|
||||
|
||||
console.log('\nURLs\n');
|
||||
|
@ -103,8 +103,8 @@ const base = {
|
|||
{
|
||||
type: 'confirm',
|
||||
name: 'vanity',
|
||||
message: 'Allow vanity URLs',
|
||||
},
|
||||
message: 'Allow vanity URLs'
|
||||
}
|
||||
]);
|
||||
|
||||
const config = {
|
||||
|
@ -112,7 +112,7 @@ const base = {
|
|||
meta: { ...base.meta },
|
||||
core: { ...base.core, ...core },
|
||||
uploader: { ...base.uploader, ...uploader },
|
||||
urls: { ...base.urls, ...urls },
|
||||
urls: { ...base.urls, ...urls }
|
||||
};
|
||||
writeFileSync('Zipline.toml', stringify(config));
|
||||
})();
|
||||
|
|
|
@ -14,11 +14,11 @@ import { useDispatch } from 'react-redux';
|
|||
|
||||
const useStyles = makeStyles({
|
||||
field: {
|
||||
width: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
padding: {
|
||||
padding: '10px',
|
||||
},
|
||||
padding: '10px'
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
|
@ -40,7 +40,7 @@ export default function Login() {
|
|||
await fetch('/api/user/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
body: JSON.stringify({ username, password })
|
||||
})
|
||||
).json();
|
||||
if (!d.error) {
|
||||
|
@ -55,7 +55,7 @@ export default function Login() {
|
|||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
|
|
|
@ -14,17 +14,17 @@ import { useDispatch } from 'react-redux';
|
|||
|
||||
const useStyles = makeStyles({
|
||||
margin: {
|
||||
margin: '5px',
|
||||
margin: '5px'
|
||||
},
|
||||
padding: {
|
||||
padding: '10px',
|
||||
padding: '10px'
|
||||
},
|
||||
field: {
|
||||
width: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
button: {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
marginLeft: 'auto'
|
||||
}
|
||||
});
|
||||
|
||||
export default function ManageUser() {
|
||||
|
@ -40,7 +40,7 @@ export default function ManageUser() {
|
|||
await fetch('/api/user', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
body: JSON.stringify({ username, password })
|
||||
})
|
||||
).json();
|
||||
if (!d.error) {
|
||||
|
@ -53,7 +53,7 @@ export default function ManageUser() {
|
|||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
|
@ -96,4 +96,4 @@ export default function ManageUser() {
|
|||
</Card>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,14 +41,14 @@ const drawerWidth = 240;
|
|||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flexGrow: 1
|
||||
},
|
||||
drawer: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
flexShrink: 0
|
||||
},
|
||||
outlineColor: '#fff',
|
||||
outlineColor: '#fff'
|
||||
},
|
||||
appBar: {
|
||||
display: 'flex',
|
||||
|
@ -56,31 +56,31 @@ const useStyles = makeStyles(theme => ({
|
|||
color: '#fff',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'calc(100%)',
|
||||
marginLeft: drawerWidth,
|
||||
marginLeft: drawerWidth
|
||||
},
|
||||
borderBottom: '1px solid #1f1f1f',
|
||||
borderBottom: '1px solid #1f1f1f'
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
rightButton: {
|
||||
marginLeft: 'auto',
|
||||
marginLeft: 'auto'
|
||||
},
|
||||
// necessary for content to be below app bar
|
||||
toolbar: theme.mixins.toolbar,
|
||||
drawerPaper: {
|
||||
width: drawerWidth,
|
||||
width: drawerWidth
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(1),
|
||||
padding: theme.spacing(1)
|
||||
},
|
||||
menuIcon: {
|
||||
marginRight: '10px',
|
||||
},
|
||||
marginRight: '10px'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function UI({ children }) {
|
||||
|
@ -175,12 +175,12 @@ export default function UI({ children }) {
|
|||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
open={open}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
|
@ -271,7 +271,7 @@ export default function UI({ children }) {
|
|||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
|
@ -349,10 +349,10 @@ export default function UI({ children }) {
|
|||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
paper: classes.drawerPaper
|
||||
}}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
keepMounted: true // Better open performance on mobile.
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
|
@ -361,7 +361,7 @@ export default function UI({ children }) {
|
|||
<Hidden xsDown implementation='css'>
|
||||
<Drawer
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
paper: classes.drawerPaper
|
||||
}}
|
||||
variant='permanent'
|
||||
open
|
||||
|
|
|
@ -15,41 +15,41 @@ import { makeStyles, useTheme } from '@material-ui/core/styles';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
display: 'flex'
|
||||
},
|
||||
drawer: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 240,
|
||||
flexShrink: 0,
|
||||
},
|
||||
flexShrink: 0
|
||||
}
|
||||
},
|
||||
appBar: {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'calc(100%)',
|
||||
marginLeft: 240,
|
||||
},
|
||||
marginLeft: 240
|
||||
}
|
||||
},
|
||||
menuButton: {
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
rightButton: {
|
||||
marginLeft: 'auto',
|
||||
marginLeft: 'auto'
|
||||
},
|
||||
// necessary for content to be below app bar
|
||||
toolbar: theme.mixins.toolbar,
|
||||
drawerPaper: {
|
||||
width: 240,
|
||||
width: 240
|
||||
},
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(3),
|
||||
padding: theme.spacing(3)
|
||||
},
|
||||
fullWidth: {
|
||||
width: '100%',
|
||||
},
|
||||
width: '100%'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function UIPlaceholder() {
|
||||
|
@ -132,10 +132,10 @@ export default function UIPlaceholder() {
|
|||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
paper: classes.drawerPaper
|
||||
}}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
keepMounted: true // Better open performance on mobile.
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
|
@ -144,7 +144,7 @@ export default function UIPlaceholder() {
|
|||
<Hidden xsDown implementation='css'>
|
||||
<Drawer
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
paper: classes.drawerPaper
|
||||
}}
|
||||
variant='permanent'
|
||||
open
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
FastifyInstanceToken,
|
||||
Inject,
|
||||
GET,
|
||||
DELETE,
|
||||
DELETE
|
||||
} from 'fastify-decorators';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Image } from '../entities/Image';
|
||||
|
@ -30,8 +30,8 @@ export class ImagesController {
|
|||
|
||||
const images = await this.images.find({
|
||||
where: {
|
||||
user: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
user: readBaseCookie(req.cookies.zipline)
|
||||
}
|
||||
});
|
||||
|
||||
return reply.send(images);
|
||||
|
@ -47,21 +47,21 @@ export class ImagesController {
|
|||
const image = await this.images.findOne({
|
||||
where: {
|
||||
user: readBaseCookie(req.cookies.zipline),
|
||||
id: req.params.id,
|
||||
},
|
||||
id: req.params.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!image) throw new Error('No image');
|
||||
|
||||
this.images.delete({
|
||||
id: req.params.id,
|
||||
id: req.params.id
|
||||
});
|
||||
|
||||
Console.logger(Image).info(`image ${image.id} was deleted`);
|
||||
if (this.webhooks.events.includes(WebhookType.DELETE_IMAGE))
|
||||
WebhookHelper.sendWebhook(this.webhooks.upload.content, {
|
||||
image,
|
||||
host: `${req.protocol}://${req.hostname}${config.uploader.route}/`,
|
||||
host: `${req.protocol}://${req.hostname}${config.uploader.route}/`
|
||||
});
|
||||
|
||||
return reply.send(image);
|
||||
|
@ -73,8 +73,8 @@ export class ImagesController {
|
|||
|
||||
const images = await this.images.find({
|
||||
where: {
|
||||
user: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
user: readBaseCookie(req.cookies.zipline)
|
||||
}
|
||||
});
|
||||
|
||||
return reply.send(images.slice(1).slice(-3).reverse());
|
||||
|
@ -86,8 +86,8 @@ export class ImagesController {
|
|||
|
||||
const images = await this.images.find({
|
||||
where: {
|
||||
user: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
user: readBaseCookie(req.cookies.zipline)
|
||||
}
|
||||
});
|
||||
|
||||
function chunk(array: Image[], size: number) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
POST,
|
||||
FastifyInstanceToken,
|
||||
Inject,
|
||||
GET,
|
||||
GET
|
||||
} from 'fastify-decorators';
|
||||
import { Multipart } from 'fastify-multipart';
|
||||
import { createWriteStream, existsSync, mkdirSync } from 'fs';
|
||||
|
@ -62,7 +62,7 @@ export class RootController {
|
|||
|
||||
for (const user of users) {
|
||||
const usersImages = await this.images.find({
|
||||
where: { user: user.id },
|
||||
where: { user: user.id }
|
||||
});
|
||||
|
||||
lb.push({
|
||||
|
@ -70,14 +70,14 @@ export class RootController {
|
|||
images: usersImages.length,
|
||||
views: usersImages
|
||||
.map(x => x.views)
|
||||
.reduce((a, b) => Number(a) + Number(b), 0),
|
||||
.reduce((a, b) => Number(a) + Number(b), 0)
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
images: images.length,
|
||||
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({
|
||||
where: {
|
||||
token: req.headers.authorization,
|
||||
},
|
||||
token: req.headers.authorization
|
||||
}
|
||||
});
|
||||
if (!user) return new AuthError('Incorrect token!');
|
||||
|
||||
|
@ -125,7 +125,7 @@ export class RootController {
|
|||
if (this.webhooks.events.includes(WebhookType.UPLOAD))
|
||||
WebhookHelper.sendWebhook(this.webhooks.upload.content, {
|
||||
image,
|
||||
host: `${req.protocol}://${req.hostname}${config.uploader.route}/`,
|
||||
host: `${req.protocol}://${req.hostname}${config.uploader.route}/`
|
||||
});
|
||||
|
||||
reply.send(
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
Inject,
|
||||
GET,
|
||||
DELETE,
|
||||
POST,
|
||||
POST
|
||||
} from 'fastify-decorators';
|
||||
import { Repository } from 'typeorm';
|
||||
import { URL } from '../entities/URL';
|
||||
|
@ -34,8 +34,8 @@ export class URLSController {
|
|||
|
||||
const all = await this.urls.find({
|
||||
where: {
|
||||
user: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
user: readBaseCookie(req.cookies.zipline)
|
||||
}
|
||||
});
|
||||
|
||||
return reply.send(all);
|
||||
|
@ -51,22 +51,22 @@ export class URLSController {
|
|||
const url = await this.urls.findOne({
|
||||
where: {
|
||||
user: readBaseCookie(req.cookies.zipline),
|
||||
id: req.params.id,
|
||||
},
|
||||
id: req.params.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!url) throw new Error('No url');
|
||||
|
||||
this.logger.verbose(`attempting to delete url ${url.id}`);
|
||||
this.urls.delete({
|
||||
id: req.params.id,
|
||||
id: req.params.id
|
||||
});
|
||||
|
||||
this.logger.info(`url ${url.id} was deleted`);
|
||||
if (this.webhooks.events.includes(WebhookType.DELETE_URL))
|
||||
WebhookHelper.sendWebhook(this.webhooks.delete_url.content, {
|
||||
url,
|
||||
host: `${req.protocol}://${req.hostname}${config.urls.route}/`,
|
||||
host: `${req.protocol}://${req.hostname}${config.urls.route}/`
|
||||
});
|
||||
|
||||
return reply.send(url);
|
||||
|
@ -82,16 +82,16 @@ export class URLSController {
|
|||
if (config.urls.vanity && req.body.vanity) {
|
||||
const existingVanity = await this.urls.findOne({
|
||||
where: {
|
||||
vanity: req.body.vanity,
|
||||
},
|
||||
vanity: req.body.vanity
|
||||
}
|
||||
});
|
||||
if (existingVanity) throw new Error('There is an existing vanity!');
|
||||
}
|
||||
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
id: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
id: readBaseCookie(req.cookies.zipline)
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) throw new LoginError('No user');
|
||||
|
@ -107,7 +107,7 @@ export class URLSController {
|
|||
if (this.webhooks.events.includes(WebhookType.SHORTEN))
|
||||
WebhookHelper.sendWebhook(this.webhooks.shorten.content, {
|
||||
url,
|
||||
host: `${req.protocol}://${req.hostname}${config.urls.route}/`,
|
||||
host: `${req.protocol}://${req.hostname}${config.urls.route}/`
|
||||
});
|
||||
|
||||
return reply.send(url);
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
PATCH,
|
||||
FastifyInstanceToken,
|
||||
Inject,
|
||||
DELETE,
|
||||
DELETE
|
||||
} from 'fastify-decorators';
|
||||
import { Repository } from 'typeorm';
|
||||
import { User } from '../entities/User';
|
||||
|
@ -14,7 +14,7 @@ import {
|
|||
UserNotFoundError,
|
||||
MissingBodyData,
|
||||
LoginError,
|
||||
UserExistsError,
|
||||
UserExistsError
|
||||
} from '../lib/api/APIErrors';
|
||||
import { Configuration, ConfigWebhooks } from '../lib/Config';
|
||||
import { Console } from '../lib/logger';
|
||||
|
@ -23,7 +23,7 @@ import {
|
|||
createBaseCookie,
|
||||
createToken,
|
||||
encryptPassword,
|
||||
readBaseCookie,
|
||||
readBaseCookie
|
||||
} from '../lib/Util';
|
||||
import { WebhookType, WebhookHelper } from '../lib/Webhooks';
|
||||
|
||||
|
@ -41,7 +41,7 @@ export class UserController {
|
|||
@GET('/login-status')
|
||||
async loginStatus(req: FastifyRequest, reply: FastifyReply) {
|
||||
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.');
|
||||
const user = await this.users.findOne({
|
||||
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;
|
||||
return reply.send(user);
|
||||
}
|
||||
|
@ -67,10 +68,11 @@ export class UserController {
|
|||
|
||||
const user = await this.users.findOne({
|
||||
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})`);
|
||||
user.username = req.body.username;
|
||||
|
@ -80,7 +82,7 @@ export class UserController {
|
|||
this.logger.info(`saved ${user.username} (${user.id})`);
|
||||
if (this.webhooks.events.includes(WebhookType.USER_EDIT))
|
||||
WebhookHelper.sendWebhook(this.webhooks.user_edit.content, {
|
||||
user,
|
||||
user
|
||||
});
|
||||
|
||||
delete user.password;
|
||||
|
@ -98,8 +100,8 @@ export class UserController {
|
|||
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
username: req.body.username,
|
||||
},
|
||||
username: req.body.username
|
||||
}
|
||||
});
|
||||
|
||||
if (!user)
|
||||
|
@ -115,13 +117,13 @@ export class UserController {
|
|||
this.logger.verbose(`set cookie for ${user.username} (${user.id})`);
|
||||
reply.setCookie('zipline', createBaseCookie(user.id), {
|
||||
path: '/',
|
||||
maxAge: 1036800000,
|
||||
maxAge: 1036800000
|
||||
});
|
||||
|
||||
this.logger.info(`${user.username} (${user.id}) logged in`);
|
||||
if (this.webhooks.events.includes(WebhookType.LOGIN))
|
||||
WebhookHelper.sendWebhook(this.webhooks.login.content, {
|
||||
user,
|
||||
user
|
||||
});
|
||||
|
||||
return reply.send(user);
|
||||
|
@ -144,8 +146,8 @@ export class UserController {
|
|||
|
||||
const user = await this.users.findOne({
|
||||
where: {
|
||||
id: readBaseCookie(req.cookies.zipline),
|
||||
},
|
||||
id: readBaseCookie(req.cookies.zipline)
|
||||
}
|
||||
});
|
||||
|
||||
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})`);
|
||||
if (this.webhooks.events.includes(WebhookType.TOKEN_RESET))
|
||||
WebhookHelper.sendWebhook(this.webhooks.token_reset.content, {
|
||||
user,
|
||||
user
|
||||
});
|
||||
|
||||
return reply.send({ updated: true });
|
||||
|
@ -176,7 +178,7 @@ export class UserController {
|
|||
if (!req.body.password) throw new MissingBodyData('Missing uassword.');
|
||||
|
||||
const existing = await this.users.findOne({
|
||||
where: { username: req.body.username },
|
||||
where: { username: req.body.username }
|
||||
});
|
||||
if (existing) throw new UserExistsError('User exists already');
|
||||
|
||||
|
@ -193,7 +195,7 @@ export class UserController {
|
|||
this.logger.info(`created user ${user.username} (${user.id})`);
|
||||
if (this.webhooks.events.includes(WebhookType.CREATE_USER))
|
||||
WebhookHelper.sendWebhook(this.webhooks.create_user.content, {
|
||||
user,
|
||||
user
|
||||
});
|
||||
|
||||
delete user.password;
|
||||
|
@ -213,7 +215,7 @@ export class UserController {
|
|||
reply: FastifyReply
|
||||
) {
|
||||
const existing = await this.users.findOne({
|
||||
where: { id: req.params.id },
|
||||
where: { id: req.params.id }
|
||||
});
|
||||
if (!existing) throw new UserExistsError('User doesnt exist');
|
||||
|
||||
|
@ -222,13 +224,13 @@ export class UserController {
|
|||
`attempting to delete ${existing.username} (${existing.id})`
|
||||
);
|
||||
await this.users.delete({
|
||||
id: existing.id,
|
||||
id: existing.id
|
||||
});
|
||||
|
||||
this.logger.info(`deleted ${existing.username} (${existing.id})`);
|
||||
if (this.webhooks.events.includes(WebhookType.USER_DELETE))
|
||||
WebhookHelper.sendWebhook(this.webhooks.user_delete.content, {
|
||||
user: existing,
|
||||
user: existing
|
||||
});
|
||||
|
||||
return reply.send({ ok: true });
|
||||
|
|
28
src/index.ts
28
src/index.ts
|
@ -29,7 +29,6 @@ Mode : ${bold(dev ? red('dev') : green('production'))}
|
|||
Verbose : ${bold(process.env.VERBOSE ? red('yes') : green('no'))}
|
||||
`);
|
||||
|
||||
|
||||
Console.logger(Configuration).verbose('searching for config...');
|
||||
const config = Configuration.readConfig();
|
||||
if (!config) {
|
||||
|
@ -71,14 +70,14 @@ server.get(`${config.urls.route}/:id`, async function (
|
|||
|
||||
const urlId = await urls.findOne({
|
||||
where: {
|
||||
id: req.params.id,
|
||||
},
|
||||
id: req.params.id
|
||||
}
|
||||
});
|
||||
|
||||
const urlVanity = await urls.findOne({
|
||||
where: {
|
||||
vanity: req.params.id,
|
||||
},
|
||||
vanity: req.params.id
|
||||
}
|
||||
});
|
||||
|
||||
if (config.urls.vanity && urlVanity) return reply.redirect(urlVanity.url);
|
||||
|
@ -95,7 +94,7 @@ server.register(fastifyTypeorm, {
|
|||
...config.database,
|
||||
entities: [dev ? './src/entities/**/*.ts' : './dist/entities/**/*.js'],
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
logging: false
|
||||
});
|
||||
|
||||
server.register(bootstrap, {
|
||||
|
@ -103,23 +102,23 @@ server.register(bootstrap, {
|
|||
UserController,
|
||||
RootController,
|
||||
ImagesController,
|
||||
URLSController,
|
||||
],
|
||||
URLSController
|
||||
]
|
||||
});
|
||||
|
||||
server.register(fastifyCookies, {
|
||||
secret: config.core.secret,
|
||||
secret: config.core.secret
|
||||
});
|
||||
|
||||
server.register(fastifyStatic, {
|
||||
root: join(process.cwd(), config.uploader.directory),
|
||||
prefix: config.uploader.route,
|
||||
prefix: config.uploader.route
|
||||
});
|
||||
|
||||
server.register(fastifyStatic, {
|
||||
root: join(process.cwd(), 'public'),
|
||||
prefix: '/public',
|
||||
decorateReply: false,
|
||||
decorateReply: false
|
||||
});
|
||||
|
||||
server.register(fastifyFavicon);
|
||||
|
@ -136,8 +135,11 @@ server.listen(config.core.port, err => {
|
|||
});
|
||||
|
||||
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);
|
||||
return (reply.sent = true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -67,7 +67,8 @@ export class Configuration {
|
|||
try {
|
||||
const data = readFileSync(resolve(process.cwd(), 'Zipline.toml'), 'utf8');
|
||||
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;
|
||||
} catch (e) {
|
||||
return null;
|
||||
|
|
|
@ -25,7 +25,7 @@ export enum WebhookParseTokens {
|
|||
USER_ADMIN = '{user_admin}',
|
||||
URL_ID = '{url_id}',
|
||||
URL_URL = '{url}',
|
||||
URL_VANITY = '{url_vanity}',
|
||||
URL_VANITY = '{url_vanity}'
|
||||
}
|
||||
|
||||
export interface WebhookData {
|
||||
|
@ -77,12 +77,12 @@ export class WebhookHelper {
|
|||
await fetch(config.webhooks.url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: config.webhooks.username,
|
||||
content: WebhookHelper.parseContent(content, data),
|
||||
}),
|
||||
content: WebhookHelper.parseContent(content, data)
|
||||
})
|
||||
});
|
||||
} catch (e) {
|
||||
Console.logger(WebhookHelper).error(e);
|
||||
|
|
|
@ -16,7 +16,7 @@ export enum ConsoleLevel {
|
|||
ERROR,
|
||||
INFO,
|
||||
TRACE,
|
||||
VERBOSE,
|
||||
VERBOSE
|
||||
}
|
||||
|
||||
export class Console {
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
import { ConsoleLevel } from '.';
|
||||
import {
|
||||
blue,
|
||||
red,
|
||||
reset,
|
||||
white,
|
||||
yellow
|
||||
} from '@dicedtomato/colors';
|
||||
|
||||
import { blue, red, reset, white, yellow } from '@dicedtomato/colors';
|
||||
|
||||
export interface Formatter {
|
||||
format(
|
||||
|
@ -24,7 +17,7 @@ export class DefaultFormatter implements Formatter {
|
|||
1: red('error') + ':',
|
||||
2: blue('info') + ':',
|
||||
3: white('trace') + ':',
|
||||
4: yellow('verbose') + ':',
|
||||
4: yellow('verbose') + ':'
|
||||
}[level];
|
||||
}
|
||||
|
||||
|
@ -38,4 +31,4 @@ export class DefaultFormatter implements Formatter {
|
|||
level
|
||||
)} ${reset(message)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,30 +4,30 @@ const darkTheme = createMuiTheme({
|
|||
palette: {
|
||||
type: 'dark',
|
||||
primary: {
|
||||
main: '#fff',
|
||||
main: '#fff'
|
||||
},
|
||||
secondary: {
|
||||
main: '#4a5bb0',
|
||||
main: '#4a5bb0'
|
||||
},
|
||||
background: {
|
||||
default: '#111111',
|
||||
paper: '#000000',
|
||||
},
|
||||
paper: '#000000'
|
||||
}
|
||||
},
|
||||
overrides: {
|
||||
MuiListItem: {
|
||||
root: {
|
||||
'&$selected': {
|
||||
backgroundColor: '#1F1F1F',
|
||||
},
|
||||
},
|
||||
backgroundColor: '#1F1F1F'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCard: {
|
||||
root: {
|
||||
backgroundColor: '#080808',
|
||||
},
|
||||
},
|
||||
},
|
||||
backgroundColor: '#080808'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default darkTheme;
|
||||
|
|
|
@ -36,7 +36,7 @@ function MyApp({ Component, pageProps }) {
|
|||
|
||||
MyApp.propTypes = {
|
||||
Component: PropTypes.elementType.isRequired,
|
||||
pageProps: PropTypes.object.isRequired,
|
||||
pageProps: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default MyApp;
|
||||
|
|
|
@ -44,9 +44,10 @@ MyDocument.getInitialProps = async ctx => {
|
|||
const sheets = new ServerStyleSheets();
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
|
||||
ctx.renderPage = () => originalRenderPage({
|
||||
enhanceApp: App => props => sheets.collect(<App {...props} />),
|
||||
});
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: App => props => sheets.collect(<App {...props} />)
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
return {
|
||||
|
@ -54,7 +55,7 @@ MyDocument.getInitialProps = async ctx => {
|
|||
config: Configuration.readConfig(),
|
||||
styles: [
|
||||
...React.Children.toArray(initialProps.styles),
|
||||
sheets.getStyleElement(),
|
||||
],
|
||||
sheets.getStyleElement()
|
||||
]
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,24 +20,22 @@ import { ConfigUploader } from '../lib/Config';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
margin: {
|
||||
margin: '5px',
|
||||
margin: '5px'
|
||||
},
|
||||
padding: {
|
||||
border: '1px solid #1f1f1f',
|
||||
padding: '10px',
|
||||
padding: '10px'
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
color: '#fff',
|
||||
color: '#fff'
|
||||
},
|
||||
gridList: {
|
||||
width: theme.zIndex.drawer + 1,
|
||||
height: 450,
|
||||
},
|
||||
height: 450
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export default function Images({ config }: { config: ConfigUploader }) {
|
||||
const classes = useStyles();
|
||||
const router = useRouter();
|
||||
|
@ -89,7 +87,7 @@ export default function Images({ config }: { config: ConfigUploader }) {
|
|||
if (!selectedImage) return;
|
||||
const d = await (
|
||||
await fetch(`/api/images/${selectedImage.id}`, {
|
||||
method: 'DELETE',
|
||||
method: 'DELETE'
|
||||
})
|
||||
).json();
|
||||
if (!d.error) {
|
||||
|
@ -138,11 +136,11 @@ export default function Images({ config }: { config: ConfigUploader }) {
|
|||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: 'center',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'center',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
disableRestoreFocus
|
||||
|
|
|
@ -16,16 +16,16 @@ import { ConfigUploader } from '../lib/Config';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
margin: {
|
||||
margin: '5px',
|
||||
margin: '5px'
|
||||
},
|
||||
padding: {
|
||||
border: '1px solid #1f1f1f',
|
||||
padding: '10px',
|
||||
padding: '10px'
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
color: '#fff',
|
||||
},
|
||||
color: '#fff'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Index({ config }: { config: ConfigUploader }) {
|
||||
|
@ -86,9 +86,8 @@ export default function Index({ config }: { config: ConfigUploader }) {
|
|||
})}
|
||||
</Grid>
|
||||
</Paper>
|
||||
) : null
|
||||
}
|
||||
</UI >
|
||||
) : null}
|
||||
</UI>
|
||||
);
|
||||
}
|
||||
return <UIPlaceholder />;
|
||||
|
|
|
@ -17,19 +17,19 @@ import { store } from '../store';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
margin: {
|
||||
margin: '5px',
|
||||
margin: '5px'
|
||||
},
|
||||
padding: {
|
||||
border: '1px solid #1f1f1f',
|
||||
padding: '10px',
|
||||
padding: '10px'
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
color: '#fff',
|
||||
color: '#fff'
|
||||
},
|
||||
tableBorder: {
|
||||
borderColor: '#130929',
|
||||
},
|
||||
borderColor: '#130929'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Index() {
|
||||
|
@ -68,16 +68,10 @@ export default function Index() {
|
|||
<TableCell className={classes.tableBorder}>
|
||||
User
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={classes.tableBorder}
|
||||
align='right'
|
||||
>
|
||||
<TableCell className={classes.tableBorder} align='right'>
|
||||
Images
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className={classes.tableBorder}
|
||||
align='right'
|
||||
>
|
||||
<TableCell className={classes.tableBorder} align='right'>
|
||||
Views
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
@ -20,16 +20,16 @@ import { ConfigUploader } from '../lib/Config';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
margin: {
|
||||
margin: '5px',
|
||||
margin: '5px'
|
||||
},
|
||||
padding: {
|
||||
border: '1px solid #1f1f1f',
|
||||
padding: '10px',
|
||||
padding: '10px'
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
color: '#fff',
|
||||
},
|
||||
color: '#fff'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Urls({ config }: { config: ConfigUploader }) {
|
||||
|
@ -67,7 +67,7 @@ export default function Urls({ config }: { config: ConfigUploader }) {
|
|||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
|
|
|
@ -27,19 +27,19 @@ import { makeStyles } from '@material-ui/core';
|
|||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
margin: {
|
||||
margin: '5px',
|
||||
margin: '5px'
|
||||
},
|
||||
padding: {
|
||||
border: '1px solid #1f1f1f',
|
||||
padding: '10px',
|
||||
padding: '10px'
|
||||
},
|
||||
field: {
|
||||
width: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
backdrop: {
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
color: '#fff',
|
||||
},
|
||||
color: '#fff'
|
||||
}
|
||||
}));
|
||||
|
||||
export default function Index() {
|
||||
|
@ -82,7 +82,9 @@ export default function Index() {
|
|||
};
|
||||
|
||||
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) {
|
||||
setDeleteOpen(false);
|
||||
setAlertOpen(true);
|
||||
|
@ -92,13 +94,17 @@ export default function Index() {
|
|||
};
|
||||
|
||||
const createUserThenClose = async () => {
|
||||
const d = await (await fetch('/api/user/create', {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username, password, administrator
|
||||
const d = await (
|
||||
await fetch('/api/user/create', {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
administrator
|
||||
})
|
||||
})
|
||||
})).json();
|
||||
).json();
|
||||
if (!d.error) {
|
||||
setCreateOpen(false);
|
||||
setAlertOpen(true);
|
||||
|
@ -115,7 +121,7 @@ export default function Index() {
|
|||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
|
@ -167,8 +173,14 @@ export default function Index() {
|
|||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={administrator} onChange={() => setAdministrator(!administrator)} name="admin" />}
|
||||
label="Administrator"
|
||||
control={
|
||||
<Switch
|
||||
checked={administrator}
|
||||
onChange={() => setAdministrator(!administrator)}
|
||||
name='admin'
|
||||
/>
|
||||
}
|
||||
label='Administrator'
|
||||
/>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
|
@ -185,7 +197,10 @@ export default function Index() {
|
|||
<Paper elevation={3} className={classes.padding}>
|
||||
<Typography variant='h5'>
|
||||
User
|
||||
<IconButton aria-label='Create User' onClick={() => setCreateOpen(true)}>
|
||||
<IconButton
|
||||
aria-label='Create User'
|
||||
onClick={() => setCreateOpen(true)}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Typography>
|
||||
|
@ -203,7 +218,9 @@ export default function Index() {
|
|||
</IconButton>
|
||||
}
|
||||
title={`${u.username} (${u.id})`}
|
||||
subheader={`${u.administrator ? 'Administrator' : 'User'}`}
|
||||
subheader={`${
|
||||
u.administrator ? 'Administrator' : 'User'
|
||||
}`}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface State {
|
|||
const initialState: State = {
|
||||
loggedIn: false,
|
||||
user: null,
|
||||
loading: true,
|
||||
loading: true
|
||||
};
|
||||
|
||||
export function reducer(state: State = initialState, action) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { reducer } from './reducer';
|
|||
const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'root',
|
||||
storage,
|
||||
storage
|
||||
},
|
||||
reducer
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue